casaroli opened a new pull request, #21324: URL: https://github.com/apache/nuttx/pull/21324
## Summary POSIX: `send()`, `sendto()`, `sendmsg()` and `write()` on a stream socket that is no longer connected fail with `EPIPE`, and the calling thread also gets `SIGPIPE`, unless `MSG_NOSIGNAL` is given. NuttX returned `EPIPE` only. So `SIGPIPE` handlers and `CONFIG_SIG_SIGPIPE_ACTION` never saw it, and `MSG_NOSIGNAL` had no effect. `Documentation/reference/user/11_network.rst` and the comments of `send()`/`sendto()` already describe the signal. This PR adds `psock_sigpipe()`. It raises `SIGPIPE` in the calling thread when a send on a `SOCK_STREAM` or `SOCK_SEQPACKET` socket returned `-EPIPE` and `MSG_NOSIGNAL` is not set. The user interfaces call it: `sendto()` (and so `send()`), `sendmsg()`, and the `write` method of a socket (`write()`, `writev()`). The `psock_send*()` interfaces that the OS uses (telnet driver, NFS, 9P, userfs, OP-TEE socket, usrsock server, ...) do not raise it, and kernel threads do not get it. ## Impact All build modes with `CONFIG_NET`. No change without `CONFIG_NET`, and no change with `CONFIG_DISABLE_ALL_SIGNALS`. This changes the behaviour of the flat build too: `send()`, `sendto()`, `sendmsg()`, `write()` and `writev()` on a socket are the same functions for all tasks there. A task that gets `EPIPE` from one of these calls on a stream socket, without `MSG_NOSIGNAL`, now also gets `SIGPIPE`. With `CONFIG_SIG_SIGPIPE_ACTION=y` and no handler, the task now ends; on master it continued with `EPIPE`. A send gets `EPIPE` on master in these cases: a local (Unix) stream socket whose peer closed, a TCP or RPMsg socket after its own `shutdown(SHUT_WR)`, and a usrsock socket whose remote end closed. A TCP socket whose peer closed gets `ENOTCONN`, not `EPIPE`, so it raises no signal (not changed by this PR). In these cases a program that does not use `MSG_NOSIGNAL`: - with a `SIGPIPE` handler: the handler now runs, and the call still fails with `EPIPE`; - with `SIGPIPE` ignored: no change; - without a handler and with `CONFIG_SIG_SIGPIPE_ACTION=y`: the program now ends, as the POSIX default action says. No in-tree defconfig sets this option (default `n`); - without a handler and without `CONFIG_SIG_SIGPIPE_ACTION`: no change (the signal has no action and is discarded). In-tree programs that can get `EPIPE` on a send: `netutils/rexecd` writes to the command through `dpopen(O_RDWR)` (a local stream socket pair) and ends with `CONFIG_SIG_SIGPIPE_ACTION=y` when the command exits first; programs on a usrsock network (TCP over a modem or a host daemon) when the remote end closes. `netutils/libwebsockets` and `netutils/paho_mqtt` ignore `SIGPIPE`, `netutils/s2opc` uses `MSG_NOSIGNAL`. A program that must not end ignores `SIGPIPE` or uses `MSG_NOSIGNAL`. ## Testing Base: master 5c441693fa8. Test program `sigpipe.c` (each case: the errno and the number of `SIGPIPE` signals that a handler counts), on qemu-armv8a:knsh (kernel build) and qemu-armv8a:nsh (flat build, the test as a builtin app), QEMU with HVF, with `CONFIG_NET`, `CONFIG_NET_LOCAL`, `CONFIG_NET_LOCAL_STREAM`, `CONFIG_NET_TCP`, `CONFIG_NET_IPv4`, `CONFIG_NET_LOOPBACK`, `CONFIG_NETDEV_LATEINIT`, `CONFIG_NET_SOCKOPTS`, `CONFIG_SCHED_HPWORK`, `CONFIG_SIG_DEFAULT`, `CONFIG_SIG_SIGPIPE_ACTION`, `CONFIG_SCHED_WAITPID`, `CONFIG_SCHED_CHILD_STATUS`. The results are the same in both builds: | Case | master | this PR | |---|---|---| | local stream socket, `send()` after the peer closed | `EPIPE`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | `send(MSG_NOSIGNAL)` | `EPIPE`, no signal: PASS | `EPIPE`, no signal: PASS | | `sendto()` | `EPIPE`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | `sendmsg()` | `EPIPE`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | `sendmsg(MSG_NOSIGNAL)` | `EPIPE`, no signal: PASS | `EPIPE`, no signal: PASS | | `write()` | `EPIPE`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | `writev()` | `EPIPE`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | `send()` in a second thread: the handler runs in that thread | no signal: FAIL | PASS | | `SIGPIPE` blocked: pending after the send | not pending: FAIL | pending: PASS | | `SIGPIPE` ignored | `EPIPE`: PASS | `EPIPE`: PASS | | TCP over loopback, `send()` after `shutdown(SHUT_WR)` | `EPIPE`, no signal: FAIL | `EPIPE`, 1 signal: PASS | | the same with `MSG_NOSIGNAL` | `EPIPE`, no signal: PASS | `EPIPE`, no signal: PASS | | TCP over loopback, the peer closed | `ENOTCONN`, no signal | `ENOTCONN`, no signal | | a child without a handler (default action) | exits with status 0: FAIL | ends, wait status 0x100: PASS | ostest exits with status 0 on qemu-armv8a:knsh and qemu-armv8a:nsh with the options above, on master and with this PR. Build, each OK: qemu-armv8a:nsh (flat, arm64) without `CONFIG_NET`, with the network options and `CONFIG_SIG_DEFAULT=n`, with `CONFIG_ENABLE_PARTIAL_SIGNALS`; qemu-armv8a:knsh (kernel, arm64) with the network options and `CONFIG_SIG_DEFAULT=n`; rv-virt:nsh64 (flat, rv64), rv-virt:pnsh64 (protected, rv64) and rv-virt:knsh64 (kernel, rv64) with the network options, `CONFIG_SIG_DEFAULT` and `CONFIG_SIG_SIGPIPE_ACTION`. `tools/checkpatch.sh -c -u -m -g` passes. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
