From patchwork Mon Mar 15 21:08:27 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeff Hostetler X-Patchwork-Id: 12140697 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 12133C43331 for ; Mon, 15 Mar 2021 21:09:23 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E569764F50 for ; Mon, 15 Mar 2021 21:09:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233889AbhCOVIx (ORCPT ); Mon, 15 Mar 2021 17:08:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53376 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233071AbhCOVIi (ORCPT ); Mon, 15 Mar 2021 17:08:38 -0400 Received: from mail-wr1-x42a.google.com (mail-wr1-x42a.google.com [IPv6:2a00:1450:4864:20::42a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 61942C06174A for ; Mon, 15 Mar 2021 14:08:38 -0700 (PDT) Received: by mail-wr1-x42a.google.com with SMTP id y16so9408849wrw.3 for ; Mon, 15 Mar 2021 14:08:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=5Pdau/r0fcMGLYHt+z4JaTsxB8Kwqlni+GJpFcY8nBk=; b=fFA+5iU/evQ+WE0k87hGw4ph6zzq6p8q5ZKsxWaeD+ptHrBPxm+tWn3gFbTa7htBRq YSA86HM53OFoYZbMKaN5yKHOdNxPFqoXeYl3l9exPOmTO7HDSNWPmxIazqpsAbFyzDk+ szXsoIfk2K8E32+c0c87r0B+cEPmhXCAGSpJ7ysBjyl9dcL6ROgj6dpM7q45VvbsVLSe nRQD0J61ra/GJQa6kKbSsT3iMstU9w2PdYo4pDMRNn44c2FC1i0i2nSbeT1vAz4ZSOpi 9vXU/J2q9zOKRx+s0CsAGLCgB2IwZ5kaKgFaBssg7IleE5mS5f2b5VKc4IoOPbiH4PPE WUaA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=5Pdau/r0fcMGLYHt+z4JaTsxB8Kwqlni+GJpFcY8nBk=; b=b7IXDyWP6w+Qzd0uSQx1Q7fYV1o7YY9hOsRm9TOL1k6q8tj1ZB3I82fmEZpdNqjXdZ Ao8hyRjdMReM3LeWX//Phb4+1QYZgYO0xhi/bTdEPu979sSEIB4X12oSmmzdFAOWbOPG nW0emnFpu87pjvkL2+IpoR5K4fX4eDQxW0rEmRdvEsFaRZBVWlJHMIK5fKSyzOsj2a1n oe4uB9stuQ+/L2Ue7mvRZjkmk5Dg8ZLXhF/Hb2aYcXymjwLX6ZuHASppWfWS8JJSz6kB 1+2vfDBnxv/O0P7Cfm5yQN83CU0DgCe7a1bXFb0kZDKrgiQM/VawDG0Z7H0QCbrYFq4S HFPA== X-Gm-Message-State: AOAM533ZRgVPxoeup4nfHKrQ7NwOVQt0XfaERnsi7u3lh3ygTbQmTgA7 F9r1e3Q52HpUePwri5QZcwAHGvulrdk= X-Google-Smtp-Source: ABdhPJwyXT+jYrKmprs5AyeB6BvOCfC3cteZd+JTryEedHxcZSlbnH3oeRPX1l2asmkWBTpU48jsIw== X-Received: by 2002:adf:90f0:: with SMTP id i103mr1430154wri.318.1615842517120; Mon, 15 Mar 2021 14:08:37 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id d204sm804085wmc.17.2021.03.15.14.08.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 15 Mar 2021 14:08:36 -0700 (PDT) Message-Id: <02c885fd623df3551c46aa270c23f87e7ef79af2.1615842510.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Mon, 15 Mar 2021 21:08:27 +0000 Subject: [PATCH v6 10/12] unix-stream-server: create unix domain socket under lock Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsA==?= Bjarmason , Jeff Hostetler , Jeff King , SZEDER =?utf-8?b?R8OhYm9y?= , Johannes Schindelin , Chris Torek , Jeff Hostetler , Jeff Hostetler Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Jeff Hostetler From: Jeff Hostetler Create a wrapper class for `unix_stream_listen()` that uses a ".lock" lockfile to create the unix domain socket in a race-free manner. Unix domain sockets have a fundamental problem on Unix systems because they persist in the filesystem until they are deleted. This is independent of whether a server is actually listening for connections. Well-behaved servers are expected to delete the socket when they shutdown. A new server cannot easily tell if a found socket is attached to an active server or is leftover cruft from a dead server. The traditional solution used by `unix_stream_listen()` is to force delete the socket pathname and then create a new socket. This solves the latter (cruft) problem, but in the case of the former, it orphans the existing server (by stealing the pathname associated with the socket it is listening on). We cannot directly use a .lock lockfile to create the socket because the socket is created by `bind(2)` rather than the `open(2)` mechanism used by `tempfile.c`. As an alternative, we hold a plain lockfile (".lock") as a mutual exclusion device. Under the lock, we test if an existing socket ("") is has an active server. If not, we create a new socket and begin listening. Then we use "rollback" to delete the lockfile in all cases. This wrapper code conceptually exists at a higher-level than the core unix_stream_connect() and unix_stream_listen() routines that it consumes. It is isolated in a wrapper class for clarity. Signed-off-by: Jeff Hostetler --- Makefile | 1 + contrib/buildsystems/CMakeLists.txt | 2 +- unix-stream-server.c | 125 ++++++++++++++++++++++++++++ unix-stream-server.h | 33 ++++++++ 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 unix-stream-server.c create mode 100644 unix-stream-server.h diff --git a/Makefile b/Makefile index d3c42d3f4f9f..012694276f6d 100644 --- a/Makefile +++ b/Makefile @@ -1665,6 +1665,7 @@ ifdef NO_UNIX_SOCKETS BASIC_CFLAGS += -DNO_UNIX_SOCKETS else LIB_OBJS += unix-socket.o + LIB_OBJS += unix-stream-server.o endif ifdef USE_WIN32_IPC diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 40c9e8e3bd9d..c94011269ebb 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -243,7 +243,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows") elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_compile_definitions(PROCFS_EXECUTABLE_PATH="/proc/self/exe" HAVE_DEV_TTY ) - list(APPEND compat_SOURCES unix-socket.c) + list(APPEND compat_SOURCES unix-socket.c unix-stream-server.c) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") diff --git a/unix-stream-server.c b/unix-stream-server.c new file mode 100644 index 000000000000..efa2a207abcd --- /dev/null +++ b/unix-stream-server.c @@ -0,0 +1,125 @@ +#include "cache.h" +#include "lockfile.h" +#include "unix-socket.h" +#include "unix-stream-server.h" + +#define DEFAULT_LOCK_TIMEOUT (100) + +/* + * Try to connect to a unix domain socket at `path` (if it exists) and + * see if there is a server listening. + * + * We don't know if the socket exists, whether a server died and + * failed to cleanup, or whether we have a live server listening, so + * we "poke" it. + * + * We immediately hangup without sending/receiving any data because we + * don't know anything about the protocol spoken and don't want to + * block while writing/reading data. It is sufficient to just know + * that someone is listening. + */ +static int is_another_server_alive(const char *path, + const struct unix_stream_listen_opts *opts) +{ + int fd = unix_stream_connect(path, opts->disallow_chdir); + if (fd >= 0) { + close(fd); + return 1; + } + + return 0; +} + +int unix_ss_create(const char *path, + const struct unix_stream_listen_opts *opts, + long timeout_ms, + struct unix_ss_socket **new_server_socket) +{ + struct lock_file lock = LOCK_INIT; + int fd_socket; + struct unix_ss_socket *server_socket; + + *new_server_socket = NULL; + + if (timeout_ms < 0) + timeout_ms = DEFAULT_LOCK_TIMEOUT; + + /* + * Create a lock at ".lock" if we can. + */ + if (hold_lock_file_for_update_timeout(&lock, path, 0, timeout_ms) < 0) + return -1; + + /* + * If another server is listening on "" give up. We do not + * want to create a socket and steal future connections from them. + */ + if (is_another_server_alive(path, opts)) { + rollback_lock_file(&lock); + errno = EADDRINUSE; + return -2; + } + + /* + * Create and bind to a Unix domain socket at "". + */ + fd_socket = unix_stream_listen(path, opts); + if (fd_socket < 0) { + int saved_errno = errno; + rollback_lock_file(&lock); + errno = saved_errno; + return -1; + } + + server_socket = xcalloc(1, sizeof(*server_socket)); + server_socket->path_socket = strdup(path); + server_socket->fd_socket = fd_socket; + lstat(path, &server_socket->st_socket); + + *new_server_socket = server_socket; + + /* + * Always rollback (just delete) ".lock" because we already created + * "" as a socket and do not want to commit_lock to do the atomic + * rename trick. + */ + rollback_lock_file(&lock); + + return 0; +} + +void unix_ss_free(struct unix_ss_socket *server_socket) +{ + if (!server_socket) + return; + + if (server_socket->fd_socket >= 0) { + if (!unix_ss_was_stolen(server_socket)) + unlink(server_socket->path_socket); + close(server_socket->fd_socket); + } + + free(server_socket->path_socket); + free(server_socket); +} + +int unix_ss_was_stolen(struct unix_ss_socket *server_socket) +{ + struct stat st_now; + + if (!server_socket) + return 0; + + if (lstat(server_socket->path_socket, &st_now) == -1) + return 1; + + if (st_now.st_ino != server_socket->st_socket.st_ino) + return 1; + if (st_now.st_dev != server_socket->st_socket.st_dev) + return 1; + + if (!S_ISSOCK(st_now.st_mode)) + return 1; + + return 0; +} diff --git a/unix-stream-server.h b/unix-stream-server.h new file mode 100644 index 000000000000..ae2712ba39b1 --- /dev/null +++ b/unix-stream-server.h @@ -0,0 +1,33 @@ +#ifndef UNIX_STREAM_SERVER_H +#define UNIX_STREAM_SERVER_H + +#include "unix-socket.h" + +struct unix_ss_socket { + char *path_socket; + struct stat st_socket; + int fd_socket; +}; + +/* + * Create a Unix Domain Socket at the given path under the protection + * of a '.lock' lockfile. + * + * Returns 0 on success, -1 on error, -2 if socket is in use. + */ +int unix_ss_create(const char *path, + const struct unix_stream_listen_opts *opts, + long timeout_ms, + struct unix_ss_socket **server_socket); + +/* + * Close and delete the socket. + */ +void unix_ss_free(struct unix_ss_socket *server_socket); + +/* + * Return 1 if the inode of the pathname to our socket changes. + */ +int unix_ss_was_stolen(struct unix_ss_socket *server_socket); + +#endif /* UNIX_STREAM_SERVER_H */