From: Jeff Hostetler <jeffhost@xxxxxxxxxxxxx> Create a gentle version of `unix_stream_listen()`. This version does not call `die()` if a socket-fd cannot be created and does not assume that it is safe to `unlink()` an existing socket-inode. `unix_stream_listen()` uses `unix_stream_socket()` helper function to create the socket-fd. Avoid that helper because it calls `die()` on errors. `unix_stream_listen()` always tries to `unlink()` the socket-path before calling `bind()`. If there is an existing server/daemon already bound and listening on that socket-path, our `unlink()` would have the effect of disassociating the existing server's bound-socket-fd from the socket-path without notifying the existing server. The existing server could continue to service existing connections (accepted-socket-fd's), but would not receive any futher new connections (since clients rendezvous via the socket-path). The existing server would effectively be offline but yet appear to be active. Furthermore, `unix_stream_listen()` creates an opportunity for a brief race condition for connecting clients if they try to connect in the interval between the forced `unlink()` and the subsequent `bind()` (which recreates the socket-path that is bound to a new socket-fd in the current process). Signed-off-by: Jeff Hostetler <jeffhost@xxxxxxxxxxxxx> --- unix-socket.c | 39 +++++++++++++++++++++++++++++++++++++++ unix-socket.h | 8 ++++++++ 2 files changed, 47 insertions(+) diff --git a/unix-socket.c b/unix-socket.c index 19ed48be990..3a9ffc32268 100644 --- a/unix-socket.c +++ b/unix-socket.c @@ -121,3 +121,42 @@ int unix_stream_listen(const char *path) errno = saved_errno; return -1; } + +int unix_stream_listen_gently(const char *path, + const struct unix_stream_listen_opts *opts) +{ + int fd = -1; + int bind_successful = 0; + int saved_errno; + struct sockaddr_un sa; + struct unix_sockaddr_context ctx; + + if (unix_sockaddr_init(&sa, path, &ctx) < 0) + goto fail; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + goto fail; + + if (opts->force_unlink_before_bind) + unlink(path); + + if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) + goto fail; + bind_successful = 1; + + if (listen(fd, opts->listen_backlog_size) < 0) + goto fail; + + unix_sockaddr_cleanup(&ctx); + return fd; + +fail: + saved_errno = errno; + unix_sockaddr_cleanup(&ctx); + close(fd); + if (bind_successful) + unlink(path); + errno = saved_errno; + return -1; +} diff --git a/unix-socket.h b/unix-socket.h index e271aeec5a0..253f579f087 100644 --- a/unix-socket.h +++ b/unix-socket.h @@ -4,4 +4,12 @@ int unix_stream_connect(const char *path); int unix_stream_listen(const char *path); +struct unix_stream_listen_opts { + int listen_backlog_size; + unsigned int force_unlink_before_bind:1; +}; + +int unix_stream_listen_gently(const char *path, + const struct unix_stream_listen_opts *opts); + #endif /* UNIX_SOCKET_H */ -- gitgitgadget