{"thread":{"id":"54978","subject":"[PATCH 01/10] pkt-line: use stack rather than static buffer in packet_write_gently()","startedAt":"2021-01-12T15:32:33Z","lastAt":"2021-03-22T10:31:03Z","messageCount":178,"participants":["Jeff Hostetler via GitGitGadget","Johannes Schindelin via GitGitGadget","Ævar Arnfjörð Bjarmason","Jeff Hostetler","Junio C Hamano","Jeff King","Chris Torek","Junio C Hamano via GitGitGadget","SZEDER Gábor","Johannes Schindelin","Taylor Blau"],"isPatch":true,"patchVersion":1,"patchTotal":10},"messages":[{"id":"414155","messageId":"1155a45cf64afb237204429cd4ff2e74f5f7602a.1610465492.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH 01/10] pkt-line: use stack rather than static buffer in packet_write_gently()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:23Z","receivedAt":"2021-01-12T15:32:33Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nTeach packet_write_gently() to use a stack buffer rather than a static\nbuffer when composing the packet line message.  This helps get us ready\nfor threaded operations.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n pkt-line.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex d633005ef74..98439a2fed0 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -196,7 +196,7 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)\n \n static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n {\n-\tstatic char packet_write_buffer[LARGE_PACKET_MAX];\n+\tchar packet_write_buffer[LARGE_PACKET_MAX];\n \tsize_t packet_size;\n \n \tif (size > sizeof(packet_write_buffer) - 4)\n-- \ngitgitgadget\n\n"},{"id":"414156","messageId":"pull.766.git.1610465492.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":null,"subject":"[PATCH 00/10] [RFC] Simple IPC Mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:22Z","receivedAt":"2021-01-12T15:32:33Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"This series introduces a multi-threaded IPC mechanism called \"Simple IPC\".\nThis is a library-layer feature to make it easy to create very long running\ndaemon/service applications and for unrelated Git commands to communicate\nwith them. Communication uses pkt-line messaging over a Windows named pipe\nor Unix domain socket.\n\nOn the server side, Simple IPC implements a (platform-specific) connection\nlistener and worker thread-pool to accept and handle a series of client\nconnections. The server functionality is completely hidden behind the\nipc_server_run() and ipc_server_run_async() APIs. The daemon/service\napplication only needs to define an application-specific callback to handle\nclient requests.\n\nNote that Simple IPC is completely unrelated to the long running process\nfeature (described in sub-process.h) where the lifetime of a \"sub-process\"\nchild is bound to that of the invoking parent process and communication\noccurs over the child's stdin/stdout.\n\nSimple IPC will serve as a basis for a future builtin FSMonitor daemon\nfeature.\n\nJeff Hostetler (7):\n  pkt-line: use stack rather than static buffer in packet_write_gently()\n  simple-ipc: design documentation for new IPC mechanism\n  simple-ipc: add win32 implementation\n  unix-socket: create gentle version of unix_stream_listen()\n  unix-socket: add no-chdir option to unix_stream_listen_gently()\n  simple-ipc: add t/helper/test-simple-ipc and t0052\n  simple-ipc: add Unix domain socket implementation\n\nJohannes Schindelin (3):\n  pkt-line: (optionally) libify the packet readers\n  pkt-line: optionally skip the flush packet in\n    write_packetized_from_buf()\n  pkt-line: accept additional options in read_packetized_to_strbuf()\n\n Documentation/technical/api-simple-ipc.txt |   31 +\n Makefile                                   |    8 +\n compat/simple-ipc/ipc-shared.c             |   28 +\n compat/simple-ipc/ipc-unix-socket.c        | 1093 ++++++++++++++++++++\n compat/simple-ipc/ipc-win32.c              |  723 +++++++++++++\n config.mak.uname                           |    2 +\n contrib/buildsystems/CMakeLists.txt        |    6 +\n convert.c                                  |    4 +-\n pkt-line.c                                 |   30 +-\n pkt-line.h                                 |   13 +-\n simple-ipc.h                               |  221 ++++\n t/helper/test-simple-ipc.c                 |  485 +++++++++\n t/helper/test-tool.c                       |    1 +\n t/helper/test-tool.h                       |    1 +\n t/t0052-simple-ipc.sh                      |  129 +++\n unix-socket.c                              |   58 +-\n unix-socket.h                              |    9 +\n 17 files changed, 2828 insertions(+), 14 deletions(-)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\n\nbase-commit: 71ca53e8125e36efbda17293c50027d31681a41f\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/766\n-- \ngitgitgadget\n"},{"id":"414157","messageId":"2f399ac107c40f6fc71805d3dc4f2d602a33c074.1610465493.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH 04/10] pkt-line: accept additional options in read_packetized_to_strbuf()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:26Z","receivedAt":"2021-01-12T15:32:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `read_packetized_to_strbuf()` function reads packets into a strbuf\nuntil a flush packet has been received. So far, it has only one caller:\n`apply_multi_file_filter()` in `convert.c`. This caller really only\nneeds the `PACKET_READ_GENTLE_ON_EOF` option to be passed to\n`packet_read()` (which makes sense in the scenario where packets should\nbe read until a flush packet is received).\n\nWe are about to introduce a caller that wants to pass other options\nthrough to `packet_read()`, so let's extend the function signature\naccordingly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n convert.c  | 2 +-\n pkt-line.c | 4 ++--\n pkt-line.h | 6 +++++-\n 3 files changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 3f396a9b288..175c5cd51d5 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -903,7 +903,7 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \t\tif (err)\n \t\t\tgoto done;\n \n-\t\terr = read_packetized_to_strbuf(process->out, &nbuf) < 0;\n+\t\terr = read_packetized_to_strbuf(process->out, &nbuf, 0) < 0;\n \t\tif (err)\n \t\t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex ef83439b9ee..615211819cd 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -437,7 +437,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)\n \treturn packet_read_line_generic(-1, src, src_len, dst_len);\n }\n \n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options)\n {\n \tint packet_len;\n \n@@ -453,7 +453,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n \t\t\t * that there is already room for the extra byte.\n \t\t\t */\n \t\t\tsb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,\n-\t\t\tPACKET_READ_GENTLE_ON_EOF);\n+\t\t\toptions | PACKET_READ_GENTLE_ON_EOF);\n \t\tif (packet_len <= 0)\n \t\t\tbreak;\n \t\tsb_out->len += packet_len;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 5b7a0fb8510..02554a20a6c 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -135,8 +135,12 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);\n \n /*\n  * Reads a stream of variable sized packets until a flush packet is detected.\n+ *\n+ * The options are augmented by PACKET_READ_GENTLE_ON_EOF and passed to\n+ * packet_read.\n  */\n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out,\n+\t\t\t\t  int options);\n \n /*\n  * Receive multiplexed output stream over git native protocol.\n-- \ngitgitgadget\n\n"},{"id":"414158","messageId":"b7d678bc918addc99a1f5fe76ad33277aa476c33.1610465493.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH 02/10] pkt-line: (optionally) libify the packet readers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:24Z","receivedAt":"2021-01-12T15:32:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nSo far, the (possibly indirect) callers of `get_packet_data()` can ask\nthat function to return an error instead of `die()`ing upon end-of-file.\nHowever, random read errors will still cause the process to die.\n\nSo let's introduce an explicit option to tell the packet reader\nmachinery to please be nice and only return an error.\n\nThis change prepares pkt-line for use by long-running daemon processes.\nSuch processes should be able to serve multiple concurrent clients and\nand survive random IO errors.  If there is an error on one connection,\na daemon should be able to drop that connection and continue serving\nexisting and future connections.\n\nThis ability will be used by a Git-aware \"Internal FSMonitor\" feature\nin a later patch series.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n pkt-line.c | 19 +++++++++++++++++--\n pkt-line.h |  4 ++++\n 2 files changed, 21 insertions(+), 2 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex 98439a2fed0..5c2d86a2f60 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -298,8 +298,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\t*src_size -= ret;\n \t} else {\n \t\tret = read_in_full(fd, dst, size);\n-\t\tif (ret < 0)\n+\t\tif (ret < 0) {\n+\t\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\t\treturn error_errno(_(\"read error\"));\n \t\t\tdie_errno(_(\"read error\"));\n+\t\t}\n \t}\n \n \t/* And complain if we didn't get enough bytes to satisfy the read. */\n@@ -307,6 +310,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n \t\t\treturn -1;\n \n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"the remote end hung up unexpectedly\"));\n \t\tdie(_(\"the remote end hung up unexpectedly\"));\n \t}\n \n@@ -335,6 +340,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \tlen = packet_length(linelen);\n \n \tif (len < 0) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length \"\n+\t\t\t\t       \"character: %.4s\"), linelen);\n \t\tdie(_(\"protocol error: bad line length character: %.4s\"), linelen);\n \t} else if (!len) {\n \t\tpacket_trace(\"0000\", 4, 0);\n@@ -349,12 +357,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \t\t*pktlen = 0;\n \t\treturn PACKET_READ_RESPONSE_END;\n \t} else if (len < 4) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n \t}\n \n \tlen -= 4;\n-\tif ((unsigned)len >= size)\n+\tif ((unsigned)len >= size) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n+\t}\n \n \tif (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) {\n \t\t*pktlen = -1;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 8c90daa59ef..c1fa245faf8 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -68,10 +68,14 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n  *\n  * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n  * ERR packet.\n+ *\n+ * With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except\n+ * an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect).\n  */\n #define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n #define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n #define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n+#define PACKET_READ_NEVER_DIE         (1u<<3)\n int packet_read(int fd, char **src_buffer, size_t *src_len, char\n \t\t*buffer, unsigned size, int options);\n \n-- \ngitgitgadget\n\n"},{"id":"414159","messageId":"7064c5e9ffa0e3e666ea6d146b0839680952757d.1610465493.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH 05/10] simple-ipc: design documentation for new IPC mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:27Z","receivedAt":"2021-01-12T15:32:33Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nBrief design documentation for new IPC mechanism allowing\nforeground Git client to talk with an existing daemon process\nat a known location using a named pipe or unix domain socket.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-simple-ipc.txt | 31 ++++++++++++++++++++++\n 1 file changed, 31 insertions(+)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n\ndiff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt\nnew file mode 100644\nindex 00000000000..920994a69d3\n--- /dev/null\n+++ b/Documentation/technical/api-simple-ipc.txt\n@@ -0,0 +1,31 @@\n+simple-ipc API\n+==============\n+\n+The simple-ipc API is used to send an IPC message and response between\n+a (presumably) foreground Git client process to a background server or\n+daemon process.  The server process must already be running.  Multiple\n+client processes can simultaneously communicate with the server\n+process.\n+\n+Communication occurs over a named pipe on Windows and a Unix domain\n+socket on other platforms.  Clients and the server rendezvous at a\n+previously agreed-to application-specific pathname (which is outside\n+the scope of this design).\n+\n+This IPC mechanism differs from the existing `sub-process.c` model\n+(Documentation/technical/long-running-process-protocol.txt) and used\n+by applications like Git-LFS because the server is assumed to be very\n+long running system service.  In contrast, a \"sub-process model process\"\n+is started with the foreground process and exits when the foreground\n+process terminates.  How the server is started is also outside the\n+scope of the IPC mechanism.\n+\n+The IPC protocol consists of a single request message from the client and\n+an optional request message from the server.  For simplicity, pkt-line\n+routines are used to hide chunking and buffering concerns.  Each side\n+terminates their message with a flush packet.\n+(Documentation/technical/protocol-common.txt)\n+\n+The actual format of the client and server messages is application\n+specific.  The IPC layer transmits and receives an opaque buffer without\n+any concern for the content within.\n-- \ngitgitgadget\n\n"},{"id":"414160","messageId":"edf5ac95d662984b67d49bd452faf480c7c2da02.1610465493.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH 03/10] pkt-line: optionally skip the flush packet in write_packetized_from_buf()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:25Z","receivedAt":"2021-01-12T15:32:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis function currently has only one caller: `apply_multi_file_filter()`\nin `convert.c`. That caller wants a flush packet to be written after\nwriting the payload.\n\nHowever, we are about to introduce a user that wants to write many\npackets before a final flush packet, so let's extend this function to\nprepare for that scenario.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n convert.c  | 2 +-\n pkt-line.c | 5 +++--\n pkt-line.h | 3 ++-\n 3 files changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07c..3f396a9b288 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -886,7 +886,7 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \tif (fd >= 0)\n \t\terr = write_packetized_from_fd(fd, process->in);\n \telse\n-\t\terr = write_packetized_from_buf(src, len, process->in);\n+\t\terr = write_packetized_from_buf(src, len, process->in, 1);\n \tif (err)\n \t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 5c2d86a2f60..ef83439b9ee 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -261,7 +261,8 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n \treturn err;\n }\n \n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n+int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,\n+\t\t\t      int flush_at_end)\n {\n \tint err = 0;\n \tsize_t bytes_written = 0;\n@@ -277,7 +278,7 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n \t\tbytes_written += bytes_to_write;\n \t}\n-\tif (!err)\n+\tif (!err && flush_at_end)\n \t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\ndiff --git a/pkt-line.h b/pkt-line.h\nindex c1fa245faf8..5b7a0fb8510 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -33,7 +33,8 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n int packet_flush_gently(int fd);\n int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n int write_packetized_from_fd(int fd_in, int fd_out);\n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n+int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,\n+\t\t\t      int flush_at_end);\n \n /*\n  * Read a packetized line into the buffer, which must be at least size bytes\n-- \ngitgitgadget\n\n"},{"id":"414161","messageId":"a1b15fb5cb0f09fd77ff95331b37ec18f13a540c.1610465493.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH 10/10] simple-ipc: add Unix domain socket implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:32Z","receivedAt":"2021-01-12T15:32:35Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Unix domain socket based implementation of \"simple-ipc\".\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |    2 +\n compat/simple-ipc/ipc-unix-socket.c | 1093 +++++++++++++++++++++++++++\n contrib/buildsystems/CMakeLists.txt |    2 +\n simple-ipc.h                        |    7 +-\n 4 files changed, 1103 insertions(+), 1 deletion(-)\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n\ndiff --git a/Makefile b/Makefile\nindex e7ba8853ea6..f2524c02ff0 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1681,6 +1681,8 @@ ifdef NO_UNIX_SOCKETS\n \tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n else\n \tLIB_OBJS += unix-socket.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-unix-socket.o\n endif\n \n ifdef USE_WIN32_IPC\ndiff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c\nnew file mode 100644\nindex 00000000000..be100049e4b\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-unix-socket.c\n@@ -0,0 +1,1093 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+#include \"unix-socket.h\"\n+\n+#ifdef NO_UNIX_SOCKETS\n+#error compat/simple-ipc/ipc-unix-socket.c requires Unix sockets\n+#endif\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\tstruct stat st;\n+\tint fd_test = -1;\n+\n+\toptions.wait_if_busy = 0;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (lstat(path, &st) == -1) {\n+\t\tswitch (errno) {\n+\t\tcase ENOENT:\n+\t\tcase ENOTDIR:\n+\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__INVALID_PATH;\n+\t\t}\n+\t}\n+\n+\t/* also complain if a plain file is in the way */\n+\tif ((st.st_mode & S_IFMT) != S_IFSOCK)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\t/*\n+\t * Just because the filesystem has a S_IFSOCK type inode\n+\t * at `path`, doesn't mean it that there is a server listening.\n+\t * Ping it to be sure.\n+\t */\n+\tstate = ipc_client_try_connect(path, &options, &fd_test);\n+\tclose(fd_test);\n+\n+\treturn state;\n+}\n+\n+/*\n+ * This value was chosen at random.\n+ */\n+#define WAIT_STEP_MS (50)\n+\n+/*\n+ * Try to connect to the server.  If the server is just starting up or\n+ * is very busy, we may not get a connection the first time.\n+ */\n+static enum ipc_active_state connect_to_server(\n+\tconst char *path,\n+\tint timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tint wait_ms = 50;\n+\tint k;\n+\n+\t*pfd = -1;\n+\n+\tfor (k = 0; k < timeout_ms; k += wait_ms) {\n+\t\tint fd = unix_stream_connect(path);\n+\n+\t\tif (fd != -1) {\n+\t\t\t*pfd = fd;\n+\t\t\treturn IPC_STATE__LISTENING;\n+\t\t}\n+\n+\t\tif (errno == ENOENT) {\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ETIMEDOUT) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ECONNREFUSED) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\n+\tsleep_and_try_again:\n+\t\tsleep_millisec(wait_ms);\n+\t}\n+\n+\treturn IPC_STATE__NOT_LISTENING;\n+}\n+\n+/*\n+ * A randomly chosen timeout value.\n+ */\n+#define MY_CONNECTION_TIMEOUT_MS (1000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\n+\t*pfd = -1;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tstate = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t  options, pfd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\treturn state;\n+}\n+\n+int ipc_client_send_command_to_fd(int fd, const char *message,\n+\t\t\t\t  struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf(message, strlen(message), fd, 1) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tif (read_packetized_to_strbuf(fd, answer, PACKET_READ_NEVER_DIE) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer)\n+{\n+\tint fd;\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\n+\tstate = ipc_client_try_connect(path, options, &fd);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_fd(fd, message, answer);\n+\tclose(fd);\n+\treturn ret;\n+}\n+\n+static int set_socket_blocking_flag(int fd, int make_nonblocking)\n+{\n+\tint flags;\n+\n+\tflags = fcntl(fd, F_GETFL, NULL);\n+\n+\tif (flags < 0)\n+\t\treturn -1;\n+\n+\tif (make_nonblocking)\n+\t\tflags |= O_NONBLOCK;\n+\telse\n+\t\tflags &= ~O_NONBLOCK;\n+\n+\treturn fcntl(fd, F_SETFL, flags);\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_WORKER_THREAD_DATA,\n+\tMAGIC_ACCEPT_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_worker_thread_data *worker_thread_data;\n+};\n+\n+struct ipc_worker_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_worker_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+};\n+\n+struct ipc_accept_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_data *server_data;\n+\tint fd_listen;\n+\tino_t inode_listen;\n+\tint fd_send_shutdown;\n+\tint fd_wait_shutdown;\n+\tpthread_t pthread_id;\n+};\n+\n+/*\n+ * With unix-sockets, the conceptual \"ipc-server\" is implemented as a single\n+ * controller \"accept-thread\" thread and a pool of \"worker-thread\" threads.\n+ * The former does the usual `accept()` loop and dispatches connections\n+ * to an idle worker thread.  The worker threads wait in an idle loop for\n+ * a new connection, communicate with the client and relay data to/from\n+ * the `application_cb` and then wait for another connection from the\n+ * server thread.  This avoids the overhead of constantly creating and\n+ * destroying threads.\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\n+\tstruct ipc_accept_thread_data *accept_thread;\n+\tstruct ipc_worker_thread_data *worker_thread_list;\n+\n+\tpthread_mutex_t work_available_mutex;\n+\tpthread_cond_t work_available_cond;\n+\n+\t/*\n+\t * Accepted but not yet processed client connections are kept\n+\t * in a circular buffer FIFO.  The queue is empty when the\n+\t * positions are equal.\n+\t */\n+\tint *fifo_fds;\n+\tint queue_size;\n+\tint back_pos;\n+\tint front_pos;\n+\n+\tint shutdown_requested;\n+\tint is_stopped;\n+};\n+\n+/*\n+ * Remove and return the oldest queued connection.\n+ *\n+ * Returns -1 if empty.\n+ */\n+static int fifo_dequeue(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint fd;\n+\n+\tif (server_data->back_pos == server_data->front_pos)\n+\t\treturn -1;\n+\n+\tfd = server_data->fifo_fds[server_data->front_pos];\n+\tserver_data->fifo_fds[server_data->front_pos] = -1;\n+\n+\tserver_data->front_pos++;\n+\tif (server_data->front_pos == server_data->queue_size)\n+\t\tserver_data->front_pos = 0;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Push a new fd onto the back of the queue.\n+ *\n+ * Drop it and return -1 if queue is already full.\n+ */\n+static int fifo_enqueue(struct ipc_server_data *server_data, int fd)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint next_back_pos;\n+\n+\tnext_back_pos = server_data->back_pos + 1;\n+\tif (next_back_pos == server_data->queue_size)\n+\t\tnext_back_pos = 0;\n+\n+\tif (next_back_pos == server_data->front_pos) {\n+\t\t/* Queue is full. Just drop it. */\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data->fifo_fds[server_data->back_pos] = fd;\n+\tserver_data->back_pos = next_back_pos;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Wait for a connection to be queued to the FIFO and return it.\n+ *\n+ * Returns -1 if someone has already requested a shutdown.\n+ */\n+static int worker_thread__wait_for_connection(\n+\tstruct ipc_worker_thread_data *worker_thread_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tint fd = -1;\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\tfor (;;) {\n+\t\tif (server_data->shutdown_requested)\n+\t\t\tbreak;\n+\n+\t\tfd = fifo_dequeue(server_data);\n+\t\tif (fd >= 0)\n+\t\t\tbreak;\n+\n+\t\tpthread_cond_wait(&server_data->work_available_cond,\n+\t\t\t\t  &server_data->work_available_mutex);\n+\t}\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf(response, response_len,\n+\t\t\t\t\t reply_data->fd, 0);\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_WAIT_POLL_TIMEOUT_MS (10)\n+\n+/*\n+ * If the client hangs up without sending any data on the wire, just\n+ * quietly close the socket and ignore this client.\n+ *\n+ * This worker thread is committed to reading the IPC request data\n+ * from the client at the other end of this fd.  Wait here for the\n+ * client to actually put something on the wire -- because if the\n+ * client just does a ping (connect and hangup without sending any\n+ * data), our use of the pkt-line read routines will spew an error\n+ * message.\n+ *\n+ * Return -1 if the client hung up.\n+ * Return 0 if data (possibly incomplete) is ready.\n+ */\n+static int worker_thread__wait_for_io_start(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tstruct pollfd pollfd[1];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = fd;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\tint in_shutdown;\n+\n+\t\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\t\tin_shutdown = server_data->shutdown_requested;\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\t\t\t/*\n+\t\t\t * If a shutdown is already in progress and this\n+\t\t\t * client has not started talking yet, just drop it.\n+\t\t\t */\n+\t\t\tif (in_shutdown)\n+\t\t\t\tgoto cleanup;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLHUP)\n+\t\t\tgoto cleanup;\n+\n+\t\tif (pollfd[0].revents & POLLIN)\n+\t\t\treturn 0;\n+\n+\t\tgoto cleanup;\n+\t}\n+\n+cleanup:\n+\tclose(fd);\n+\treturn -1;\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ */\n+static int worker_thread__do_io(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\t/* ASSERT NOT holding lock */\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.worker_thread_data = worker_thread_data;\n+\n+\treply_data.fd = fd;\n+\n+\tret = read_packetized_to_strbuf(reply_data.fd, &buf,\n+\t\t\t\t\tPACKET_READ_NEVER_DIE);\n+\tif (ret >= 0) {\n+\t\tret = worker_thread_data->server_data->application_cb(\n+\t\t\tworker_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Block SIGPIPE on the current thread (so that we get EPIPE from\n+ * write() rather than an actual signal).\n+ *\n+ * Note that using sigchain_push() and _pop() to control SIGPIPE\n+ * around our IO calls is not thread safe:\n+ * [] It uses a global stack of handler frames.\n+ * [] It uses ALLOC_GROW() to resize it.\n+ * [] Finally, according to the `signal(2)` man-page:\n+ *    \"The effects of `signal()` in a multithreaded process are unspecified.\"\n+ */\n+static void thread_block_sigpipe(sigset_t *old_set)\n+{\n+\tsigset_t new_set;\n+\n+\tsigemptyset(&new_set);\n+\tsigaddset(&new_set, SIGPIPE);\n+\n+\tsigemptyset(old_set);\n+\tpthread_sigmask(SIG_BLOCK, &new_set, old_set);\n+}\n+\n+/*\n+ * Thread proc for an IPC worker thread.  It handles a series of\n+ * connections from clients.  It pulls the next fd from the queue\n+ * processes it, and then waits for the next client.\n+ *\n+ * Block SIGPIPE in this worker thread for the life of the thread.\n+ * This avoids stray (and sometimes delayed) SIGPIPE signals caused\n+ * by client errors and/or when we are under extremely heavy IO load.\n+ *\n+ * This means that the application callback will have SIGPIPE blocked.\n+ * The callback should not change it.\n+ */\n+static void *worker_thread_proc(void *_worker_thread_data)\n+{\n+\tstruct ipc_worker_thread_data *worker_thread_data = _worker_thread_data;\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tsigset_t old_set;\n+\tint fd, io;\n+\tint ret;\n+\n+\ttrace2_thread_start(\"ipc-worker\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tfd = worker_thread__wait_for_connection(worker_thread_data);\n+\t\tif (fd == -1)\n+\t\t\tbreak; /* in shutdown */\n+\n+\t\tio = worker_thread__wait_for_io_start(worker_thread_data, fd);\n+\t\tif (io == -1)\n+\t\t\tcontinue; /* client hung up without sending anything */\n+\n+\t\tret = worker_thread__do_io(worker_thread_data, fd);\n+\n+\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\ttrace2_data_string(\"ipc-worker\", NULL, \"queue_stop_async\",\n+\t\t\t\t\t   \"application_quit\");\n+\t\t\t/* The application told us to shutdown. */\n+\t\t\tipc_server_stop_async(server_data);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * Return 1 if someone deleted or stole the on-disk socket from us.\n+ */\n+static int socket_was_stolen(struct ipc_accept_thread_data *accept_thread_data)\n+{\n+\tstruct stat st;\n+\n+\tif (lstat(accept_thread_data->server_data->buf_path.buf, &st) == -1)\n+\t\treturn 1;\n+\n+\tif (st.st_ino != accept_thread_data->inode_listen)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)\n+\n+/*\n+ * Accept a new client connection on our socket.  This uses non-blocking\n+ * IO so that we can also wait for shutdown requests on our socket-pair\n+ * without actually spinning on a fast timeout.\n+ */\n+static int accept_thread__wait_for_connection(\n+\tstruct ipc_accept_thread_data *accept_thread_data)\n+{\n+\tstruct pollfd pollfd[2];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = accept_thread_data->fd_wait_shutdown;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tpollfd[1].fd = accept_thread_data->fd_listen;\n+\t\tpollfd[1].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\treturn result;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\t/*\n+\t\t\t * If someone deletes or force-creates a new unix\n+\t\t\t * domain socket at out path, all future clients\n+\t\t\t * will be routed elsewhere and we silently starve.\n+\t\t\t * If that happens, just queue a shutdown.\n+\t\t\t */\n+\t\t\tif (socket_was_stolen(\n+\t\t\t\t    accept_thread_data)) {\n+\t\t\t\ttrace2_data_string(\"ipc-accept\", NULL,\n+\t\t\t\t\t\t   \"queue_stop_async\",\n+\t\t\t\t\t\t   \"socket_stolen\");\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\taccept_thread_data->server_data);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLIN) {\n+\t\t\t/* shutdown message queued to socketpair */\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (pollfd[1].revents & POLLIN) {\n+\t\t\t/* a connection is available on fd_listen */\n+\n+\t\t\tint client_fd = accept(accept_thread_data->fd_listen,\n+\t\t\t\t\t       NULL, NULL);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\treturn client_fd;\n+\n+\t\t\t/*\n+\t\t\t * An error here is unlikely -- it probably\n+\t\t\t * indicates that the connecting process has\n+\t\t\t * already dropped the connection.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tBUG(\"unandled poll result errno=%d r[0]=%d r[1]=%d\",\n+\t\t    errno, pollfd[0].revents, pollfd[1].revents);\n+\t}\n+}\n+\n+/*\n+ * Thread proc for the IPC server \"accept thread\".  This waits for\n+ * an incoming socket connection, appends it to the queue of available\n+ * connections, and notifies a worker thread to process it.\n+ *\n+ * Block SIGPIPE in this thread for the life of the thread.  This\n+ * avoids any stray SIGPIPE signals when closing pipe fds under\n+ * extremely heavy loads (such as when the fifo queue is full and we\n+ * drop incomming connections).\n+ */\n+static void *accept_thread_proc(void *_accept_thread_data)\n+{\n+\tstruct ipc_accept_thread_data *accept_thread_data = _accept_thread_data;\n+\tstruct ipc_server_data *server_data = accept_thread_data->server_data;\n+\tsigset_t old_set;\n+\n+\ttrace2_thread_start(\"ipc-accept\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tint client_fd = accept_thread__wait_for_connection(\n+\t\t\taccept_thread_data);\n+\n+\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\tif (server_data->shutdown_requested) {\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\tclose(client_fd);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (client_fd < 0) {\n+\t\t\t/* ignore transient accept() errors */\n+\t\t}\n+\t\telse {\n+\t\t\tfifo_enqueue(server_data, client_fd);\n+\t\t\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\t\t}\n+\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * We can't predict the connection arrival rate relative to the worker\n+ * processing rate, therefore we allow the \"accept-thread\" to queue up\n+ * a generous number of connections, since we'd rather have the client\n+ * not unnecessarily timeout if we can avoid it.  (The assumption is\n+ * that this will be used for FSMonitor and a few second wait on a\n+ * connection is better than having the client timeout and do the full\n+ * computation itself.)\n+ *\n+ * The FIFO queue size is set to a multiple of the worker pool size.\n+ * This value chosen at random.\n+ */\n+#define FIFO_SCALE (100)\n+\n+/*\n+ * The backlog value for `listen(2)`.  This doesn't need to huge,\n+ * rather just large enough for our \"accept-thread\" to wake up and\n+ * queue incoming connections onto the FIFO without the kernel\n+ * dropping any.\n+ *\n+ * This value chosen at random.\n+ */\n+#define LISTEN_BACKLOG (50)\n+\n+/*\n+ * Create a unix domain socket at the given path to listen for\n+ * client connections.  The resulting socket will then appear\n+ * in the filesystem as an inode with S_IFSOCK.  The inode is\n+ * itself created as part of the `bind(2)` operation.\n+ *\n+ * The term \"socket\" is ambiguous in this context.  We want to open a\n+ * \"socket-fd\" that is bound to a \"socket-inode\" (path) on disk.  We\n+ * listen on \"socket-fd\" for new connections and clients try to\n+ * open/connect using the \"socket-inode\" pathname.\n+ *\n+ * Unix domain sockets have a fundamental design flaw because the\n+ * \"socket-inode\" persists until the pathname is deleted; closing the listening\n+ * \"socket-fd\" only closes the socket handle/descriptor, it does not delete\n+ * the inode/pathname.\n+ *\n+ * Well-behaving service daemons are expected to also delete the inode\n+ * before shutdown.  If a service crashes (or forgets) it can leave\n+ * the (now stale) inode in the filesystem.  This behaves like a stale\n+ * \".lock\" file and may prevent future service instances from starting\n+ * up correctly.  (Because they won't be able to bind.)\n+ *\n+ * When future service instances try to create the listener socket,\n+ * `bind(2)` will fail with EADDRINUSE -- because the inode already\n+ * exists.  However, the new instance cannot tell if it is a stale\n+ * inode *or* another service instance is already running.\n+ *\n+ * One possible solution is to blindly unlink the inode before\n+ * attempting to bind a new socket-fd (and thus create) a new\n+ * socket-inode.  Then `bind(2)` should always succeed.  However, if\n+ * there is an existing service instance, it would be orphaned --\n+ * it would still be listening on a socket-fd that is still bound\n+ * to an (unlinked) socket-inode, but that socket-inode is no longer\n+ * associated with the pathname.  New client connections will arrive\n+ * at our new socket-inode and not the existing server's.  (It is upto\n+ * the existing server to detect that its socket-inode has been\n+ * stolen and shutdown.)\n+ *\n+ * Since this is rather obscure and infrequent, we try to \"gently\"\n+ * create the socket-inode without disturbing an existing service.\n+ */\n+static int create_listener_socket(const char *path,\n+\t\t\t\t  const struct ipc_server_opts *ipc_opts)\n+{\n+\tint fd_listen;\n+\tint fd_client;\n+\tstruct unix_stream_listen_opts uslg_opts = {\n+\t\t.listen_backlog_size = LISTEN_BACKLOG,\n+\t\t.force_unlink_before_bind = 0,\n+\t\t.disallow_chdir = ipc_opts->uds_disallow_chdir\n+\t};\n+\n+\ttrace2_data_string(\"ipc-server\", NULL, \"try-listen-gently\", path);\n+\n+\t/*\n+\t * Assume socket-inode does not exist and try to (gently)\n+\t * create a new socket-inode on disk at pathname and bind\n+\t * socket-fd to it.\n+\t */\n+\tfd_listen = unix_stream_listen_gently(path, &uslg_opts);\n+\tif (fd_listen >= 0)\n+\t\treturn fd_listen;\n+\n+\tif (errno != EADDRINUSE)\n+\t\treturn error_errno(_(\"could not create socket '%s'\"),\n+\t\t\t\t   path);\n+\n+\ttrace2_data_string(\"ipc-server\", NULL, \"try-detect-server\", path);\n+\n+\t/*\n+\t * A socket-inode at pathname exists on disk, but we don't\n+\t * know if it a server is using it or if it is a stale inode.\n+\t *\n+\t * poke it with a trivial connection to try to find out.\n+\t */\n+\tfd_client = unix_stream_connect(path);\n+\tif (fd_client >= 0) {\n+\t\t/*\n+\t\t * An existing service process is alive and accepted our\n+\t\t * connection.\n+\t\t */\n+\t\tclose(fd_client);\n+\n+\t\t/*\n+\t\t * We cannot create a new socket-inode here, so we cannot\n+\t\t * startup a new server on this pathname.\n+\t\t */\n+\t\terrno = EADDRINUSE;\n+\t\treturn error_errno(_(\"socket already in use '%s'\"),\n+\t\t\t\t   path);\n+\t}\n+\n+\ttrace2_data_string(\"ipc-server\", NULL, \"try-listen-force\", path);\n+\n+\t/*\n+\t * A socket-inode at pathname exists on disk, but we were not\n+\t * able to connect to it, so we believe that this is a stale\n+\t * socket-inode that a previous server forgot to delete.  Use\n+\t * the tradional solution: force unlink it and create a new\n+\t * one.\n+\t *\n+\t * TODO Note that it is possible that another server is\n+\t * listening, but is either just starting up and not yet\n+\t * responsive or is stuck somehow.  For now, I'm OK with\n+\t * stealing the socket-inode from it in this case.\n+\t */\n+\tuslg_opts.force_unlink_before_bind = 1;\n+\tfd_listen = unix_stream_listen_gently(path, &uslg_opts);\n+\tif (fd_listen >= 0)\n+\t\treturn fd_listen;\n+\n+\treturn error_errno(_(\"could not force create socket '%s'\"), path);\n+}\n+\n+static int setup_listener_socket(const char *path, ino_t *inode,\n+\t\t\t\t const struct ipc_server_opts *ipc_opts)\n+{\n+\tint fd_listen;\n+\tstruct stat st;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n+\tfd_listen = create_listener_socket(path, ipc_opts);\n+\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n+\n+\tif (fd_listen < 0)\n+\t\treturn fd_listen;\n+\n+\t/*\n+\t * We just bound a socket (descriptor) to a newly created unix\n+\t * domain socket in the filesystem.  Capture the inode number\n+\t * so we can later detect if/when someone else force-creates a\n+\t * new socket and effectively steals the path from us.  (Which\n+\t * would leave us listening to a socket that no client could\n+\t * reach.)\n+\t */\n+\tif (lstat(path, &st) < 0) {\n+\t\tint saved_errno = errno;\n+\n+\t\tclose(fd_listen);\n+\t\tunlink(path);\n+\n+\t\terrno = saved_errno;\n+\t\treturn error_errno(_(\"could not lstat listener socket '%s'\"),\n+\t\t\t\t   path);\n+\t}\n+\n+\tif (set_socket_blocking_flag(fd_listen, 1)) {\n+\t\tint saved_errno = errno;\n+\n+\t\tclose(fd_listen);\n+\t\tunlink(path);\n+\n+\t\terrno = saved_errno;\n+\t\treturn error_errno(_(\"making listener socket nonblocking '%s'\"),\n+\t\t\t\t   path);\n+\t}\n+\n+\t*inode = st.st_ino;\n+\n+\treturn fd_listen;\n+}\n+\n+/*\n+ * Start IPC server in a pool of background threads.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct ipc_server_data *server_data;\n+\tint fd_listen;\n+\tino_t inode_listen;\n+\tint sv[2];\n+\tint k;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\t/*\n+\t * Create a socketpair and set sv[1] to non-blocking.  This\n+\t * will used to send a shutdown message to the accept-thread\n+\t * and allows the accept-thread to wait on EITHER a client\n+\t * connection or a shutdown request without spinning.\n+\t */\n+\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)\n+\t\treturn error_errno(_(\"could not create socketpair for '%s'\"),\n+\t\t\t\t   path);\n+\n+\tif (set_socket_blocking_flag(sv[1], 1)) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn error_errno(_(\"making socketpair nonblocking '%s'\"),\n+\t\t\t\t   path);\n+\t}\n+\n+\tfd_listen = setup_listener_socket(path, &inode_listen, opts);\n+\tif (fd_listen < 0) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tpthread_mutex_init(&server_data->work_available_mutex, NULL);\n+\tpthread_cond_init(&server_data->work_available_cond, NULL);\n+\n+\tserver_data->queue_size = nr_threads * FIFO_SCALE;\n+\tserver_data->fifo_fds = xcalloc(server_data->queue_size,\n+\t\t\t\t\tsizeof(*server_data->fifo_fds));\n+\n+\tserver_data->accept_thread =\n+\t\txcalloc(1, sizeof(*server_data->accept_thread));\n+\tserver_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;\n+\tserver_data->accept_thread->server_data = server_data;\n+\tserver_data->accept_thread->fd_listen = fd_listen;\n+\tserver_data->accept_thread->inode_listen = inode_listen;\n+\tserver_data->accept_thread->fd_send_shutdown = sv[0];\n+\tserver_data->accept_thread->fd_wait_shutdown = sv[1];\n+\n+\tif (pthread_create(&server_data->accept_thread->pthread_id, NULL,\n+\t\t\t   accept_thread_proc, server_data->accept_thread))\n+\t\tdie_errno(_(\"could not start accept_thread '%s'\"), path);\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_worker_thread_data *wtd;\n+\n+\t\twtd = xcalloc(1, sizeof(*wtd));\n+\t\twtd->magic = MAGIC_WORKER_THREAD_DATA;\n+\t\twtd->server_data = server_data;\n+\n+\t\tif (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc,\n+\t\t\t\t   wtd)) {\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start worker[0] for '%s'\"),\n+\t\t\t\t    path);\n+\t\t\t/*\n+\t\t\t * Limp along with the thread pool that we have.\n+\t\t\t */\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\twtd->next_thread = server_data->worker_thread_list;\n+\t\tserver_data->worker_thread_list = wtd;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+/*\n+ * Gently tell the IPC server treads to shutdown.\n+ * Can be run on any thread.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tint fd;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\n+\tserver_data->shutdown_requested = 1;\n+\n+\t/*\n+\t * Write a byte to the shutdown socket pair to wake up the\n+\t * accept-thread.\n+\t */\n+\tif (write(server_data->accept_thread->fd_send_shutdown, \"Q\", 1) < 0)\n+\t\terror_errno(\"could not write to fd_send_shutdown\");\n+\n+\t/*\n+\t * Drain the queue of existing connections.\n+\t */\n+\twhile ((fd = fifo_dequeue(server_data)) != -1)\n+\t\tclose(fd);\n+\n+\t/*\n+\t * Gently tell worker threads to stop processing new connections\n+\t * and exit.  (This does not abort in-process conversations.)\n+\t */\n+\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\ttrace2_region_leave(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Wait for all IPC server threads to stop.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tpthread_join(server_data->accept_thread->pthread_id, NULL);\n+\n+\tif (!server_data->shutdown_requested)\n+\t\tBUG(\"ipc-server: accept-thread stopped for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tpthread_join(wtd->pthread_id, NULL);\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tstruct ipc_accept_thread_data * accept_thread_data;\n+\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\taccept_thread_data = server_data->accept_thread;\n+\tif (accept_thread_data) {\n+\t\tif (accept_thread_data->fd_listen != -1) {\n+\t\t\t/*\n+\t\t\t * Only unlink the unix domain socket if we\n+\t\t\t * created it.  That is, if another daemon\n+\t\t\t * process force-created a new socket at this\n+\t\t\t * path, and effectively steals our path\n+\t\t\t * (which prevents us from receiving any\n+\t\t\t * future clients), we don't want to do the\n+\t\t\t * same thing to them.\n+\t\t\t */\n+\t\t\tif (!socket_was_stolen(\n+\t\t\t\t    accept_thread_data))\n+\t\t\t\tunlink(server_data->buf_path.buf);\n+\n+\t\t\tclose(accept_thread_data->fd_listen);\n+\t\t}\n+\t\tif (accept_thread_data->fd_send_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_send_shutdown);\n+\t\tif (accept_thread_data->fd_wait_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_wait_shutdown);\n+\n+\t\tfree(server_data->accept_thread);\n+\t}\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tpthread_cond_destroy(&server_data->work_available_cond);\n+\tpthread_mutex_destroy(&server_data->work_available_mutex);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tfree(server_data->fifo_fds);\n+\tfree(server_data);\n+}\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 4bd41054ee7..4c27a373414 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -248,6 +248,8 @@ endif()\n \n if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+else()\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c)\n endif()\n \n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\ndiff --git a/simple-ipc.h b/simple-ipc.h\nindex cd525e711bd..8a6dfc72c83 100644\n--- a/simple-ipc.h\n+++ b/simple-ipc.h\n@@ -5,7 +5,7 @@\n  * See Documentation/technical/api-simple-ipc.txt\n  */\n \n-#if defined(GIT_WINDOWS_NATIVE)\n+#if defined(GIT_WINDOWS_NATIVE) || !defined(NO_UNIX_SOCKETS)\n #define SUPPORTS_SIMPLE_IPC\n #endif\n \n@@ -151,6 +151,11 @@ struct ipc_server_data;\n struct ipc_server_opts\n {\n \tint nr_threads;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n /*\n-- \ngitgitgadget\n"},{"id":"414162","messageId":"383a9755669d4ed2d7dba348137dddd49ae2f3d5.1610465493.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH 08/10] unix-socket: add no-chdir option to unix_stream_listen_gently()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:30Z","receivedAt":"2021-01-12T15:33:13Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCalls to `chdir()` are dangerous in a multi-threaded context.  If\n`unix_stream_listen()` is given a socket pathname that is too big to\nfit in a `sockaddr_un` structure, it will `chdir()` to the parent\ndirectory of the requested socket pathname, create the socket using a\nrelative pathname, and then `chdir()` back.  This is not thread-safe.\n\nAdd `disallow_chdir` flag to `struct unix_sockaddr_context` and change\nall callers to pass an initialized context structure.\n\nTeach `unix_sockaddr_init()` to not allow calls to `chdir()` when flag\nis set.\n\nExtend the public interface to `unix_stream_listen_gently()` to also\nexpose this new flag.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 21 +++++++++++++++++----\n unix-socket.h |  1 +\n 2 files changed, 18 insertions(+), 4 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 3a9ffc32268..f66987261e6 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -19,8 +19,15 @@ static int chdir_len(const char *orig, int len)\n \n struct unix_sockaddr_context {\n \tchar *orig_dir;\n+\tunsigned int disallow_chdir:1;\n };\n \n+#define UNIX_SOCKADDR_CONTEXT_INIT \\\n+{ \\\n+\t.orig_dir=NULL, \\\n+\t.disallow_chdir=0, \\\n+}\n+\n static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n {\n \tif (!ctx->orig_dir)\n@@ -40,7 +47,11 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n {\n \tint size = strlen(path) + 1;\n \n-\tctx->orig_dir = NULL;\n+\tif (ctx->disallow_chdir && size > sizeof(sa->sun_path)) {\n+\t\terrno = ENAMETOOLONG;\n+\t\treturn -1;\n+\t}\n+\n \tif (size > sizeof(sa->sun_path)) {\n \t\tconst char *slash = find_last_dir_sep(path);\n \t\tconst char *dir;\n@@ -75,7 +86,7 @@ int unix_stream_connect(const char *path)\n {\n \tint fd, saved_errno;\n \tstruct sockaddr_un sa;\n-\tstruct unix_sockaddr_context ctx;\n+\tstruct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n@@ -97,7 +108,7 @@ int unix_stream_listen(const char *path)\n {\n \tint fd, saved_errno;\n \tstruct sockaddr_un sa;\n-\tstruct unix_sockaddr_context ctx;\n+\tstruct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;\n \n \tunlink(path);\n \n@@ -129,7 +140,9 @@ int unix_stream_listen_gently(const char *path,\n \tint bind_successful = 0;\n \tint saved_errno;\n \tstruct sockaddr_un sa;\n-\tstruct unix_sockaddr_context ctx;\n+\tstruct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;\n+\n+\tctx.disallow_chdir = opts->disallow_chdir;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\tgoto fail;\ndiff --git a/unix-socket.h b/unix-socket.h\nindex 253f579f087..08d3d822111 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -7,6 +7,7 @@ int unix_stream_listen(const char *path);\n struct unix_stream_listen_opts {\n \tint listen_backlog_size;\n \tunsigned int force_unlink_before_bind:1;\n+\tunsigned int disallow_chdir:1;\n };\n \n int unix_stream_listen_gently(const char *path,\n-- \ngitgitgadget\n\n"},{"id":"414164","messageId":"69969c2b8d37d11b5e5f886d700d4b6224df9894.1610465493.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH 09/10] simple-ipc: add t/helper/test-simple-ipc and t0052","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:31Z","receivedAt":"2021-01-12T15:33:13Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate unit tests for \"simple-ipc\".  These are currently only enabled\non Windows.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                   |   1 +\n t/helper/test-simple-ipc.c | 485 +++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0052-simple-ipc.sh      | 129 ++++++++++\n 5 files changed, 617 insertions(+)\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\ndiff --git a/Makefile b/Makefile\nindex c94d5847919..e7ba8853ea6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -740,6 +740,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o\n TEST_BUILTINS_OBJS += test-sha1.o\n TEST_BUILTINS_OBJS += test-sha256.o\n TEST_BUILTINS_OBJS += test-sigchain.o\n+TEST_BUILTINS_OBJS += test-simple-ipc.o\n TEST_BUILTINS_OBJS += test-strcmp-offset.o\n TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\ndiff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c\nnew file mode 100644\nindex 00000000000..4960e79cf18\n--- /dev/null\n+++ b/t/helper/test-simple-ipc.c\n@@ -0,0 +1,485 @@\n+/*\n+ * test-simple-ipc.c: verify that the Inter-Process Communication works.\n+ */\n+\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"simple-ipc.h\"\n+#include \"parse-options.h\"\n+#include \"thread-utils.h\"\n+\n+#ifndef SUPPORTS_SIMPLE_IPC\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tdie(\"simple IPC not available on this platform\");\n+}\n+#else\n+\n+/*\n+ * The test daemon defines an \"application callback\" that supports a\n+ * series of commands (see `test_app_cb()`).\n+ *\n+ * Unknown commands are caught here and we send an error message back\n+ * to the client process.\n+ */\n+static int app__unhandled_command(const char *command,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n+\n+\tstrbuf_addf(&buf, \"unhandled command: %s\", command);\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a single very large buffer.  This is to ensure that\n+ * long response are properly handled -- whether the chunking occurs\n+ * in the kernel or in the (probably pkt-line) layer.\n+ */\n+#define BIG_ROWS (10000)\n+static int app__big_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t    struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < BIG_ROWS; row++)\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a series of lines.  This is to ensure that we can incrementally\n+ * compute the response and chunk it to the client.\n+ */\n+#define CHUNK_ROWS (10000)\n+static int app__chunk_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t      struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < CHUNK_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Slowly reply with a series of lines.  This is to model an expensive to\n+ * compute chunked response (which might happen if this callback is running\n+ * in a thread and is fighting for a lock with other threads).\n+ */\n+#define SLOW_ROWS     (1000)\n+#define SLOW_DELAY_MS (10)\n+static int app__slow_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t     struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < SLOW_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t\tsleep_millisec(SLOW_DELAY_MS);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * The client sent a command followed by a (possibly very) large buffer.\n+ */\n+static int app__sendbytes_command(const char *received,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\tconst char *p = \"?\";\n+\tint len_ballast = 0;\n+\tint k;\n+\tint errs = 0;\n+\tint ret;\n+\n+\tif (skip_prefix(received, \"sendbytes \", &p))\n+\t\tlen_ballast = strlen(p);\n+\n+\t/*\n+\t * Verify that the ballast is n copies of a single letter.\n+\t * And that the multi-threaded IO layer didn't cross the streams.\n+\t */\n+\tfor (k = 1; k < len_ballast; k++)\n+\t\tif (p[k] != p[0])\n+\t\t\terrs++;\n+\n+\tif (errs)\n+\t\tstrbuf_addf(&buf_resp, \"errs:%d\\n\", errs);\n+\telse\n+\t\tstrbuf_addf(&buf_resp, \"rcvd:%c%08d\\n\", p[0], len_ballast);\n+\n+\tret = reply_cb(reply_data, buf_resp.buf, buf_resp.len);\n+\n+\tstrbuf_release(&buf_resp);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * An arbitrary fixed address to verify that the application instance\n+ * data is handled properly.\n+ */\n+static int my_app_data = 42;\n+\n+static ipc_server_application_cb test_app_cb;\n+\n+/*\n+ * This is \"application callback\" that sits on top of the \"ipc-server\".\n+ * It completely defines the set of command verbs supported by this\n+ * application.\n+ */\n+static int test_app_cb(void *application_data,\n+\t\t       const char *command,\n+\t\t       ipc_server_reply_cb *reply_cb,\n+\t\t       struct ipc_server_reply_data *reply_data)\n+{\n+\t/*\n+\t * Verify that we received the application-data that we passed\n+\t * when we started the ipc-server.  (We have several layers of\n+\t * callbacks calling callbacks and it's easy to get things mixed\n+\t * up (especially when some are \"void*\").)\n+\t */\n+\tif (application_data != (void*)&my_app_data)\n+\t\tBUG(\"application_cb: application_data pointer wrong\");\n+\n+\tif (!strcmp(command, \"quit\")) {\n+\t\t/*\n+\t\t * Tell ipc-server to hangup with an empty reply.\n+\t\t */\n+\t\treturn SIMPLE_IPC_QUIT;\n+\t}\n+\n+\tif (!strcmp(command, \"ping\")) {\n+\t\tconst char *answer = \"pong\";\n+\t\treturn reply_cb(reply_data, answer, strlen(answer));\n+\t}\n+\n+\tif (!strcmp(command, \"big\"))\n+\t\treturn app__big_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"chunk\"))\n+\t\treturn app__chunk_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"slow\"))\n+\t\treturn app__slow_command(reply_cb, reply_data);\n+\n+\tif (starts_with(command, \"sendbytes \"))\n+\t\treturn app__sendbytes_command(command, reply_cb, reply_data);\n+\n+\treturn app__unhandled_command(command, reply_cb, reply_data);\n+}\n+\n+/*\n+ * This process will run as a simple-ipc server and listen for IPC commands\n+ * from client processes.\n+ */\n+static int daemon__run_server(const char *path, int argc, const char **argv)\n+{\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = 5\n+\t};\n+\n+\tconst char * const daemon_usage[] = {\n+\t\tN_(\"test-helper simple-ipc daemon [<options>\"),\n+\t\tNULL\n+\t};\n+\tstruct option daemon_options[] = {\n+\t\tOPT_INTEGER(0, \"threads\", &opts.nr_threads,\n+\t\t\t    N_(\"number of threads in server thread pool\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);\n+\n+\tif (opts.nr_threads < 1)\n+\t\topts.nr_threads = 1;\n+\n+\t/*\n+\t * Synchronously run the ipc-server.  We don't need any application\n+\t * instance data, so pass an arbitrary pointer (that we'll later\n+\t * verify made the round trip).\n+\t */\n+\treturn ipc_server_run(path, &opts, test_app_cb, (void*)&my_app_data);\n+}\n+\n+/*\n+ * This process will run a quick probe to see if a simple-ipc server\n+ * is active on this path.\n+ *\n+ * Returns 0 if the server is alive.\n+ */\n+static int client__probe_server(const char *path)\n+{\n+\tenum ipc_active_state s;\n+\n+\ts = ipc_get_active_state(path);\n+\tswitch (s) {\n+\tcase IPC_STATE__LISTENING:\n+\t\treturn 0;\n+\n+\tcase IPC_STATE__NOT_LISTENING:\n+\t\treturn error(\"no server listening at '%s'\", path);\n+\n+\tcase IPC_STATE__PATH_NOT_FOUND:\n+\t\treturn error(\"path not found '%s'\", path);\n+\n+\tcase IPC_STATE__INVALID_PATH:\n+\t\treturn error(\"invalid pipe/socket name '%s'\", path);\n+\n+\tcase IPC_STATE__OTHER_ERROR:\n+\tdefault:\n+\t\treturn error(\"other error for '%s'\", path);\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command to an already-running server daemon and print the\n+ * response.\n+ *\n+ * argv[2] contains a simple (1 word) command verb that `test_app_cb()`\n+ * (in the daemon process) will understand.\n+ */\n+static int client__send_ipc(int argc, const char **argv, const char *path)\n+{\n+\tconst char *command = argc > 2 ? argv[2] : \"(no command)\";\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (!ipc_client_send_command(path, &options, command, &buf)) {\n+\t\tprintf(\"%s\\n\", buf.buf);\n+\t\tfflush(stdout);\n+\t\tstrbuf_release(&buf);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"failed to send '%s' to '%s'\", command, path);\n+}\n+\n+/*\n+ * Send an IPC command followed by ballast to confirm that a large\n+ * message can be sent and that the kernel or pkt-line layers will\n+ * properly chunk it and that the daemon receives the entire message.\n+ */\n+static int do_sendbytes(int bytecount, char byte, const char *path)\n+{\n+\tstruct strbuf buf_send = STRBUF_INIT;\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\n+\tstrbuf_addstr(&buf_send, \"sendbytes \");\n+\tstrbuf_addchars(&buf_send, byte, bytecount);\n+\n+\tif (!ipc_client_send_command(path, &options, buf_send.buf, &buf_resp)) {\n+\t\tstrbuf_rtrim(&buf_resp);\n+\t\tprintf(\"sent:%c%08d %s\\n\", byte, bytecount, buf_resp.buf);\n+\t\tfflush(stdout);\n+\t\tstrbuf_release(&buf_send);\n+\t\tstrbuf_release(&buf_resp);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"client failed to sendbytes(%d, '%c') to '%s'\",\n+\t\t     bytecount, byte, path);\n+}\n+\n+/*\n+ * Send an IPC command with ballast to an already-running server daemon.\n+ */\n+static int client__sendbytes(int argc, const char **argv, const char *path)\n+{\n+\tint bytecount = 1024;\n+\tchar *string = \"x\";\n+\tconst char * const sendbytes_usage[] = {\n+\t\tN_(\"test-helper simple-ipc sendbytes [<options>]\"),\n+\t\tNULL\n+\t};\n+\tstruct option sendbytes_options[] = {\n+\t\tOPT_INTEGER(0, \"bytecount\", &bytecount, N_(\"number of bytes\")),\n+\t\tOPT_STRING(0, \"byte\", &string, N_(\"byte\"), N_(\"ballast\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, sendbytes_options, sendbytes_usage, 0);\n+\n+\treturn do_sendbytes(bytecount, string[0], path);\n+}\n+\n+struct multiple_thread_data {\n+\tpthread_t pthread_id;\n+\tstruct multiple_thread_data *next;\n+\tconst char *path;\n+\tint bytecount;\n+\tint batchsize;\n+\tint sum_errors;\n+\tint sum_good;\n+\tchar letter;\n+};\n+\n+static void *multiple_thread_proc(void *_multiple_thread_data)\n+{\n+\tstruct multiple_thread_data *d = _multiple_thread_data;\n+\tint k;\n+\n+\ttrace2_thread_start(\"multiple\");\n+\n+\tfor (k = 0; k < d->batchsize; k++) {\n+\t\tif (do_sendbytes(d->bytecount + k, d->letter, d->path))\n+\t\t\td->sum_errors++;\n+\t\telse\n+\t\t\td->sum_good++;\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * Start a client-side thread pool.  Each thread sends a series of\n+ * IPC requests.  Each request is on a new connection to the server.\n+ */\n+static int client__multiple(int argc, const char **argv, const char *path)\n+{\n+\tstruct multiple_thread_data *list = NULL;\n+\tint k;\n+\tint nr_threads = 5;\n+\tint bytecount = 1;\n+\tint batchsize = 10;\n+\tint sum_join_errors = 0;\n+\tint sum_thread_errors = 0;\n+\tint sum_good = 0;\n+\n+\tconst char * const multiple_usage[] = {\n+\t\tN_(\"test-helper simple-ipc multiple [<options>]\"),\n+\t\tNULL\n+\t};\n+\tstruct option multiple_options[] = {\n+\t\tOPT_INTEGER(0, \"bytecount\", &bytecount, N_(\"number of bytes\")),\n+\t\tOPT_INTEGER(0, \"threads\", &nr_threads, N_(\"number of threads\")),\n+\t\tOPT_INTEGER(0, \"batchsize\", &batchsize, N_(\"number of requests per thread\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, multiple_options, multiple_usage, 0);\n+\n+\tif (bytecount < 1)\n+\t\tbytecount = 1;\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\tif (batchsize < 1)\n+\t\tbatchsize = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct multiple_thread_data *d = xcalloc(1, sizeof(*d));\n+\t\td->next = list;\n+\t\td->path = path;\n+\t\td->bytecount = bytecount + batchsize*(k/26);\n+\t\td->batchsize = batchsize;\n+\t\td->sum_errors = 0;\n+\t\td->sum_good = 0;\n+\t\td->letter = 'A' + (k % 26);\n+\n+\t\tif (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) {\n+\t\t\twarning(\"failed to create thread[%d] skipping remainder\", k);\n+\t\t\tfree(d);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tlist = d;\n+\t}\n+\n+\twhile (list) {\n+\t\tstruct multiple_thread_data *d = list;\n+\n+\t\tif (pthread_join(d->pthread_id, NULL))\n+\t\t\tsum_join_errors++;\n+\n+\t\tsum_thread_errors += d->sum_errors;\n+\t\tsum_good += d->sum_good;\n+\n+\t\tlist = d->next;\n+\t\tfree(d);\n+\t}\n+\n+\tprintf(\"client (good %d) (join %d), (errors %d)\\n\",\n+\t       sum_good, sum_join_errors, sum_thread_errors);\n+\n+\treturn (sum_join_errors + sum_thread_errors) ? 1 : 0;\n+}\n+\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tconst char *path = \"ipc-test\";\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"SUPPORTS_SIMPLE_IPC\"))\n+\t\treturn 0;\n+\n+\t/* Use '!!' on all dispatch functions to map from `error()` style\n+\t * (returns -1) style to `test_must_fail` style (expects 1) and\n+\t * get less confusing shell error messages.\n+\t */\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"is-active\"))\n+\t\treturn !!client__probe_server(path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"daemon\"))\n+\t\treturn !!daemon__run_server(path, argc, argv);\n+\n+\t/*\n+\t * Client commands follow.  Ensure a server is running before\n+\t * going any further.\n+\t */\n+\tif (client__probe_server(path))\n+\t\treturn 1;\n+\n+\tif ((argc == 2 || argc == 3) && !strcmp(argv[1], \"send\"))\n+\t\treturn !!client__send_ipc(argc, argv, path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"sendbytes\"))\n+\t\treturn !!client__sendbytes(argc, argv, path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"multiple\"))\n+\t\treturn !!client__multiple(argc, argv, path);\n+\n+\tdie(\"Unhandled argv[1]: '%s'\", argv[1]);\n+}\n+#endif\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 9d6d14d9293..a409655f03b 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -64,6 +64,7 @@ static struct test_cmd cmds[] = {\n \t{ \"sha1\", cmd__sha1 },\n \t{ \"sha256\", cmd__sha256 },\n \t{ \"sigchain\", cmd__sigchain },\n+\t{ \"simple-ipc\", cmd__simple_ipc },\n \t{ \"strcmp-offset\", cmd__strcmp_offset },\n \t{ \"string-list\", cmd__string_list },\n \t{ \"submodule-config\", cmd__submodule_config },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex a6470ff62c4..564eb3c8e91 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -54,6 +54,7 @@ int cmd__sha1(int argc, const char **argv);\n int cmd__oid_array(int argc, const char **argv);\n int cmd__sha256(int argc, const char **argv);\n int cmd__sigchain(int argc, const char **argv);\n+int cmd__simple_ipc(int argc, const char **argv);\n int cmd__strcmp_offset(int argc, const char **argv);\n int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\ndiff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\nnew file mode 100755\nindex 00000000000..69588354545\n--- /dev/null\n+++ b/t/t0052-simple-ipc.sh\n@@ -0,0 +1,129 @@\n+#!/bin/sh\n+\n+test_description='simple command server'\n+\n+. ./test-lib.sh\n+\n+test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {\n+\tskip_all='simple IPC not supported on this platform'\n+\ttest_done\n+}\n+\n+stop_simple_IPC_server () {\n+\ttest -n \"$SIMPLE_IPC_PID\" || return 0\n+\n+\tkill \"$SIMPLE_IPC_PID\" &&\n+\tSIMPLE_IPC_PID=\n+}\n+\n+test_expect_success 'start simple command server' '\n+\t{ test-tool simple-ipc daemon --threads=8 & } &&\n+\tSIMPLE_IPC_PID=$! &&\n+\ttest_atexit stop_simple_IPC_server &&\n+\n+\tsleep 1 &&\n+\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'simple command server' '\n+\ttest-tool simple-ipc send ping >actual &&\n+\techo pong >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'servers cannot share the same path' '\n+\ttest_must_fail test-tool simple-ipc daemon &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'big response' '\n+\ttest-tool simple-ipc send big >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'chunk response' '\n+\ttest-tool simple-ipc send chunk >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'slow response' '\n+\ttest-tool simple-ipc send slow >actual &&\n+\ttest_line_count -ge 100 actual &&\n+\tgrep -q \"big: [0]*99\\$\" actual\n+'\n+\n+# Send an IPC with n=100,000 bytes of ballast.  This should be large enough\n+# to force both the kernel and the pkt-line layer to chunk the message to the\n+# daemon and for the daemon to receive it in chunks.\n+#\n+test_expect_success 'sendbytes' '\n+\ttest-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual &&\n+\tgrep \"sent:A00100000 rcvd:A00100000\" actual\n+'\n+\n+# Start a series of <threads> client threads that each make <batchsize>\n+# IPC requests to the server.  Each (<threads> * <batchsize>) request\n+# will open a new connection to the server and randomly bind to a server\n+# thread.  Each client thread exits after completing its batch.  So the\n+# total number of live client threads will be smaller than the total.\n+# Each request will send a message containing at least <bytecount> bytes\n+# of ballast.  (Responses are small.)\n+#\n+# The purpose here is to test threading in the server and responding to\n+# many concurrent client requests (regardless of whether they come from\n+# 1 client process or many).  And to test that the server side of the\n+# named pipe/socket is stable.  (On Windows this means that the server\n+# pipe is properly recycled.)\n+#\n+# On Windows it also lets us adjust the connection timeout in the\n+# `ipc_client_send_command()`.\n+#\n+# Note it is easy to drive the system into failure by requesting an\n+# insane number of threads on client or server and/or increasing the\n+# per-thread batchsize or the per-request bytecount (ballast).\n+# On Windows these failures look like \"pipe is busy\" errors.\n+# So I've chosen fairly conservative values for now.\n+#\n+# We expect output of the form \"sent:<letter><length> ...\"\n+# With terms (7, 19, 13) we expect:\n+#   <letter> in [A-G]\n+#   <length> in [19+0 .. 19+(13-1)]\n+# and (7 * 13) successful responses.\n+#\n+test_expect_success 'stress test threads' '\n+\ttest-tool simple-ipc multiple \\\n+\t\t--threads=7 \\\n+\t\t--bytecount=19 \\\n+\t\t--batchsize=13 \\\n+\t\t>actual &&\n+\ttest_line_count = 92 actual &&\n+\tgrep \"good 91\" actual &&\n+\tgrep \"sent:A\" <actual >actual_a &&\n+\tcat >expect_a <<-EOF &&\n+\t\tsent:A00000019 rcvd:A00000019\n+\t\tsent:A00000020 rcvd:A00000020\n+\t\tsent:A00000021 rcvd:A00000021\n+\t\tsent:A00000022 rcvd:A00000022\n+\t\tsent:A00000023 rcvd:A00000023\n+\t\tsent:A00000024 rcvd:A00000024\n+\t\tsent:A00000025 rcvd:A00000025\n+\t\tsent:A00000026 rcvd:A00000026\n+\t\tsent:A00000027 rcvd:A00000027\n+\t\tsent:A00000028 rcvd:A00000028\n+\t\tsent:A00000029 rcvd:A00000029\n+\t\tsent:A00000030 rcvd:A00000030\n+\t\tsent:A00000031 rcvd:A00000031\n+\tEOF\n+\ttest_cmp expect_a actual_a\n+'\n+\n+test_expect_success '`quit` works' '\n+\ttest-tool simple-ipc send quit &&\n+\ttest_must_fail test-tool simple-ipc is-active &&\n+\ttest_must_fail test-tool simple-ipc send ping\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"414163","messageId":"96268351ac66371a0998d189db619f357d2b71fa.1610465493.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH 07/10] unix-socket: create gentle version of unix_stream_listen()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:29Z","receivedAt":"2021-01-12T15:33:14Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a gentle version of `unix_stream_listen()`.  This version does\nnot call `die()` if a socket-fd cannot be created and does not assume\nthat it is safe to `unlink()` an existing socket-inode.\n\n`unix_stream_listen()` uses `unix_stream_socket()` helper function to\ncreate the socket-fd.  Avoid that helper because it calls `die()` on\nerrors.\n\n`unix_stream_listen()` always tries to `unlink()` the socket-path before\ncalling `bind()`.  If there is an existing server/daemon already bound\nand listening on that socket-path, our `unlink()` would have the effect\nof disassociating the existing server's bound-socket-fd from the socket-path\nwithout notifying the existing server.  The existing server could continue\nto service existing connections (accepted-socket-fd's), but would not\nreceive any futher new connections (since clients rendezvous via the\nsocket-path).  The existing server would effectively be offline but yet\nappear to be active.\n\nFurthermore, `unix_stream_listen()` creates an opportunity for a brief\nrace condition for connecting clients if they try to connect in the\ninterval between the forced `unlink()` and the subsequent `bind()` (which\nrecreates the socket-path that is bound to a new socket-fd in the current\nprocess).\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 39 +++++++++++++++++++++++++++++++++++++++\n unix-socket.h |  8 ++++++++\n 2 files changed, 47 insertions(+)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 19ed48be990..3a9ffc32268 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -121,3 +121,42 @@ int unix_stream_listen(const char *path)\n \terrno = saved_errno;\n \treturn -1;\n }\n+\n+int unix_stream_listen_gently(const char *path,\n+\t\t\t      const struct unix_stream_listen_opts *opts)\n+{\n+\tint fd = -1;\n+\tint bind_successful = 0;\n+\tint saved_errno;\n+\tstruct sockaddr_un sa;\n+\tstruct unix_sockaddr_context ctx;\n+\n+\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\t\tgoto fail;\n+\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n+\n+\tif (opts->force_unlink_before_bind)\n+\t\tunlink(path);\n+\n+\tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n+\t\tgoto fail;\n+\tbind_successful = 1;\n+\n+\tif (listen(fd, opts->listen_backlog_size) < 0)\n+\t\tgoto fail;\n+\n+\tunix_sockaddr_cleanup(&ctx);\n+\treturn fd;\n+\n+fail:\n+\tsaved_errno = errno;\n+\tunix_sockaddr_cleanup(&ctx);\n+\tclose(fd);\n+\tif (bind_successful)\n+\t\tunlink(path);\n+\terrno = saved_errno;\n+\treturn -1;\n+}\ndiff --git a/unix-socket.h b/unix-socket.h\nindex e271aeec5a0..253f579f087 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -4,4 +4,12 @@\n int unix_stream_connect(const char *path);\n int unix_stream_listen(const char *path);\n \n+struct unix_stream_listen_opts {\n+\tint listen_backlog_size;\n+\tunsigned int force_unlink_before_bind:1;\n+};\n+\n+int unix_stream_listen_gently(const char *path,\n+\t\t\t      const struct unix_stream_listen_opts *opts);\n+\n #endif /* UNIX_SOCKET_H */\n-- \ngitgitgadget\n\n"},{"id":"414165","messageId":"9e27c07d7852c1476f5a2ca0bb0fe0c1f05e2de1.1610465493.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH 06/10] simple-ipc: add win32 implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-12T15:31:28Z","receivedAt":"2021-01-12T15:33:14Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Windows implementation of \"simple-ipc\" using named pipes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   5 +\n compat/simple-ipc/ipc-shared.c      |  28 ++\n compat/simple-ipc/ipc-win32.c       | 723 ++++++++++++++++++++++++++++\n config.mak.uname                    |   2 +\n contrib/buildsystems/CMakeLists.txt |   4 +\n simple-ipc.h                        | 216 +++++++++\n 6 files changed, 978 insertions(+)\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n\ndiff --git a/Makefile b/Makefile\nindex 7b64106930a..c94d5847919 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1682,6 +1682,11 @@ else\n \tLIB_OBJS += unix-socket.o\n endif\n \n+ifdef USE_WIN32_IPC\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-win32.o\n+endif\n+\n ifdef NO_ICONV\n \tBASIC_CFLAGS += -DNO_ICONV\n endif\ndiff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c\nnew file mode 100644\nindex 00000000000..1edec815953\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-shared.c\n@@ -0,0 +1,28 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data)\n+{\n+\tstruct ipc_server_data *server_data = NULL;\n+\tint ret;\n+\n+\tret = ipc_server_run_async(&server_data, path, opts,\n+\t\t\t\t   application_cb, application_data);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = ipc_server_await(server_data);\n+\n+\tipc_server_free(server_data);\n+\n+\treturn ret;\n+}\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\ndiff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c\nnew file mode 100644\nindex 00000000000..475d9f02ff6\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-win32.c\n@@ -0,0 +1,723 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+#error This file can only be compiled on Windows\n+#endif\n+\n+static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc)\n+{\n+\tint off = 0;\n+\tstruct strbuf realpath = STRBUF_INIT;\n+\n+\tif (!strbuf_realpath(&realpath, path, 0))\n+\t\treturn -1;\n+\n+\toff = swprintf(wpath, alloc, L\"\\\\\\\\.\\\\pipe\\\\\");\n+\tif (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0)\n+\t\treturn -1;\n+\n+\t/* Handle drive prefix */\n+\tif (wpath[off] && wpath[off + 1] == L':') {\n+\t\twpath[off + 1] = L'_';\n+\t\toff += 2;\n+\t}\n+\n+\tfor (; wpath[off]; off++)\n+\t\tif (wpath[off] == L'/')\n+\t\t\twpath[off] = L'\\\\';\n+\n+\tstrbuf_release(&realpath);\n+\treturn 0;\n+}\n+\n+static enum ipc_active_state get_active_state(wchar_t *pipe_path)\n+{\n+\tif (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT))\n+\t\treturn IPC_STATE__LISTENING;\n+\n+\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\tif (GetLastError() == ERROR_FILE_NOT_FOUND)\n+\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\treturn IPC_STATE__OTHER_ERROR;\n+}\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\twchar_t pipe_path[MAX_PATH];\n+\n+\tif (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\treturn get_active_state(pipe_path);\n+}\n+\n+#define WAIT_STEP_MS (50)\n+\n+static enum ipc_active_state connect_to_server(\n+\tconst wchar_t *wpath,\n+\tDWORD timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tDWORD t_start_ms, t_waited_ms;\n+\tDWORD step_ms;\n+\tHANDLE hPipe = INVALID_HANDLE_VALUE;\n+\tDWORD mode = PIPE_READMODE_BYTE;\n+\tDWORD gle;\n+\n+\t*pfd = -1;\n+\n+\tfor (;;) {\n+\t\thPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE,\n+\t\t\t\t    0, NULL, OPEN_EXISTING, 0, NULL);\n+\t\tif (hPipe != INVALID_HANDLE_VALUE)\n+\t\t\tbreak;\n+\n+\t\tgle = GetLastError();\n+\n+\t\tswitch (gle) {\n+\t\tcase ERROR_FILE_NOT_FOUND:\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tstep_ms = (timeout_ms < WAIT_STEP_MS) ?\n+\t\t\t\ttimeout_ms : WAIT_STEP_MS;\n+\t\t\tsleep_millisec(step_ms);\n+\n+\t\t\ttimeout_ms -= step_ms;\n+\t\t\tbreak; /* try again */\n+\n+\t\tcase ERROR_PIPE_BUSY:\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tt_start_ms = (DWORD)(getnanotime() / 1000000);\n+\n+\t\t\tif (!WaitNamedPipeW(wpath, timeout_ms)) {\n+\t\t\t\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * A pipe server instance became available.\n+\t\t\t * Race other client processes to connect to\n+\t\t\t * it.\n+\t\t\t *\n+\t\t\t * But first decrement our overall timeout so\n+\t\t\t * that we don't starve if we keep losing the\n+\t\t\t * race.  But also guard against special\n+\t\t\t * NPMWAIT_ values (0 and -1).\n+\t\t\t */\n+\t\t\tt_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms;\n+\t\t\tif (t_waited_ms < timeout_ms)\n+\t\t\t\ttimeout_ms -= t_waited_ms;\n+\t\t\telse\n+\t\t\t\ttimeout_ms = 1;\n+\t\t\tbreak; /* try again */\n+\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t}\n+\t}\n+\n+\tif (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t*pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);\n+\tif (*pfd < 0) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t/* fd now owns hPipe */\n+\n+\treturn IPC_STATE__LISTENING;\n+}\n+\n+/*\n+ * The default connection timeout for Windows clients.\n+ *\n+ * This is not currently part of the ipc_ API (nor the config settings)\n+ * because of differences between Windows and other platforms.\n+ *\n+ * This value was chosen at random.\n+ */\n+#define WINDOWS_CONNECTION_TIMEOUT_MS (30000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\twchar_t wpath[MAX_PATH];\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\n+\t*pfd = -1;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tif (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0)\n+\t\tstate = IPC_STATE__INVALID_PATH;\n+\telse\n+\t\tstate = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t\t  options, pfd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\treturn state;\n+}\n+\n+int ipc_client_send_command_to_fd(int fd, const char *message,\n+\t\t\t\t  struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf(message, strlen(message), fd, 1) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tFlushFileBuffers((HANDLE)_get_osfhandle(fd));\n+\n+\tif (read_packetized_to_strbuf(fd, answer, PACKET_READ_NEVER_DIE) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *response)\n+{\n+\tint fd;\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\n+\tstate = ipc_client_try_connect(path, options, &fd);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_fd(fd, message, response);\n+\tclose(fd);\n+\treturn ret;\n+}\n+\n+/*\n+ * Duplicate the given pipe handle and wrap it in a file descriptor so\n+ * that we can use pkt-line on it.\n+ */\n+static int dup_fd_from_pipe(const HANDLE pipe)\n+{\n+\tHANDLE process = GetCurrentProcess();\n+\tHANDLE handle;\n+\tint fd;\n+\n+\tif (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE,\n+\t\t\t     DUPLICATE_SAME_ACCESS)) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\treturn -1;\n+\t}\n+\n+\tfd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY);\n+\tif (fd < 0) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\tCloseHandle(handle);\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * `handle` is now owned by `fd` and will be automatically closed\n+\t * when the descriptor is closed.\n+\t */\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_SERVER_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_server_thread_data *server_thread_data;\n+};\n+\n+struct ipc_server_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+\tHANDLE hPipe;\n+};\n+\n+/*\n+ * On Windows, the conceptual \"ipc-server\" is implemented as a pool of\n+ * n idential/peer \"server-thread\" threads.  That is, there is no\n+ * hierarchy of threads; and therefore no controller thread managing\n+ * the pool.  Each thread has an independent handle to the named pipe,\n+ * receives incoming connections, processes the client, and re-uses\n+ * the pipe for the next client connection.\n+ *\n+ * Therefore, the \"ipc-server\" only needs to maintain a list of the\n+ * spawned threads for eventual \"join\" purposes.\n+ *\n+ * A single \"stop-event\" is visible to all of the server threads to\n+ * tell them to shutdown (when idle).\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\twchar_t wpath[MAX_PATH];\n+\n+\tHANDLE hEventStopRequested;\n+\tstruct ipc_server_thread_data *thread_list;\n+\tint is_stopped;\n+};\n+\n+enum connect_result {\n+\tCR_CONNECTED = 0,\n+\tCR_CONNECT_PENDING,\n+\tCR_CONNECT_ERROR,\n+\tCR_WAIT_ERROR,\n+\tCR_SHUTDOWN,\n+};\n+\n+static enum connect_result queue_overlapped_connect(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tif (ConnectNamedPipe(server_thread_data->hPipe, lpo))\n+\t\tgoto failed;\n+\n+\tswitch (GetLastError()) {\n+\tcase ERROR_IO_PENDING:\n+\t\treturn CR_CONNECT_PENDING;\n+\n+\tcase ERROR_PIPE_CONNECTED:\n+\t\tSetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+failed:\n+\terror(_(\"ConnectNamedPipe failed for '%s' (%lu)\"),\n+\t      server_thread_data->server_data->buf_path.buf,\n+\t      GetLastError());\n+\treturn CR_CONNECT_ERROR;\n+}\n+\n+/*\n+ * Use Windows Overlapped IO to wait for a connection or for our event\n+ * to be signalled.\n+ */\n+static enum connect_result wait_for_connection(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tenum connect_result r;\n+\tHANDLE waitHandles[2];\n+\tDWORD dwWaitResult;\n+\n+\tr = queue_overlapped_connect(server_thread_data, lpo);\n+\tif (r != CR_CONNECT_PENDING)\n+\t\treturn r;\n+\n+\twaitHandles[0] = server_thread_data->server_data->hEventStopRequested;\n+\twaitHandles[1] = lpo->hEvent;\n+\n+\tdwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);\n+\tswitch (dwWaitResult) {\n+\tcase WAIT_OBJECT_0 + 0:\n+\t\treturn CR_SHUTDOWN;\n+\n+\tcase WAIT_OBJECT_0 + 1:\n+\t\tResetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\treturn CR_WAIT_ERROR;\n+\t}\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf(response, response_len,\n+\t\t\t\t\t reply_data->fd, 0);\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ *\n+ * Simple-IPC only contains one round trip, so we flush and close\n+ * here after the response.\n+ */\n+static int do_io(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.server_thread_data = server_thread_data;\n+\n+\treply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe);\n+\tif (reply_data.fd < 0)\n+\t\treturn error(_(\"could not create fd from pipe for '%s'\"),\n+\t\t\t     server_thread_data->server_data->buf_path.buf);\n+\n+\tret = read_packetized_to_strbuf(reply_data.fd, &buf,\n+\t\t\t\t\tPACKET_READ_NEVER_DIE);\n+\tif (ret >= 0) {\n+\t\tret = server_thread_data->server_data->application_cb(\n+\t\t\tserver_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\n+\t\tFlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd)));\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Handle IPC request and response with this connected client.  And reset\n+ * the pipe to prepare for the next client.\n+ */\n+static int use_connection(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tint ret;\n+\n+\tret = do_io(server_thread_data);\n+\n+\tFlushFileBuffers(server_thread_data->hPipe);\n+\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Thread proc for an IPC server worker thread.  It handles a series of\n+ * connections from clients.  It cleans and reuses the hPipe between each\n+ * client.\n+ */\n+static void *server_thread_proc(void *_server_thread_data)\n+{\n+\tstruct ipc_server_thread_data *server_thread_data = _server_thread_data;\n+\tHANDLE hEventConnected = INVALID_HANDLE_VALUE;\n+\tOVERLAPPED oConnect;\n+\tenum connect_result cr;\n+\tint ret;\n+\n+\tassert(server_thread_data->hPipe != INVALID_HANDLE_VALUE);\n+\n+\ttrace2_thread_start(\"ipc-server\");\n+\ttrace2_data_string(\"ipc-server\", NULL, \"pipe\",\n+\t\t\t   server_thread_data->server_data->buf_path.buf);\n+\n+\thEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL);\n+\n+\tmemset(&oConnect, 0, sizeof(oConnect));\n+\toConnect.hEvent = hEventConnected;\n+\n+\tfor (;;) {\n+\t\tcr = wait_for_connection(server_thread_data, &oConnect);\n+\n+\t\tswitch (cr) {\n+\t\tcase CR_SHUTDOWN:\n+\t\t\tgoto finished;\n+\n+\t\tcase CR_CONNECTED:\n+\t\t\tret = use_connection(server_thread_data);\n+\t\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\tserver_thread_data->server_data);\n+\t\t\t\tgoto finished;\n+\t\t\t}\n+\t\t\tif (ret > 0) {\n+\t\t\t\t/*\n+\t\t\t\t * Ignore (transient) IO errors with this\n+\t\t\t\t * client and reset for the next client.\n+\t\t\t\t */\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase CR_CONNECT_PENDING:\n+\t\t\t/* By construction, this should not happen. */\n+\t\t\tBUG(\"ipc-server[%s]: unexpeced CR_CONNECT_PENDING\",\n+\t\t\t    server_thread_data->server_data->buf_path.buf);\n+\n+\t\tcase CR_CONNECT_ERROR:\n+\t\tcase CR_WAIT_ERROR:\n+\t\t\t/*\n+\t\t\t * Ignore these theoretical errors.\n+\t\t\t */\n+\t\t\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tBUG(\"unandled case after wait_for_connection\");\n+\t\t}\n+\t}\n+\n+finished:\n+\tCloseHandle(server_thread_data->hPipe);\n+\tCloseHandle(hEventConnected);\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+static HANDLE create_new_pipe(wchar_t *wpath, int is_first)\n+{\n+\tHANDLE hPipe;\n+\tDWORD dwOpenMode, dwPipeMode;\n+\tLPSECURITY_ATTRIBUTES lpsa = NULL;\n+\n+\tdwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |\n+\t\tFILE_FLAG_OVERLAPPED;\n+\n+\tdwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |\n+\t\tPIPE_REJECT_REMOTE_CLIENTS;\n+\n+\tif (is_first) {\n+\t\tdwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE;\n+\n+\t\t/*\n+\t\t * On Windows, the first server pipe instance gets to\n+\t\t * set the ACL / Security Attributes on the named\n+\t\t * pipe; subsequent instances inherit and cannot\n+\t\t * change them.\n+\t\t *\n+\t\t * TODO Should we allow the application layer to\n+\t\t * specify security attributes, such as `LocalService`\n+\t\t * or `LocalSystem`, when we create the named pipe?\n+\t\t * This question is probably not important when the\n+\t\t * daemon is started by a foreground user process and\n+\t\t * only needs to talk to the current user, but may be\n+\t\t * if the daemon is run via the Control Panel as a\n+\t\t * System Service.\n+\t\t */\n+\t}\n+\n+\thPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,\n+\t\t\t\t PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);\n+\n+\treturn hPipe;\n+}\n+\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct ipc_server_data *server_data;\n+\twchar_t wpath[MAX_PATH];\n+\tHANDLE hPipeFirst = INVALID_HANDLE_VALUE;\n+\tint k;\n+\tint ret = 0;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\tret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));\n+\tif (ret < 0)\n+\t\treturn error(\n+\t\t\t_(\"could not create normalized wchar_t path for '%s'\"),\n+\t\t\tpath);\n+\n+\thPipeFirst = create_new_pipe(wpath, 1);\n+\tif (hPipeFirst == INVALID_HANDLE_VALUE)\n+\t\treturn error(_(\"IPC server already running on '%s'\"), path);\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tserver_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL);\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\twcscpy(server_data->wpath, wpath);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_server_thread_data *std;\n+\n+\t\tstd = xcalloc(1, sizeof(*std));\n+\t\tstd->magic = MAGIC_SERVER_THREAD_DATA;\n+\t\tstd->server_data = server_data;\n+\t\tstd->hPipe = INVALID_HANDLE_VALUE;\n+\n+\t\tstd->hPipe = (k == 0)\n+\t\t\t? hPipeFirst\n+\t\t\t: create_new_pipe(server_data->wpath, 0);\n+\n+\t\tif (std->hPipe == INVALID_HANDLE_VALUE) {\n+\t\t\t/*\n+\t\t\t * If we've reached a pipe instance limit for\n+\t\t\t * this path, just use fewer threads.\n+\t\t\t */\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (pthread_create(&std->pthread_id, NULL,\n+\t\t\t\t   server_thread_proc, std)) {\n+\t\t\t/*\n+\t\t\t * Likewise, if we're out of threads, just use\n+\t\t\t * fewer threads than requested.\n+\t\t\t *\n+\t\t\t * However, we just give up if we can't even get\n+\t\t\t * one thread.  This should not happen.\n+\t\t\t */\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start thread[0] for '%s'\"),\n+\t\t\t\t    path);\n+\n+\t\t\tCloseHandle(std->hPipe);\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tstd->next_thread = server_data->thread_list;\n+\t\tserver_data->thread_list = std;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Gently tell all of the ipc_server threads to shutdown.\n+\t * This will be seen the next time they are idle (and waiting\n+\t * for a connection).\n+\t *\n+\t * We DO NOT attempt to force them to drop an active connection.\n+\t */\n+\tSetEvent(server_data->hEventStopRequested);\n+\treturn 0;\n+}\n+\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tDWORD dwWaitResult;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\tdwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE);\n+\tif (dwWaitResult != WAIT_OBJECT_0)\n+\t\treturn error(_(\"wait for hEvent failed for '%s'\"),\n+\t\t\t     server_data->buf_path.buf);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tpthread_join(std->pthread_id, NULL);\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tif (server_data->hEventStopRequested != INVALID_HANDLE_VALUE)\n+\t\tCloseHandle(server_data->hEventStopRequested);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tfree(server_data);\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 198ab1e58f8..76087cff678 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows)\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \t# USE_NED_ALLOCATOR = YesPlease\n@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \tUSE_NED_ALLOCATOR = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex c151dd7257f..4bd41054ee7 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n \tlist(APPEND compat_SOURCES unix-socket.c)\n endif()\n \n+if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+endif()\n+\n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\n \n #header checks\ndiff --git a/simple-ipc.h b/simple-ipc.h\nnew file mode 100644\nindex 00000000000..cd525e711bd\n--- /dev/null\n+++ b/simple-ipc.h\n@@ -0,0 +1,216 @@\n+#ifndef GIT_SIMPLE_IPC_H\n+#define GIT_SIMPLE_IPC_H\n+\n+/*\n+ * See Documentation/technical/api-simple-ipc.txt\n+ */\n+\n+#if defined(GIT_WINDOWS_NATIVE)\n+#define SUPPORTS_SIMPLE_IPC\n+#endif\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+\n+/*\n+ * Simple IPC Client Side API.\n+ */\n+\n+enum ipc_active_state {\n+\t/*\n+\t * The pipe/socket exists and the daemon is waiting for connections.\n+\t */\n+\tIPC_STATE__LISTENING = 0,\n+\n+\t/*\n+\t * The pipe/socket exists, but the daemon is not listening.\n+\t * Perhaps it is very busy.\n+\t * Perhaps the daemon died without deleting the path.\n+\t * Perhaps it is shutting down and draining existing clients.\n+\t * Perhaps it is dead, but other clients are lingering and\n+\t * still holding a reference to the pathname.\n+\t */\n+\tIPC_STATE__NOT_LISTENING,\n+\n+\t/*\n+\t * The requested pathname is bogus and no amount of retries\n+\t * will fix that.\n+\t */\n+\tIPC_STATE__INVALID_PATH,\n+\n+\t/*\n+\t * The requested pathname is not found.  This usually means\n+\t * that there is no daemon present.\n+\t */\n+\tIPC_STATE__PATH_NOT_FOUND,\n+\n+\tIPC_STATE__OTHER_ERROR,\n+};\n+\n+struct ipc_client_connect_options {\n+\t/*\n+\t * Spin under timeout if the server is running but can't\n+\t * accept our connection yet.  This should always be set\n+\t * unless you just want to poke the server and see if it\n+\t * is alive.\n+\t */\n+\tunsigned int wait_if_busy:1;\n+\n+\t/*\n+\t * Spin under timeout if the pipe/socket is not yet present\n+\t * on the file system.  This is useful if we just started\n+\t * the service and need to wait for it to become ready.\n+\t */\n+\tunsigned int wait_if_not_found:1;\n+};\n+\n+#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n+\t.wait_if_busy = 0, \\\n+\t.wait_if_not_found = 0, \\\n+}\n+\n+/*\n+ * Determine if a server is listening on this named pipe or socket using\n+ * platform-specific logic.  This might just probe the filesystem or it\n+ * might make a trivial connection to the server using this pathname.\n+ */\n+enum ipc_active_state ipc_get_active_state(const char *path);\n+\n+/*\n+ * Try to connect to the daemon on the named pipe or socket.\n+ *\n+ * Returns IPC_STATE__LISTENING (and an fd) when connected.\n+ *\n+ * Otherwise, returns info to help decide whether to retry or to\n+ * spawn/respawn the server.\n+ */\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd);\n+\n+/*\n+ * Used by the client to synchronously send and receive a message with\n+ * the server on the provided fd.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command_to_fd(int fd, const char *message,\n+\t\t\t\t  struct strbuf *answer);\n+\n+/*\n+ * Used by the client to synchronously connect and send and receive a\n+ * message to the server listening at the given path.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer);\n+\n+/*\n+ * Simple IPC Server Side API.\n+ */\n+\n+struct ipc_server_reply_data;\n+\n+typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *,\n+\t\t\t\t  const char *response,\n+\t\t\t\t  size_t response_len);\n+\n+/*\n+ * Prototype for an application-supplied callback to process incoming\n+ * client IPC messages and compose a reply.  The `application_cb` should\n+ * use the provided `reply_cb` and `reply_data` to send an IPC response\n+ * back to the client.  The `reply_cb` callback can be called multiple\n+ * times for chunking purposes.  A reply message is optional and may be\n+ * omitted if not necessary for the application.\n+ *\n+ * The return value from the application callback is ignored.\n+ * The value `SIMPLE_IPC_QUIT` can be used to shutdown the server.\n+ */\n+typedef int (ipc_server_application_cb)(void *application_data,\n+\t\t\t\t\tconst char *request,\n+\t\t\t\t\tipc_server_reply_cb *reply_cb,\n+\t\t\t\t\tstruct ipc_server_reply_data *reply_data);\n+\n+#define SIMPLE_IPC_QUIT -2\n+\n+/*\n+ * Opaque instance data to represent an IPC server instance.\n+ */\n+struct ipc_server_data;\n+\n+/*\n+ * Control parameters for the IPC server instance.\n+ * Use this to hide platform-specific settings.\n+ */\n+struct ipc_server_opts\n+{\n+\tint nr_threads;\n+};\n+\n+/*\n+ * Start an IPC server instance in one or more background threads\n+ * and return a handle to the pool.\n+ *\n+ * Returns 0 if the asynchronous server pool was started successfully.\n+ * Returns -1 if not.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data);\n+\n+/*\n+ * Gently signal the IPC server pool to shutdown.  No new client\n+ * connections will be accepted, but existing connections will be\n+ * allowed to complete.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data);\n+\n+/*\n+ * Block the calling thread until all threads in the IPC server pool\n+ * have completed and been joined.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data);\n+\n+/*\n+ * Close and free all resource handles associated with the IPC server\n+ * pool.\n+ */\n+void ipc_server_free(struct ipc_server_data *server_data);\n+\n+/*\n+ * Run an IPC server instance and block the calling thread of the\n+ * current process.  It does not return until the IPC server has\n+ * either shutdown or had an unrecoverable error.\n+ *\n+ * The IPC server handles incoming IPC messages from client processes\n+ * and may use one or more background threads as necessary.\n+ *\n+ * Returns 0 after the server has completed successfully.\n+ * Returns -1 if the server cannot be started.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ *\n+ * Note that `ipc_server_run()` is a synchronous wrapper around the\n+ * above asynchronous routines.  It effectively hides all of the\n+ * server state and thread details from the caller and presents a\n+ * simple synchronous interface.\n+ */\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data);\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\n+#endif /* GIT_SIMPLE_IPC_H */\n-- \ngitgitgadget\n\n"},{"id":"414173","messageId":"87eeiq5csb.fsf@evledraar.gmail.com","threadId":"54978","inReplyTo":"7064c5e9ffa0e3e666ea6d146b0839680952757d.1610465493.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 05/10] simple-ipc: design documentation for new IPC mechanism","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-01-12T16:40:36Z","receivedAt":"2021-01-12T16:41:37Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Jan 12 2021, Jeff Hostetler via GitGitGadget wrote:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n>\n> Brief design documentation for new IPC mechanism allowing\n> foreground Git client to talk with an existing daemon process\n> at a known location using a named pipe or unix domain socket.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> ---\n>  Documentation/technical/api-simple-ipc.txt | 31 ++++++++++++++++++++++\n>  1 file changed, 31 insertions(+)\n>  create mode 100644 Documentation/technical/api-simple-ipc.txt\n>\n> diff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt\n> new file mode 100644\n> index 00000000000..920994a69d3\n> --- /dev/null\n> +++ b/Documentation/technical/api-simple-ipc.txt\n> @@ -0,0 +1,31 @@\n> +simple-ipc API\n> +==============\n> +\n> +The simple-ipc API is used to send an IPC message and response between\n> +a (presumably) foreground Git client process to a background server or\n> +daemon process.  The server process must already be running.  Multiple\n> +client processes can simultaneously communicate with the server\n> +process.\n> +\n> +Communication occurs over a named pipe on Windows and a Unix domain\n> +socket on other platforms.  Clients and the server rendezvous at a\n> +previously agreed-to application-specific pathname (which is outside\n> +the scope of this design).\n> +\n> +This IPC mechanism differs from the existing `sub-process.c` model\n> +(Documentation/technical/long-running-process-protocol.txt) and used\n> +by applications like Git-LFS because the server is assumed to be very\n\ns/to be very long running/to be a long running/, or at least \"s/to be\nvery/to be a very/.\n\n> +long running system service.  In contrast, a \"sub-process model process\"\n> +is started with the foreground process and exits when the foreground\n> +process terminates.  How the server is started is also outside the\n> +scope of the IPC mechanism.\n> +\n> +The IPC protocol consists of a single request message from the client and\n> +an optional request message from the server.  For simplicity, pkt-line\n> +routines are used to hide chunking and buffering concerns.  Each side\n> +terminates their message with a flush packet.\n> +(Documentation/technical/protocol-common.txt)\n> +\n> +The actual format of the client and server messages is application\n> +specific.  The IPC layer transmits and receives an opaque buffer without\n> +any concern for the content within.\n\n"},{"id":"414176","messageId":"87bldu5cbh.fsf@evledraar.gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/10] [RFC] Simple IPC Mechanism","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-01-12T16:50:42Z","receivedAt":"2021-01-12T16:51:55Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Jan 12 2021, Jeff Hostetler via GitGitGadget wrote:\n\n> This series introduces a multi-threaded IPC mechanism called \"Simple IPC\".\n> This is a library-layer feature to make it easy to create very long running\n> daemon/service applications and for unrelated Git commands to communicate\n> with them. Communication uses pkt-line messaging over a Windows named pipe\n> or Unix domain socket.\n>\n> On the server side, Simple IPC implements a (platform-specific) connection\n> listener and worker thread-pool to accept and handle a series of client\n> connections. The server functionality is completely hidden behind the\n> ipc_server_run() and ipc_server_run_async() APIs. The daemon/service\n> application only needs to define an application-specific callback to handle\n> client requests.\n>\n> Note that Simple IPC is completely unrelated to the long running process\n> feature (described in sub-process.h) where the lifetime of a \"sub-process\"\n> child is bound to that of the invoking parent process and communication\n> occurs over the child's stdin/stdout.\n>\n> Simple IPC will serve as a basis for a future builtin FSMonitor daemon\n> feature.\n\nI only skimmed this so far. In the past we had a git-read-cache--daemon\n-> git-index-helper[1] -> watchman. The last iteration of that seems to\nbe the [3] re-roll from Ben Peart in 2017. I used/tested that for a\nwhile and had some near-production use-cases of it.\n\nHow does this new series relate to that past work (if at all), and (not\nhaving re-read the old threads) were there reasons those old patch\nserieses weren't merged in that are addressed here, mitigated etc?\n\n1. https://lore.kernel.org/git/1402406665-27988-1-git-send-email-pclouds@gmail.com/\n2. https://lore.kernel.org/git/1457548582-28302-1-git-send-email-dturner@twopensource.com/\n3. https://lore.kernel.org/git/20170518201333.13088-1-benpeart@microsoft.com/\n4. https://lore.kernel.org/git/87bmhfwmqa.fsf@evledraar.gmail.com/\n"},{"id":"414189","messageId":"7b32caeb-0989-d638-5830-89ea4a2be506@jeffhostetler.com","threadId":"54978","inReplyTo":"87bldu5cbh.fsf@evledraar.gmail.com","subject":"Re: [PATCH 00/10] [RFC] Simple IPC Mechanism","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-01-12T18:25:59Z","receivedAt":"2021-01-12T18:36:35Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/12/21 11:50 AM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Tue, Jan 12 2021, Jeff Hostetler via GitGitGadget wrote:\n> \n>> This series introduces a multi-threaded IPC mechanism called \"Simple IPC\".\n>> This is a library-layer feature to make it easy to create very long running\n>> daemon/service applications and for unrelated Git commands to communicate\n>> with them. Communication uses pkt-line messaging over a Windows named pipe\n>> or Unix domain socket.\n>>\n>> On the server side, Simple IPC implements a (platform-specific) connection\n>> listener and worker thread-pool to accept and handle a series of client\n>> connections. The server functionality is completely hidden behind the\n>> ipc_server_run() and ipc_server_run_async() APIs. The daemon/service\n>> application only needs to define an application-specific callback to handle\n>> client requests.\n>>\n>> Note that Simple IPC is completely unrelated to the long running process\n>> feature (described in sub-process.h) where the lifetime of a \"sub-process\"\n>> child is bound to that of the invoking parent process and communication\n>> occurs over the child's stdin/stdout.\n>>\n>> Simple IPC will serve as a basis for a future builtin FSMonitor daemon\n>> feature.\n> \n> I only skimmed this so far. In the past we had a git-read-cache--daemon\n> -> git-index-helper[1] -> watchman. The last iteration of that seems to\n> be the [3] re-roll from Ben Peart in 2017. I used/tested that for a\n> while and had some near-production use-cases of it.\n> \n> How does this new series relate to that past work (if at all), and (not\n> having re-read the old threads) were there reasons those old patch\n> serieses weren't merged in that are addressed here, mitigated etc?\n> \n> 1. https://lore.kernel.org/git/1402406665-27988-1-git-send-email-pclouds@gmail.com/\n> 2. https://lore.kernel.org/git/1457548582-28302-1-git-send-email-dturner@twopensource.com/\n> 3. https://lore.kernel.org/git/20170518201333.13088-1-benpeart@microsoft.com/\n> 4. https://lore.kernel.org/git/87bmhfwmqa.fsf@evledraar.gmail.com/\n> \n\nI'm starting with the model used by the existing FSMonitor feature\nthat Ben Peart and Kevin Willford added to Git.\n\nItem [3] looks to be an earlier draft of that effort.  The idea there\nwas to add the fsmonitor hook that could talk to a daemon like Watchman\nand quickly update the in-memory cache-entry flags without the need to\nlstat() and similarly update the untracked-cache.  An index extension\nwas added to remember the last fsmonitor response processed.\n\nCurrently in Git, we have a fsmonitor hook (usually a perl script) that\ntalks to Watchman and translates the Watchman response back into\nsomething that the Git client can understand.  This comes back as a\nlist of files that have changed since some timestamp (or in V2, relative\nto some daemon-specific token).\n\nItems [1,2] are not related to that.  That was a different effort to\nquickly fetch a read-only copy of an already-parsed index via shared\nmemory.  In the last version I saw, there were 2 daemons.  index-helper\nkept a fresh view of the index in shared memory and could give it to\nthe Git client.  The client could just mmap the pre-parsed index and\navoid calling `read_index()`.  Index-helper would drive Watchman to\nkeep track of cache-entries as they changed and handle the lstat's.\n\nI'm not familiar with [4] (and I only quickly scanned it).  There are\nseveral ideas for finding slow spots while reading the index.  I don't\nwant to go into all of them, but several are obsolete now.  They didn't\ncontribute to the current effort.\n\n\nThe Simple IPC series (and a soon to be submitted fsmonitor--daemon\nseries) are intended to be a follow on to FSMonitor effort that is\ncurrently in Git.\n\n1. Build a git-native daemon to watch the file system and avoid needing\na third-party tool.  This doesn't preclude the use of Watchman, but\nhaving a builtin tool might simplify engineering support costs when\ndeploying to a large team.\n\n2. Use direct IPC between the Git command and the daemon to avoid the\nexpense of the Hook API (which is expensive on Windows).\n\n3. Make the daemon Git-aware.  For example, it might want to pre-filter\nignored files.  (This might not be present in V1.  And we might extend\nthe daemon to do more of this as we improve performance.)\n\nJeff\n"},{"id":"414225","messageId":"xmqq5z4153gq.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/10] [RFC] Simple IPC Mechanism","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-12T20:01:57Z","receivedAt":"2021-01-12T21:47:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This series introduces a multi-threaded IPC mechanism called \"Simple IPC\".\n> This is a library-layer feature to make it easy to create very long running\n> daemon/service applications and for unrelated Git commands to communicate\n> with them. Communication uses pkt-line messaging over a Windows named pipe\n> or Unix domain socket.\n>\n> On the server side, Simple IPC implements a (platform-specific) connection\n> listener and worker thread-pool to accept and handle a series of client\n> connections. The server functionality is completely hidden behind the\n> ipc_server_run() and ipc_server_run_async() APIs. The daemon/service\n> application only needs to define an application-specific callback to handle\n> client requests.\n>\n> Note that Simple IPC is completely unrelated to the long running process\n> feature (described in sub-process.h) where the lifetime of a \"sub-process\"\n> child is bound to that of the invoking parent process and communication\n> occurs over the child's stdin/stdout.\n>\n> Simple IPC will serve as a basis for a future builtin FSMonitor daemon\n> feature.\n\nWhat kind of security implications does this bring into the system?\n\nCan a Simple IPC daemon be connected by any client?  How does the\ndaemon know that the other side is authorized to make requests?\nWhen a git binary acting as client connect to whatever happens to be\nlistening to the well-known location, how does it know if the other\nside is the daemon it wanted to talk to and not a malicious MITM or\nother impersonator?\n\nOr is this to be only used for \"this is meant to be used to give\nread-only access to data that is public anyway\" kind of daemon,\ne.g. \"git://\" transport that serves clones and fetches?\n\nOr is this meant to be used on client machines where all the\nprocesses are assumed to be working for the end user, so it is OK to\ndeclare that anything will go (good old DOS mental model?)\n\nI know at the Mechanism level we do not yet know how it will be\nused, but we cannot retrofit sufficient security, so it would be\nnecessary to know answers to these questions.\n\nThanks.\n"},{"id":"414235","messageId":"cee527cb-7962-1528-0c70-583ad805e624@jeffhostetler.com","threadId":"54978","inReplyTo":"xmqq5z4153gq.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 00/10] [RFC] Simple IPC Mechanism","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-01-12T23:25:20Z","receivedAt":"2021-01-12T23:26:19Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/12/21 3:01 PM, Junio C Hamano wrote:\n> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> This series introduces a multi-threaded IPC mechanism called \"Simple IPC\".\n>> This is a library-layer feature to make it easy to create very long running\n>> daemon/service applications and for unrelated Git commands to communicate\n>> with them. Communication uses pkt-line messaging over a Windows named pipe\n>> or Unix domain socket.\n>>\n>> On the server side, Simple IPC implements a (platform-specific) connection\n>> listener and worker thread-pool to accept and handle a series of client\n>> connections. The server functionality is completely hidden behind the\n>> ipc_server_run() and ipc_server_run_async() APIs. The daemon/service\n>> application only needs to define an application-specific callback to handle\n>> client requests.\n>>\n>> Note that Simple IPC is completely unrelated to the long running process\n>> feature (described in sub-process.h) where the lifetime of a \"sub-process\"\n>> child is bound to that of the invoking parent process and communication\n>> occurs over the child's stdin/stdout.\n>>\n>> Simple IPC will serve as a basis for a future builtin FSMonitor daemon\n>> feature.\n> \n> What kind of security implications does this bring into the system?\n> \n> Can a Simple IPC daemon be connected by any client?  How does the\n> daemon know that the other side is authorized to make requests?\n> When a git binary acting as client connect to whatever happens to be\n> listening to the well-known location, how does it know if the other\n> side is the daemon it wanted to talk to and not a malicious MITM or\n> other impersonator?\n> \n> Or is this to be only used for \"this is meant to be used to give\n> read-only access to data that is public anyway\" kind of daemon,\n> e.g. \"git://\" transport that serves clones and fetches?\n> \n> Or is this meant to be used on client machines where all the\n> processes are assumed to be working for the end user, so it is OK to\n> declare that anything will go (good old DOS mental model?)\n> \n> I know at the Mechanism level we do not yet know how it will be\n> used, but we cannot retrofit sufficient security, so it would be\n> necessary to know answers to these questions.\n> \n> Thanks.\n> \n\nGood questions.\n\nYes, this is a local-only mechanism.  A local-only named pipe on\nWindows and a Unix domain socket on Unix.  In both cases the daemon\ncreates the pipe/socket as the foreground user (since the daemon\nprocess will be implicitly started by the first Git command that\nneeds to talk to it).  Later client process try to open the pipe/socket\nwith RW access if they can.\n\nOn Windows a local named pipe is created by the server side.  It rejects\nremote connections.  I did not put an ACL, so it should inherit the\nsystem default which grants the user RW access (since the daemon is\nimplicitly started by the first foreground client command that needs\nto talk to it.)  Other users in the user's group and the anonymous\nuser should have R but not W access to it, so they could not be able\nto connect.  The name pipe is kept in the local Named Pipe File System\n(NPFS) as `\\\\.\\pipe\\<unique-path>` so it is globally visible on the \nsystem, but I don't think it is a problem.\n\nOn the Unix side, the socket is created inside the .git directory\nby the daemon.  Potential clients would have to have access to the\nworking directory and the .git directory to connect to the socket,\nso in normal circumstances they would be able to read everything in\nthe WD anyway.  So again, I don't think it is a problem.\n\n\nAlternatively, if a malicious server is started and holds the named\npipe or socket:  the client might be able to talk to it (assuming\nthat bad server grants access or impersonates the rightful user).\nThe client might not be able to tell they've been tricked, but at\nthat point the system is already compromised.\n\nSo unless I'm missing something, I think we're OK either way.\n\nJeff\n"},{"id":"414236","messageId":"968235ae-4b31-f06c-149a-c581e64b318b@jeffhostetler.com","threadId":"54978","inReplyTo":"xmqqo8htzoba.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 00/10] [RFC] Simple IPC Mechanism","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-01-13T00:32:38Z","receivedAt":"2021-01-13T00:46:49Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/12/21 7:13 PM, Junio C Hamano wrote:\n> Jeff Hostetler <git@jeffhostetler.com> writes:\n> \n>> On Windows a local named pipe is created by the server side.  It rejects\n>> remote connections.  I did not put an ACL, so it should inherit the\n>> system default which grants the user RW access (since the daemon is\n>> implicitly started by the first foreground client command that needs\n>> to talk to it.)  Other users in the user's group and the anonymous\n>> user should have R but not W access to it, so they could not be able\n>> to connect.  The name pipe is kept in the local Named Pipe File System\n>> (NPFS) as `\\\\.\\pipe\\<unique-path>` so it is globally visible on the\n>> system, but I don't think it is a problem.\n> \n> It is not intuitively obvious why globalluy visible thing is OK to\n> me, but I'll take your word for it on stuff about Windows.\n\nSorry, that's a quirk of Windows.  Windows has a funky virtual drive\nwhere named pipes are stored -- kind of like a magic directory in /proc\non Linux.  All local named pipes have the \"\\\\.\\pipe\\\" path prefix.\n\nSo they are globally visible as a side-effect of that \"namespace\"\nrestriction.\n\n> \n>> On the Unix side, the socket is created inside the .git directory\n>> by the daemon.  Potential clients would have to have access to the\n>> working directory and the .git directory to connect to the socket,\n>> so in normal circumstances they would be able to read everything in\n>> the WD anyway.  So again, I don't think it is a problem.\n> \n> OK, yes, writability to .git would automatically mean that\n> everything is a fair game to those who can talk to the daemon, so\n> there is no new issue here, as long as the first process that\n> creates the socket is careful not to loosen the permission.\n> \n> Thanks.\n> \n"},{"id":"414238","messageId":"xmqqo8htzoba.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"cee527cb-7962-1528-0c70-583ad805e624@jeffhostetler.com","subject":"Re: [PATCH 00/10] [RFC] Simple IPC Mechanism","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-13T00:13:29Z","receivedAt":"2021-01-13T00:47:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff Hostetler <git@jeffhostetler.com> writes:\n\n> On Windows a local named pipe is created by the server side.  It rejects\n> remote connections.  I did not put an ACL, so it should inherit the\n> system default which grants the user RW access (since the daemon is\n> implicitly started by the first foreground client command that needs\n> to talk to it.)  Other users in the user's group and the anonymous\n> user should have R but not W access to it, so they could not be able\n> to connect.  The name pipe is kept in the local Named Pipe File System\n> (NPFS) as `\\\\.\\pipe\\<unique-path>` so it is globally visible on the\n> system, but I don't think it is a problem.\n\nIt is not intuitively obvious why globalluy visible thing is OK to\nme, but I'll take your word for it on stuff about Windows.\n\n> On the Unix side, the socket is created inside the .git directory\n> by the daemon.  Potential clients would have to have access to the\n> working directory and the .git directory to connect to the socket,\n> so in normal circumstances they would be able to read everything in\n> the WD anyway.  So again, I don't think it is a problem.\n\nOK, yes, writability to .git would automatically mean that\neverything is a fair game to those who can talk to the daemon, so\nthere is no new issue here, as long as the first process that\ncreates the socket is careful not to loosen the permission.\n\nThanks.\n"},{"id":"414261","messageId":"X/71qByO5jSceIFn@coredump.intra.peff.net","threadId":"54978","inReplyTo":"1155a45cf64afb237204429cd4ff2e74f5f7602a.1610465492.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/10] pkt-line: use stack rather than static buffer in packet_write_gently()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-13T13:29:12Z","receivedAt":"2021-01-13T13:29:55Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jan 12, 2021 at 03:31:23PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> Teach packet_write_gently() to use a stack buffer rather than a static\n> buffer when composing the packet line message.  This helps get us ready\n> for threaded operations.\n\nSounds like a good goal, but...\n\n>  static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n>  {\n> -\tstatic char packet_write_buffer[LARGE_PACKET_MAX];\n> +\tchar packet_write_buffer[LARGE_PACKET_MAX];\n>  \tsize_t packet_size;\n\n64k is awfully big for the stack, especially if you are thinking about\nhaving threads. I know we've run into issues around that size before\n(though I don't offhand recall whether there was any recursion\ninvolved).\n\nWe might need to use thread-local storage here. Heap would also\nobviously work, but I don't think we'd want a new allocation per write\n(or maybe it wouldn't matter; we're making a syscall, so a malloc() may\nnot be that big a deal in terms of performance).\n\n-Peff\n"},{"id":"414262","messageId":"X/75mip8tYO/mmSW@coredump.intra.peff.net","threadId":"54978","inReplyTo":"cee527cb-7962-1528-0c70-583ad805e624@jeffhostetler.com","subject":"Re: [PATCH 00/10] [RFC] Simple IPC Mechanism","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-13T13:46:02Z","receivedAt":"2021-01-13T13:46:45Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jan 12, 2021 at 06:25:20PM -0500, Jeff Hostetler wrote:\n\n> On the Unix side, the socket is created inside the .git directory\n> by the daemon.  Potential clients would have to have access to the\n> working directory and the .git directory to connect to the socket,\n> so in normal circumstances they would be able to read everything in\n> the WD anyway.  So again, I don't think it is a problem.\n\nJust thinking out loud, here are two potential issues with putting it in\n.git that we may have to deal with later:\n\n  - fsmonitor is conceptually a read-only thing (i.e., it would speed up\n    \"git status\", etc). And not knowing much about how it will work, I'd\n    guess that is carried through (i.e., even though you may open the\n    socket R/W so that you can write requests and read them back, there\n    is no operation you can request that will overwrite data). But the\n    running user may not have write access to .git.\n\n    As long as we cleanly bail to the non-fsmonitor code paths, I don't\n    think it's the end of the world. Those read-only users just won't\n    get to use the speedup (and it may even be desirable). They may\n    complain, but it is open source so the onus is on them to improve\n    it. You will not have made anything worse. :)\n\n  - repositories may be on network filesystems that do not support unix\n    sockets.\n\nSo it would be nice if there was some way to specify an alternate path\nto be used for the socket. Possibly one or both of:\n\n  - a config option to give a root path for sockets, where Git would\n    then canonicalize the $GIT_DIR name and use $root/$GIT_DIR for the\n    socket. That solves the problem for a given user once for all repos.\n\n  - a config option to say \"use this path for the socket\". This would be\n    per-repo, but is more flexible and possibly less confusing.\n\nOne final note: on some systems[1] the permissions on the socket file\nitself are ignored. The safe way to protect it is to make sure the\npermissions on the surrounding directory are what you want. See\ncredential-cache's init_socket_directory() for an example.\n\n-Peff\n\n[1] Sorry, I don't remember which systems. This is one of those random\n    bits of Unix lore I've carried around for 20 years, and it's\n    entirely possible it is simply obsolete at this point.\n"},{"id":"414264","messageId":"X/7+YU16XrPFDYlN@coredump.intra.peff.net","threadId":"54978","inReplyTo":"96268351ac66371a0998d189db619f357d2b71fa.1610465493.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 07/10] unix-socket: create gentle version of unix_stream_listen()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-13T14:06:25Z","receivedAt":"2021-01-13T14:07:27Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jan 12, 2021 at 03:31:29PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n> Create a gentle version of `unix_stream_listen()`.  This version does\n> not call `die()` if a socket-fd cannot be created and does not assume\n> that it is safe to `unlink()` an existing socket-inode.\n\nThe existing one is meant to be gentle. Maybe it is worth fixing it\ninstead.\n\n> `unix_stream_listen()` uses `unix_stream_socket()` helper function to\n> create the socket-fd.  Avoid that helper because it calls `die()` on\n> errors.\n\nYeah, I think this is just a bug. My thinking in the original was that\nsocket() would basically never fail. And it generally wouldn't, but\nthings like EMFILE do happen. There are only two callers, and both would\nbe one-liners to propagate the error up the stack.\n\n> `unix_stream_listen()` always tries to `unlink()` the socket-path before\n> calling `bind()`.  If there is an existing server/daemon already bound\n> and listening on that socket-path, our `unlink()` would have the effect\n> of disassociating the existing server's bound-socket-fd from the socket-path\n> without notifying the existing server.  The existing server could continue\n> to service existing connections (accepted-socket-fd's), but would not\n> receive any futher new connections (since clients rendezvous via the\n> socket-path).  The existing server would effectively be offline but yet\n> appear to be active.\n\nThe trouble here is that one cannot tell if the existing file is active,\nand you are orphaning an existing server, or if there is leftover cruft\nfrom an exited server that did not clean up after itself (you will get\nEADDRINUSE either way).\n\nHandling those cases (and especially doing so in a non-racy way) is\nprobably outside the scope of unix_stream_listen(), but it makes sense\nfor this to be an option. And it looks like you even made it so here,\nso unix_stream_listen() could just become a wrapper that sets the\noption. Or since there is only one caller in the whole code-base,\nperhaps it could just learn to pass the option struct. :)\n\nLikewise for the no-chdir option added in the follow-on patch.\n\n> Furthermore, `unix_stream_listen()` creates an opportunity for a brief\n> race condition for connecting clients if they try to connect in the\n> interval between the forced `unlink()` and the subsequent `bind()` (which\n> recreates the socket-path that is bound to a new socket-fd in the current\n> process).\n\nI'll be curious to see how you do this atomically. From my skim of patch\n10, you will connect to see if it's active, and unlink if it's not. But\nthen two simultaneous new processes could both see an inactive one and\nrace to forcefully create the new one. One of them will lose and be\norphaned with a socket that has no filesystem name.\n\nThere might be a solution using link() to have an atomic winner, but it\ngets tricky around unlinking the old name out of the way. You might need\na separate dot-lock to make sure only one process does the\nunlink-and-create process at a time.\n\n-Peff\n"},{"id":"414272","messageId":"8735z46dn8.fsf@evledraar.gmail.com","threadId":"54978","inReplyTo":"X/75mip8tYO/mmSW@coredump.intra.peff.net","subject":"Re: [PATCH 00/10] [RFC] Simple IPC Mechanism","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-01-13T15:48:59Z","receivedAt":"2021-01-13T15:49:44Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Jan 13 2021, Jeff King wrote:\n\n> On Tue, Jan 12, 2021 at 06:25:20PM -0500, Jeff Hostetler wrote:\n>\n>> On the Unix side, the socket is created inside the .git directory\n>> by the daemon.  Potential clients would have to have access to the\n>> working directory and the .git directory to connect to the socket,\n>> so in normal circumstances they would be able to read everything in\n>> the WD anyway.  So again, I don't think it is a problem.\n>\n> Just thinking out loud, here are two potential issues with putting it in\n> .git that we may have to deal with later:\n>\n>   - fsmonitor is conceptually a read-only thing (i.e., it would speed up\n>     \"git status\", etc). And not knowing much about how it will work, I'd\n>     guess that is carried through (i.e., even though you may open the\n>     socket R/W so that you can write requests and read them back, there\n>     is no operation you can request that will overwrite data). But the\n>     running user may not have write access to .git.\n>\n>     As long as we cleanly bail to the non-fsmonitor code paths, I don't\n>     think it's the end of the world. Those read-only users just won't\n>     get to use the speedup (and it may even be desirable). They may\n>     complain, but it is open source so the onus is on them to improve\n>     it. You will not have made anything worse. :)\n>\n>   - repositories may be on network filesystems that do not support unix\n>     sockets.\n>\n> So it would be nice if there was some way to specify an alternate path\n> to be used for the socket. Possibly one or both of:\n>\n>   - a config option to give a root path for sockets, where Git would\n>     then canonicalize the $GIT_DIR name and use $root/$GIT_DIR for the\n>     socket. That solves the problem for a given user once for all repos.\n>\n>   - a config option to say \"use this path for the socket\". This would be\n>     per-repo, but is more flexible and possibly less confusing.\n>\n> One final note: on some systems[1] the permissions on the socket file\n> itself are ignored. The safe way to protect it is to make sure the\n> permissions on the surrounding directory are what you want. See\n> credential-cache's init_socket_directory() for an example.\n>\n> -Peff\n>\n> [1] Sorry, I don't remember which systems. This is one of those random\n>     bits of Unix lore I've carried around for 20 years, and it's\n>     entirely possible it is simply obsolete at this point.\n\nAccording to StackExchange lore this seems to have been the case with\n4.2 BSD & maybe something obscure like HP/UX:\nhttps://unix.stackexchange.com/questions/83032/which-systems-do-not-honor-socket-read-write-permissions\n\nI'd say it's probably safe to ignore this as a concern for new features\nin git in general, and certainly for something like a thing intended for\na watchman-like program which users are likely to only run on modern\nOS's.\n"},{"id":"414292","messageId":"CAPx1GvcpmLJzSpO9J4u8t+ZZ+MdZuVuJ2gPHP5ei0xng3cV9oQ@mail.gmail.com","threadId":"54978","inReplyTo":"X/7+YU16XrPFDYlN@coredump.intra.peff.net","subject":"Re: [PATCH 07/10] unix-socket: create gentle version of unix_stream_listen()","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2021-01-14T01:19:45Z","receivedAt":"2021-01-14T01:43:42Z","isPatch":true,"sender":{"key":"chris.torek@gmail.com","avatar":"https://avatars.githubusercontent.com/u/16826774?v=4"},"body":"I had saved this to comment on, but Peff beat me to it :-)\n\nOn Wed, Jan 13, 2021 at 6:07 AM Jeff King <peff@peff.net> wrote:\n> There might be a solution using link() to have an atomic winner, but it\n> gets tricky around unlinking the old name out of the way.\n\nYou definitely should be able to do this atomically with link(), but\nthe cleanup is indeed messy, and there's already existing locking\ncode, so it's probably better to press that into service here.\n\nChris\n"},{"id":"415262","messageId":"04f62d1a-42bb-cc45-0e05-a00bfd5eceba@jeffhostetler.com","threadId":"54978","inReplyTo":"X/71qByO5jSceIFn@coredump.intra.peff.net","subject":"Re: [PATCH 01/10] pkt-line: use stack rather than static buffer in packet_write_gently()","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-01-25T19:34:27Z","receivedAt":"2021-01-26T01:53:44Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/13/21 8:29 AM, Jeff King wrote:\n> On Tue, Jan 12, 2021 at 03:31:23PM +0000, Jeff Hostetler via GitGitGadget wrote:\n> \n>> Teach packet_write_gently() to use a stack buffer rather than a static\n>> buffer when composing the packet line message.  This helps get us ready\n>> for threaded operations.\n> \n> Sounds like a good goal, but...\n> \n>>   static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n>>   {\n>> -\tstatic char packet_write_buffer[LARGE_PACKET_MAX];\n>> +\tchar packet_write_buffer[LARGE_PACKET_MAX];\n>>   \tsize_t packet_size;\n> \n> 64k is awfully big for the stack, especially if you are thinking about\n> having threads. I know we've run into issues around that size before\n> (though I don't offhand recall whether there was any recursion\n> involved).\n> \n> We might need to use thread-local storage here. Heap would also\n> obviously work, but I don't think we'd want a new allocation per write\n> (or maybe it wouldn't matter; we're making a syscall, so a malloc() may\n> not be that big a deal in terms of performance).\n> \n> -Peff\n> \n\nGood point.\n\nI'll look at the callers and see if I can do something safer.\n\nJeeff\n"},{"id":"415790","messageId":"4c6766d41834da9508142d1f420a741dc550806b.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 01/14] ci/install-depends: attempt to fix \"brew cask\" stuff","fromName":"Junio C Hamano via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:34Z","receivedAt":"2021-02-01T19:47:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nWe run \"git pull\" against \"$cask_repo\"; clarify that we are\nexpecting not to have any of our own modifications and running \"git\npull\" to merely update, by passing \"--ff-only\" on the command line.\n\nAlso, the \"brew cask install\" command line triggers an error message\nthat says:\n\n    Error: Calling brew cask install is disabled! Use brew install\n    [--cask] instead.\n\nIn addition, \"brew install caskroom/cask/perforce\" step triggers an\nerror that says:\n\n    Error: caskroom/cask was moved. Tap homebrew/cask instead.\n\nAttempt to see if blindly following the suggestion in these error\nmessages gets us into a better shape.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n ci/install-dependencies.sh | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh\nindex 0229a77f7d2..0b1184e04ad 100755\n--- a/ci/install-dependencies.sh\n+++ b/ci/install-dependencies.sh\n@@ -44,13 +44,13 @@ osx-clang|osx-gcc)\n \ttest -z \"$BREW_INSTALL_PACKAGES\" ||\n \tbrew install $BREW_INSTALL_PACKAGES\n \tbrew link --force gettext\n-\tbrew cask install --no-quarantine perforce || {\n+\tbrew install --cask --no-quarantine perforce || {\n \t\t# Update the definitions and try again\n \t\tcask_repo=\"$(brew --repository)\"/Library/Taps/homebrew/homebrew-cask &&\n-\t\tgit -C \"$cask_repo\" pull --no-stat &&\n-\t\tbrew cask install --no-quarantine perforce\n+\t\tgit -C \"$cask_repo\" pull --no-stat --ff-only &&\n+\t\tbrew install --cask --no-quarantine perforce\n \t} ||\n-\tbrew install caskroom/cask/perforce\n+\tbrew install homebrew/cask/perforce\n \tcase \"$jobname\" in\n \tosx-gcc)\n \t\tbrew install gcc@9\n-- \ngitgitgadget\n\n"},{"id":"415791","messageId":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.git.1610465492.gitgitgadget@gmail.com","subject":"[PATCH v2 00/14] Simple IPC Mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:33Z","receivedAt":"2021-02-01T19:47:39Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"Here is version 2 of my \"Simple IPC\" series and addresses the following\nreview comments:\n\n[1] Redo packet_write_gently() to take a scratch buffer argument and fixup\ncallers to avoid potential thread-stack problems caused by a very large\nstack buffer when used multi-threaded callers. This turned out to be a\nlittle more involved than anticipated because the pkt-line code doesn't know\nabout its thread state nor an opportunity to initialize thread-state.\n\n[2] Deleted the unix_stream_socket() helper function and inline it in the\nfew callers and then let those call sites decide whether to call die() or\nnot.\n\n[3] Refactor unix_stream_listen() to take an \"options\" structure and to\nincorporate the changes I described in my earlier\nunix_stream_listen_gently().\n\n[4] Update unix_stream_connect() to return errors rather than calling die().\n\n[5] Update the simple-ipc server startup to detect dead and/or in-use Unix\ndomain sockets and/or create a new socket in a race-friendly way. I now use\na variation of the atomic lock-rename-trick when creating the socket\n(details are in a large comment in the code).\n\nJeff Hostetler (10):\n  pkt-line: promote static buffer in packet_write_gently() to callers\n  pkt-line: add write_packetized_from_buf2() that takes scratch buffer\n  simple-ipc: design documentation for new IPC mechanism\n  simple-ipc: add win32 implementation\n  simple-ipc: add t/helper/test-simple-ipc and t0052\n  unix-socket: elimiate static unix_stream_socket() helper function\n  unix-socket: add options to unix_stream_listen()\n  unix-socket: add no-chdir option to unix_stream_listen()\n  unix-socket: do not call die in unix_stream_connect()\n  simple-ipc: add Unix domain socket implementation\n\nJohannes Schindelin (3):\n  pkt-line: optionally skip the flush packet in\n    write_packetized_from_buf()\n  pkt-line: (optionally) libify the packet readers\n  pkt-line: accept additional options in read_packetized_to_strbuf()\n\nJunio C Hamano (1):\n  ci/install-depends: attempt to fix \"brew cask\" stuff\n\n Documentation/technical/api-simple-ipc.txt |   34 +\n Makefile                                   |    8 +\n builtin/credential-cache--daemon.c         |    3 +-\n ci/install-dependencies.sh                 |    8 +-\n compat/simple-ipc/ipc-shared.c             |   28 +\n compat/simple-ipc/ipc-unix-socket.c        | 1127 ++++++++++++++++++++\n compat/simple-ipc/ipc-win32.c              |  751 +++++++++++++\n config.mak.uname                           |    2 +\n contrib/buildsystems/CMakeLists.txt        |    6 +\n convert.c                                  |    4 +-\n pkt-line.c                                 |   70 +-\n pkt-line.h                                 |   26 +-\n simple-ipc.h                               |  230 ++++\n t/helper/test-simple-ipc.c                 |  485 +++++++++\n t/helper/test-tool.c                       |    1 +\n t/helper/test-tool.h                       |    1 +\n t/t0052-simple-ipc.sh                      |  129 +++\n unix-socket.c                              |   67 +-\n unix-socket.h                              |   16 +-\n 19 files changed, 2949 insertions(+), 47 deletions(-)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\n\nbase-commit: 71ca53e8125e36efbda17293c50027d31681a41f\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/766\n\nRange-diff vs v1:\n\n  1:  1155a45cf64 <  -:  ----------- pkt-line: use stack rather than static buffer in packet_write_gently()\n  -:  ----------- >  1:  4c6766d4183 ci/install-depends: attempt to fix \"brew cask\" stuff\n  -:  ----------- >  2:  3b03a8ff7a7 pkt-line: promote static buffer in packet_write_gently() to callers\n  -:  ----------- >  3:  e671894b4c0 pkt-line: add write_packetized_from_buf2() that takes scratch buffer\n  3:  edf5ac95d66 !  4:  0832f7d324d pkt-line: optionally skip the flush packet in write_packetized_from_buf()\n     @@ Commit message\n          packets before a final flush packet, so let's extend this function to\n          prepare for that scenario.\n      \n     +    Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n       ## convert.c ##\n     @@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out)\n      -int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n      +int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,\n      +\t\t\t      int flush_at_end)\n     + {\n     + \tstatic struct packet_scratch_space scratch;\n     + \n     +-\treturn write_packetized_from_buf2(src_in, len, fd_out, &scratch);\n     ++\treturn write_packetized_from_buf2(src_in, len, fd_out,\n     ++\t\t\t\t\t  flush_at_end, &scratch);\n     + }\n     + \n     + int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n     ++\t\t\t       int flush_at_end,\n     + \t\t\t       struct packet_scratch_space *scratch)\n       {\n       \tint err = 0;\n     - \tsize_t bytes_written = 0;\n     -@@ pkt-line.c: int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n     - \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n     +@@ pkt-line.c: int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n     + \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write, scratch);\n       \t\tbytes_written += bytes_to_write;\n       \t}\n      -\tif (!err)\n     @@ pkt-line.h: void packet_buf_write_len(struct strbuf *buf, const char *data, size\n      -int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n      +int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,\n      +\t\t\t      int flush_at_end);\n     + int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n     ++\t\t\t       int flush_at_end,\n     + \t\t\t       struct packet_scratch_space *scratch);\n       \n       /*\n     -  * Read a packetized line into the buffer, which must be at least size bytes\n  2:  b7d678bc918 !  5:  43bc4a26b79 pkt-line: (optionally) libify the packet readers\n     @@ pkt-line.c: enum packet_read_status packet_read_with_status(int fd, char **src_b\n       \t\t*pktlen = -1;\n      \n       ## pkt-line.h ##\n     -@@ pkt-line.h: int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n     +@@ pkt-line.h: int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n        *\n        * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n        * ERR packet.\n  4:  2f399ac107c =  6:  6a389a35335 pkt-line: accept additional options in read_packetized_to_strbuf()\n  5:  7064c5e9ffa !  7:  a7275b4bdc2 simple-ipc: design documentation for new IPC mechanism\n     @@ Documentation/technical/api-simple-ipc.txt (new)\n      +\n      +This IPC mechanism differs from the existing `sub-process.c` model\n      +(Documentation/technical/long-running-process-protocol.txt) and used\n     -+by applications like Git-LFS because the server is assumed to be very\n     -+long running system service.  In contrast, a \"sub-process model process\"\n     -+is started with the foreground process and exits when the foreground\n     -+process terminates.  How the server is started is also outside the\n     -+scope of the IPC mechanism.\n     ++by applications like Git-LFS.  In the simple-ipc model the server is\n     ++assumed to be a very long-running system service.  In contrast, in the\n     ++LFS-style sub-process model the helper is started with the foreground\n     ++process and exits when the foreground process terminates.\n     ++\n     ++How the simple-ipc server is started is also outside the scope of the\n     ++IPC mechanism.  For example, the server might be started during\n     ++maintenance operations.\n      +\n      +The IPC protocol consists of a single request message from the client and\n      +an optional request message from the server.  For simplicity, pkt-line\n  6:  9e27c07d785 !  8:  388366913d4 simple-ipc: add win32 implementation\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +enum ipc_active_state ipc_client_try_connect(\n      +\tconst char *path,\n      +\tconst struct ipc_client_connect_options *options,\n     -+\tint *pfd)\n     ++\tstruct ipc_client_connection **p_connection)\n      +{\n      +\twchar_t wpath[MAX_PATH];\n      +\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n     ++\tint fd = -1;\n      +\n     -+\t*pfd = -1;\n     ++\t*p_connection = NULL;\n      +\n      +\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n      +\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +\t\tstate = IPC_STATE__INVALID_PATH;\n      +\telse\n      +\t\tstate = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,\n     -+\t\t\t\t\t  options, pfd);\n     ++\t\t\t\t\t  options, &fd);\n      +\n      +\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n      +\t\t\t   (intmax_t)state);\n      +\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n     ++\n     ++\tif (state == IPC_STATE__LISTENING) {\n     ++\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n     ++\t\t(*p_connection)->fd = fd;\n     ++\t}\n     ++\n      +\treturn state;\n      +}\n      +\n     -+int ipc_client_send_command_to_fd(int fd, const char *message,\n     -+\t\t\t\t  struct strbuf *answer)\n     ++void ipc_client_close_connection(struct ipc_client_connection *connection)\n     ++{\n     ++\tif (!connection)\n     ++\t\treturn;\n     ++\n     ++\tif (connection->fd != -1)\n     ++\t\tclose(connection->fd);\n     ++\n     ++\tfree(connection);\n     ++}\n     ++\n     ++int ipc_client_send_command_to_connection(\n     ++\tstruct ipc_client_connection *connection,\n     ++\tconst char *message, struct strbuf *answer)\n      +{\n      +\tint ret = 0;\n      +\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +\n      +\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n      +\n     -+\tif (write_packetized_from_buf(message, strlen(message), fd, 1) < 0) {\n     ++\tif (write_packetized_from_buf2(message, strlen(message),\n     ++\t\t\t\t       connection->fd, 1,\n     ++\t\t\t\t       &connection->scratch_write_buffer) < 0) {\n      +\t\tret = error(_(\"could not send IPC command\"));\n      +\t\tgoto done;\n      +\t}\n      +\n     -+\tFlushFileBuffers((HANDLE)_get_osfhandle(fd));\n     ++\tFlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));\n      +\n     -+\tif (read_packetized_to_strbuf(fd, answer, PACKET_READ_NEVER_DIE) < 0) {\n     ++\tif (read_packetized_to_strbuf(connection->fd, answer,\n     ++\t\t\t\t      PACKET_READ_NEVER_DIE) < 0) {\n      +\t\tret = error(_(\"could not read IPC response\"));\n      +\t\tgoto done;\n      +\t}\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +\t\t\t    const struct ipc_client_connect_options *options,\n      +\t\t\t    const char *message, struct strbuf *response)\n      +{\n     -+\tint fd;\n      +\tint ret = -1;\n      +\tenum ipc_active_state state;\n     ++\tstruct ipc_client_connection *connection = NULL;\n      +\n     -+\tstate = ipc_client_try_connect(path, options, &fd);\n     ++\tstate = ipc_client_try_connect(path, options, &connection);\n      +\n      +\tif (state != IPC_STATE__LISTENING)\n      +\t\treturn ret;\n      +\n     -+\tret = ipc_client_send_command_to_fd(fd, message, response);\n     -+\tclose(fd);\n     ++\tret = ipc_client_send_command_to_connection(connection, message, response);\n     ++\n     ++\tipc_client_close_connection(connection);\n     ++\n      +\treturn ret;\n      +}\n      +\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +\tstruct ipc_server_data *server_data;\n      +\tpthread_t pthread_id;\n      +\tHANDLE hPipe;\n     ++\tstruct packet_scratch_space scratch_write_buffer;\n      +};\n      +\n      +/*\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n      +\t\t       const char *response, size_t response_len)\n      +{\n     ++\tstruct packet_scratch_space *scratch =\n     ++\t\t&reply_data->server_thread_data->scratch_write_buffer;\n     ++\n      +\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n      +\t\tBUG(\"reply_cb called with wrong instance data\");\n      +\n     -+\treturn write_packetized_from_buf(response, response_len,\n     -+\t\t\t\t\t reply_data->fd, 0);\n     ++\treturn write_packetized_from_buf2(response, response_len,\n     ++\t\t\t\t\t  reply_data->fd, 0, scratch);\n      +}\n      +\n      +/*\n     @@ simple-ipc.h (new)\n      +#endif\n      +\n      +#ifdef SUPPORTS_SIMPLE_IPC\n     ++#include \"pkt-line.h\"\n      +\n      +/*\n      + * Simple IPC Client Side API.\n     @@ simple-ipc.h (new)\n      + */\n      +enum ipc_active_state ipc_get_active_state(const char *path);\n      +\n     ++struct ipc_client_connection {\n     ++\tint fd;\n     ++\tstruct packet_scratch_space scratch_write_buffer;\n     ++};\n     ++\n      +/*\n      + * Try to connect to the daemon on the named pipe or socket.\n      + *\n     -+ * Returns IPC_STATE__LISTENING (and an fd) when connected.\n     ++ * Returns IPC_STATE__LISTENING and a connection handle.\n      + *\n      + * Otherwise, returns info to help decide whether to retry or to\n      + * spawn/respawn the server.\n     @@ simple-ipc.h (new)\n      +enum ipc_active_state ipc_client_try_connect(\n      +\tconst char *path,\n      +\tconst struct ipc_client_connect_options *options,\n     -+\tint *pfd);\n     ++\tstruct ipc_client_connection **p_connection);\n     ++\n     ++void ipc_client_close_connection(struct ipc_client_connection *connection);\n      +\n      +/*\n      + * Used by the client to synchronously send and receive a message with\n     -+ * the server on the provided fd.\n     ++ * the server on the provided client connection.\n      + *\n      + * Returns 0 when successful.\n      + *\n      + * Calls error() and returns non-zero otherwise.\n      + */\n     -+int ipc_client_send_command_to_fd(int fd, const char *message,\n     -+\t\t\t\t  struct strbuf *answer);\n     ++int ipc_client_send_command_to_connection(\n     ++\tstruct ipc_client_connection *connection,\n     ++\tconst char *message, struct strbuf *answer);\n      +\n      +/*\n      + * Used by the client to synchronously connect and send and receive a\n  9:  69969c2b8d3 =  9:  f0bebf1cdb3 simple-ipc: add t/helper/test-simple-ipc and t0052\n  -:  ----------- > 10:  f5d5445cf42 unix-socket: elimiate static unix_stream_socket() helper function\n  7:  96268351ac6 ! 11:  7a6a69dfc20 unix-socket: create gentle version of unix_stream_listen()\n     @@ Metadata\n      Author: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## Commit message ##\n     -    unix-socket: create gentle version of unix_stream_listen()\n     +    unix-socket: add options to unix_stream_listen()\n      \n     -    Create a gentle version of `unix_stream_listen()`.  This version does\n     -    not call `die()` if a socket-fd cannot be created and does not assume\n     -    that it is safe to `unlink()` an existing socket-inode.\n     +    Update `unix_stream_listen()` to take an options structure to override\n     +    default behaviors.  This includes the size of the `listen()` backlog\n     +    and whether it should always unlink the socket file before trying to\n     +    create a new one.  Also eliminate calls to `die()` if it cannot create\n     +    a socket.\n      \n     -    `unix_stream_listen()` uses `unix_stream_socket()` helper function to\n     -    create the socket-fd.  Avoid that helper because it calls `die()` on\n     -    errors.\n     -\n     -    `unix_stream_listen()` always tries to `unlink()` the socket-path before\n     -    calling `bind()`.  If there is an existing server/daemon already bound\n     -    and listening on that socket-path, our `unlink()` would have the effect\n     -    of disassociating the existing server's bound-socket-fd from the socket-path\n     -    without notifying the existing server.  The existing server could continue\n     -    to service existing connections (accepted-socket-fd's), but would not\n     -    receive any futher new connections (since clients rendezvous via the\n     -    socket-path).  The existing server would effectively be offline but yet\n     -    appear to be active.\n     +    Normally, `unix_stream_listen()` always tries to `unlink()` the\n     +    socket-path before calling `bind()`.  If there is an existing\n     +    server/daemon already bound and listening on that socket-path, our\n     +    `unlink()` would have the effect of disassociating the existing\n     +    server's bound-socket-fd from the socket-path without notifying the\n     +    existing server.  The existing server could continue to service\n     +    existing connections (accepted-socket-fd's), but would not receive any\n     +    futher new connections (since clients rendezvous via the socket-path).\n     +    The existing server would effectively be offline but yet appear to be\n     +    active.\n      \n          Furthermore, `unix_stream_listen()` creates an opportunity for a brief\n          race condition for connecting clients if they try to connect in the\n     @@ Commit message\n      \n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n     + ## builtin/credential-cache--daemon.c ##\n     +@@ builtin/credential-cache--daemon.c: static int serve_cache_loop(int fd)\n     + \n     + static void serve_cache(const char *socket_path, int debug)\n     + {\n     ++\tstruct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n     + \tint fd;\n     + \n     +-\tfd = unix_stream_listen(socket_path);\n     ++\tfd = unix_stream_listen(socket_path, &opts);\n     + \tif (fd < 0)\n     + \t\tdie_errno(\"unable to bind to '%s'\", socket_path);\n     + \n     +\n       ## unix-socket.c ##\n     -@@ unix-socket.c: int unix_stream_listen(const char *path)\n     - \terrno = saved_errno;\n     +@@ unix-socket.c: int unix_stream_connect(const char *path)\n       \treturn -1;\n       }\n     -+\n     -+int unix_stream_listen_gently(const char *path,\n     -+\t\t\t      const struct unix_stream_listen_opts *opts)\n     -+{\n     + \n     +-int unix_stream_listen(const char *path)\n     ++int unix_stream_listen(const char *path,\n     ++\t\t       const struct unix_stream_listen_opts *opts)\n     + {\n     +-\tint fd, saved_errno;\n      +\tint fd = -1;\n     -+\tint bind_successful = 0;\n      +\tint saved_errno;\n     -+\tstruct sockaddr_un sa;\n     -+\tstruct unix_sockaddr_context ctx;\n     -+\n     -+\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n     -+\t\tgoto fail;\n     ++\tint bind_successful = 0;\n     ++\tint backlog;\n     + \tstruct sockaddr_un sa;\n     + \tstruct unix_sockaddr_context ctx;\n     + \n     +-\tunlink(path);\n     +-\n     + \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n     + \t\treturn -1;\n      +\n     -+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n     -+\tif (fd < 0)\n     + \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n     + \tif (fd < 0)\n     +-\t\tdie_errno(\"unable to create socket\");\n      +\t\tgoto fail;\n      +\n      +\tif (opts->force_unlink_before_bind)\n      +\t\tunlink(path);\n     -+\n     -+\tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n     -+\t\tgoto fail;\n     + \n     + \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n     + \t\tgoto fail;\n      +\tbind_successful = 1;\n     -+\n     -+\tif (listen(fd, opts->listen_backlog_size) < 0)\n     -+\t\tgoto fail;\n     -+\n     -+\tunix_sockaddr_cleanup(&ctx);\n     -+\treturn fd;\n     -+\n     -+fail:\n     -+\tsaved_errno = errno;\n     -+\tunix_sockaddr_cleanup(&ctx);\n     -+\tclose(fd);\n     + \n     +-\tif (listen(fd, 5) < 0)\n     ++\tif (opts->listen_backlog_size > 0)\n     ++\t\tbacklog = opts->listen_backlog_size;\n     ++\telse\n     ++\t\tbacklog = 5;\n     ++\tif (listen(fd, backlog) < 0)\n     + \t\tgoto fail;\n     + \n     + \tunix_sockaddr_cleanup(&ctx);\n     +@@ unix-socket.c: int unix_stream_listen(const char *path)\n     + fail:\n     + \tsaved_errno = errno;\n     + \tunix_sockaddr_cleanup(&ctx);\n     +-\tclose(fd);\n     ++\tif (fd != -1)\n     ++\t\tclose(fd);\n      +\tif (bind_successful)\n      +\t\tunlink(path);\n     -+\terrno = saved_errno;\n     -+\treturn -1;\n     -+}\n     + \terrno = saved_errno;\n     + \treturn -1;\n     + }\n      \n       ## unix-socket.h ##\n      @@\n     - int unix_stream_connect(const char *path);\n     - int unix_stream_listen(const char *path);\n     + #ifndef UNIX_SOCKET_H\n     + #define UNIX_SOCKET_H\n       \n      +struct unix_stream_listen_opts {\n      +\tint listen_backlog_size;\n      +\tunsigned int force_unlink_before_bind:1;\n      +};\n      +\n     -+int unix_stream_listen_gently(const char *path,\n     -+\t\t\t      const struct unix_stream_listen_opts *opts);\n     ++#define UNIX_STREAM_LISTEN_OPTS_INIT \\\n     ++{ \\\n     ++\t.listen_backlog_size = 5, \\\n     ++\t.force_unlink_before_bind = 1, \\\n     ++}\n      +\n     + int unix_stream_connect(const char *path);\n     +-int unix_stream_listen(const char *path);\n     ++int unix_stream_listen(const char *path,\n     ++\t\t       const struct unix_stream_listen_opts *opts);\n     + \n       #endif /* UNIX_SOCKET_H */\n  8:  383a9755669 ! 12:  745b6d5fb74 unix-socket: add no-chdir option to unix_stream_listen_gently()\n     @@ Metadata\n      Author: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## Commit message ##\n     -    unix-socket: add no-chdir option to unix_stream_listen_gently()\n     +    unix-socket: add no-chdir option to unix_stream_listen()\n      \n          Calls to `chdir()` are dangerous in a multi-threaded context.  If\n          `unix_stream_listen()` is given a socket pathname that is too big to\n     @@ Commit message\n          Teach `unix_sockaddr_init()` to not allow calls to `chdir()` when flag\n          is set.\n      \n     -    Extend the public interface to `unix_stream_listen_gently()` to also\n     -    expose this new flag.\n     -\n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## unix-socket.c ##\n     @@ unix-socket.c: int unix_stream_connect(const char *path)\n       \n       \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n       \t\treturn -1;\n     -@@ unix-socket.c: int unix_stream_listen(const char *path)\n     - {\n     - \tint fd, saved_errno;\n     - \tstruct sockaddr_un sa;\n     --\tstruct unix_sockaddr_context ctx;\n     -+\tstruct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;\n     - \n     - \tunlink(path);\n     - \n     -@@ unix-socket.c: int unix_stream_listen_gently(const char *path,\n     +@@ unix-socket.c: int unix_stream_listen(const char *path,\n       \tint bind_successful = 0;\n     - \tint saved_errno;\n     + \tint backlog;\n       \tstruct sockaddr_un sa;\n      -\tstruct unix_sockaddr_context ctx;\n      +\tstruct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;\n     @@ unix-socket.c: int unix_stream_listen_gently(const char *path,\n      +\tctx.disallow_chdir = opts->disallow_chdir;\n       \n       \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n     - \t\tgoto fail;\n     + \t\treturn -1;\n      \n       ## unix-socket.h ##\n     -@@ unix-socket.h: int unix_stream_listen(const char *path);\n     +@@\n       struct unix_stream_listen_opts {\n       \tint listen_backlog_size;\n       \tunsigned int force_unlink_before_bind:1;\n      +\tunsigned int disallow_chdir:1;\n       };\n       \n     - int unix_stream_listen_gently(const char *path,\n     + #define UNIX_STREAM_LISTEN_OPTS_INIT \\\n     + { \\\n     + \t.listen_backlog_size = 5, \\\n     + \t.force_unlink_before_bind = 1, \\\n     ++\t.disallow_chdir = 0, \\\n     + }\n     + \n     + int unix_stream_connect(const char *path);\n  -:  ----------- > 13:  2cca15a10ec unix-socket: do not call die in unix_stream_connect()\n 10:  a1b15fb5cb0 ! 14:  72c1c209c38 simple-ipc: add Unix domain socket implementation\n     @@ Commit message\n      \n          Create Unix domain socket based implementation of \"simple-ipc\".\n      \n     +    A set of `ipc_client` routines implement a client library to connect\n     +    to an `ipc_server` over a Unix domain socket, send a simple request,\n     +    and receive a single response.  Clients use blocking IO on the socket.\n     +\n     +    A set of `ipc_server` routines implement a thread pool to listen for\n     +    and concurrently service client connections.\n     +\n     +    The server creates a new Unix domain socket at a known location.  If a\n     +    socket already exists with that name, the server tries to determine if\n     +    another server is already listening on the socket or if the socket is\n     +    dead.  If socket is busy, the server exits with an error rather than\n     +    stealing the socket.  If the socket is dead, the server creates a new\n     +    one and starts up.\n     +\n     +    If while running, the server detects that its socket has been stolen\n     +    by another server, it automatically exits.\n     +\n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## Makefile ##\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\tstruct ipc_client_connect_options options\n      +\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n      +\tstruct stat st;\n     -+\tint fd_test = -1;\n     ++\tstruct ipc_client_connection *connection_test = NULL;\n      +\n      +\toptions.wait_if_busy = 0;\n      +\toptions.wait_if_not_found = 0;\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t * at `path`, doesn't mean it that there is a server listening.\n      +\t * Ping it to be sure.\n      +\t */\n     -+\tstate = ipc_client_try_connect(path, &options, &fd_test);\n     -+\tclose(fd_test);\n     ++\tstate = ipc_client_try_connect(path, &options, &connection_test);\n     ++\tipc_client_close_connection(connection_test);\n      +\n      +\treturn state;\n      +}\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +enum ipc_active_state ipc_client_try_connect(\n      +\tconst char *path,\n      +\tconst struct ipc_client_connect_options *options,\n     -+\tint *pfd)\n     ++\tstruct ipc_client_connection **p_connection)\n      +{\n      +\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n     ++\tint fd = -1;\n      +\n     -+\t*pfd = -1;\n     ++\t*p_connection = NULL;\n      +\n      +\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n      +\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n      +\n      +\tstate = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,\n     -+\t\t\t\t  options, pfd);\n     ++\t\t\t\t  options, &fd);\n      +\n      +\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n      +\t\t\t   (intmax_t)state);\n      +\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n     ++\n     ++\tif (state == IPC_STATE__LISTENING) {\n     ++\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n     ++\t\t(*p_connection)->fd = fd;\n     ++\t}\n     ++\n      +\treturn state;\n      +}\n      +\n     -+int ipc_client_send_command_to_fd(int fd, const char *message,\n     -+\t\t\t\t  struct strbuf *answer)\n     ++void ipc_client_close_connection(struct ipc_client_connection *connection)\n     ++{\n     ++\tif (!connection)\n     ++\t\treturn;\n     ++\n     ++\tif (connection->fd != -1)\n     ++\t\tclose(connection->fd);\n     ++\n     ++\tfree(connection);\n     ++}\n     ++\n     ++int ipc_client_send_command_to_connection(\n     ++\tstruct ipc_client_connection *connection,\n     ++\tconst char *message, struct strbuf *answer)\n      +{\n      +\tint ret = 0;\n      +\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\n      +\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n      +\n     -+\tif (write_packetized_from_buf(message, strlen(message), fd, 1) < 0) {\n     ++\tif (write_packetized_from_buf2(message, strlen(message),\n     ++\t\t\t\t       connection->fd, 1,\n     ++\t\t\t\t       &connection->scratch_write_buffer) < 0) {\n      +\t\tret = error(_(\"could not send IPC command\"));\n      +\t\tgoto done;\n      +\t}\n      +\n     -+\tif (read_packetized_to_strbuf(fd, answer, PACKET_READ_NEVER_DIE) < 0) {\n     ++\tif (read_packetized_to_strbuf(connection->fd, answer,\n     ++\t\t\t\t      PACKET_READ_NEVER_DIE) < 0) {\n      +\t\tret = error(_(\"could not read IPC response\"));\n      +\t\tgoto done;\n      +\t}\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\t\t    const struct ipc_client_connect_options *options,\n      +\t\t\t    const char *message, struct strbuf *answer)\n      +{\n     -+\tint fd;\n      +\tint ret = -1;\n      +\tenum ipc_active_state state;\n     ++\tstruct ipc_client_connection *connection = NULL;\n      +\n     -+\tstate = ipc_client_try_connect(path, options, &fd);\n     ++\tstate = ipc_client_try_connect(path, options, &connection);\n      +\n      +\tif (state != IPC_STATE__LISTENING)\n      +\t\treturn ret;\n      +\n     -+\tret = ipc_client_send_command_to_fd(fd, message, answer);\n     -+\tclose(fd);\n     ++\tret = ipc_client_send_command_to_connection(connection, message, answer);\n     ++\n     ++\tipc_client_close_connection(connection);\n     ++\n      +\treturn ret;\n      +}\n      +\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\tstruct ipc_worker_thread_data *next_thread;\n      +\tstruct ipc_server_data *server_data;\n      +\tpthread_t pthread_id;\n     ++\tstruct packet_scratch_space scratch_write_buffer;\n      +};\n      +\n      +struct ipc_accept_thread_data {\n      +\tenum magic magic;\n      +\tstruct ipc_server_data *server_data;\n     ++\n      +\tint fd_listen;\n     -+\tino_t inode_listen;\n     ++\tstruct stat st_listen;\n     ++\n      +\tint fd_send_shutdown;\n      +\tint fd_wait_shutdown;\n      +\tpthread_t pthread_id;\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n      +\t\t       const char *response, size_t response_len)\n      +{\n     ++\tstruct packet_scratch_space *scratch =\n     ++\t\t&reply_data->worker_thread_data->scratch_write_buffer;\n     ++\n      +\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n      +\t\tBUG(\"reply_cb called with wrong instance data\");\n      +\n     -+\treturn write_packetized_from_buf(response, response_len,\n     -+\t\t\t\t\t reply_data->fd, 0);\n     ++\treturn write_packetized_from_buf2(response, response_len,\n     ++\t\t\t\t\t  reply_data->fd, 0, scratch);\n      +}\n      +\n      +/* A randomly chosen value. */\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +static int socket_was_stolen(struct ipc_accept_thread_data *accept_thread_data)\n      +{\n      +\tstruct stat st;\n     ++\tstruct stat *ref_st = &accept_thread_data->st_listen;\n      +\n      +\tif (lstat(accept_thread_data->server_data->buf_path.buf, &st) == -1)\n      +\t\treturn 1;\n      +\n     -+\tif (st.st_ino != accept_thread_data->inode_listen)\n     ++\tif (st.st_ino != ref_st->st_ino)\n      +\t\treturn 1;\n      +\n     ++\t/* We might also consider the creation time on some platforms. */\n     ++\n      +\treturn 0;\n      +}\n      +\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      + * open/connect using the \"socket-inode\" pathname.\n      + *\n      + * Unix domain sockets have a fundamental design flaw because the\n     -+ * \"socket-inode\" persists until the pathname is deleted; closing the listening\n     -+ * \"socket-fd\" only closes the socket handle/descriptor, it does not delete\n     -+ * the inode/pathname.\n     ++ * \"socket-inode\" persists until the pathname is deleted; closing the\n     ++ * listening \"socket-fd\" only closes the socket handle/descriptor, it\n     ++ * does not delete the inode/pathname.\n      + *\n      + * Well-behaving service daemons are expected to also delete the inode\n      + * before shutdown.  If a service crashes (or forgets) it can leave\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      + * inode *or* another service instance is already running.\n      + *\n      + * One possible solution is to blindly unlink the inode before\n     -+ * attempting to bind a new socket-fd (and thus create) a new\n     ++ * attempting to bind a new socket-fd and thus create a new\n      + * socket-inode.  Then `bind(2)` should always succeed.  However, if\n     -+ * there is an existing service instance, it would be orphaned --\n     -+ * it would still be listening on a socket-fd that is still bound\n     -+ * to an (unlinked) socket-inode, but that socket-inode is no longer\n     ++ * there is an existing service instance, it would be orphaned -- it\n     ++ * would still be listening on a socket-fd that is still bound to an\n     ++ * (unlinked) socket-inode, but that socket-inode is no longer\n      + * associated with the pathname.  New client connections will arrive\n     -+ * at our new socket-inode and not the existing server's.  (It is upto\n     -+ * the existing server to detect that its socket-inode has been\n     -+ * stolen and shutdown.)\n     ++ * at OUR new socket-inode -- rather than the existing server's\n     ++ * socket.  (I suppose it is up to the existing server to detect that\n     ++ * its socket-inode has been stolen and shutdown.)\n     ++ *\n     ++ * Another possible solution is to try to use the \".lock\" trick, but\n     ++ * bind() does not have a exclusive-create use bit like open() does,\n     ++ * so we cannot have multiple servers fighting/racing to create the\n     ++ * same file name without having losers lose without knowing that they\n     ++ * lost.\n     ++ *\n     ++ * We try to avoid such stealing and would rather fail to run than\n     ++ * steal an existing socket-inode (because we assume that the\n     ++ * existing server has more context and value to the clients than a\n     ++ * freshly started server).  However, if multiple servers are racing\n     ++ * to start, we don't care which one wins -- none of them have any\n     ++ * state information yet worth fighting for.\n     ++ *\n     ++ * Create a \"unique\" socket-inode (with our PID in it (and assume that\n     ++ * we can force-delete an existing socket with that name)).  Stat it\n     ++ * to get the inode number and ctime -- so that we can identify it as\n     ++ * the one we created.  Then use the atomic-rename trick to install it\n     ++ * in the real location.  (This will unlink an existing socket with\n     ++ * that pathname -- and thereby steal the real socket-inode from an\n     ++ * existing server.)\n      + *\n     -+ * Since this is rather obscure and infrequent, we try to \"gently\"\n     -+ * create the socket-inode without disturbing an existing service.\n     ++ * Elsewhere, our thread will periodically poll the socket-inode to\n     ++ * see if someone else steals ours.\n      + */\n      +static int create_listener_socket(const char *path,\n     -+\t\t\t\t  const struct ipc_server_opts *ipc_opts)\n     ++\t\t\t\t  const struct ipc_server_opts *ipc_opts,\n     ++\t\t\t\t  struct stat *st_socket)\n      +{\n     ++\tstruct stat st;\n     ++\tstruct strbuf buf_uniq = STRBUF_INIT;\n      +\tint fd_listen;\n     -+\tint fd_client;\n     -+\tstruct unix_stream_listen_opts uslg_opts = {\n     -+\t\t.listen_backlog_size = LISTEN_BACKLOG,\n     -+\t\t.force_unlink_before_bind = 0,\n     -+\t\t.disallow_chdir = ipc_opts->uds_disallow_chdir\n     -+\t};\n     -+\n     -+\ttrace2_data_string(\"ipc-server\", NULL, \"try-listen-gently\", path);\n     -+\n     -+\t/*\n     -+\t * Assume socket-inode does not exist and try to (gently)\n     -+\t * create a new socket-inode on disk at pathname and bind\n     -+\t * socket-fd to it.\n     -+\t */\n     -+\tfd_listen = unix_stream_listen_gently(path, &uslg_opts);\n     -+\tif (fd_listen >= 0)\n     -+\t\treturn fd_listen;\n     ++\tstruct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n      +\n     -+\tif (errno != EADDRINUSE)\n     -+\t\treturn error_errno(_(\"could not create socket '%s'\"),\n     -+\t\t\t\t   path);\n     -+\n     -+\ttrace2_data_string(\"ipc-server\", NULL, \"try-detect-server\", path);\n     -+\n     -+\t/*\n     -+\t * A socket-inode at pathname exists on disk, but we don't\n     -+\t * know if it a server is using it or if it is a stale inode.\n     -+\t *\n     -+\t * poke it with a trivial connection to try to find out.\n     -+\t */\n     -+\tfd_client = unix_stream_connect(path);\n     -+\tif (fd_client >= 0) {\n     ++\tif (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {\n     ++\t\tint fd_client;\n      +\t\t/*\n     -+\t\t * An existing service process is alive and accepted our\n     -+\t\t * connection.\n     ++\t\t * A socket-inode at `path` exists on disk, but we\n     ++\t\t * don't know whether it belongs to an active server\n     ++\t\t * or if the last server died without cleaning up.\n     ++\t\t *\n     ++\t\t * Poke it with a trivial connection to try to find out.\n      +\t\t */\n     -+\t\tclose(fd_client);\n     -+\n     -+\t\t/*\n     -+\t\t * We cannot create a new socket-inode here, so we cannot\n     -+\t\t * startup a new server on this pathname.\n     -+\t\t */\n     -+\t\terrno = EADDRINUSE;\n     -+\t\treturn error_errno(_(\"socket already in use '%s'\"),\n     ++\t\ttrace2_data_string(\"ipc-server\", NULL, \"try-detect-server\",\n      +\t\t\t\t   path);\n     ++\t\tfd_client = unix_stream_connect(path);\n     ++\t\tif (fd_client >= 0) {\n     ++\t\t\tclose(fd_client);\n     ++\t\t\terrno = EADDRINUSE;\n     ++\t\t\treturn error_errno(_(\"socket already in use '%s'\"),\n     ++\t\t\t\t\t   path);\n     ++\t\t}\n      +\t}\n      +\n     -+\ttrace2_data_string(\"ipc-server\", NULL, \"try-listen-force\", path);\n     -+\n      +\t/*\n     -+\t * A socket-inode at pathname exists on disk, but we were not\n     -+\t * able to connect to it, so we believe that this is a stale\n     -+\t * socket-inode that a previous server forgot to delete.  Use\n     -+\t * the tradional solution: force unlink it and create a new\n     -+\t * one.\n     -+\t *\n     -+\t * TODO Note that it is possible that another server is\n     -+\t * listening, but is either just starting up and not yet\n     -+\t * responsive or is stuck somehow.  For now, I'm OK with\n     -+\t * stealing the socket-inode from it in this case.\n     ++\t * Create pathname to our \"unique\" socket and set it up for\n     ++\t * business.\n      +\t */\n     -+\tuslg_opts.force_unlink_before_bind = 1;\n     -+\tfd_listen = unix_stream_listen_gently(path, &uslg_opts);\n     -+\tif (fd_listen >= 0)\n     -+\t\treturn fd_listen;\n     -+\n     -+\treturn error_errno(_(\"could not force create socket '%s'\"), path);\n     -+}\n     -+\n     -+static int setup_listener_socket(const char *path, ino_t *inode,\n     -+\t\t\t\t const struct ipc_server_opts *ipc_opts)\n     -+{\n     -+\tint fd_listen;\n     -+\tstruct stat st;\n     -+\n     -+\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n     -+\tfd_listen = create_listener_socket(path, ipc_opts);\n     -+\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n     ++\tstrbuf_addf(&buf_uniq, \"%s.%d\", path, getpid());\n      +\n     -+\tif (fd_listen < 0)\n     -+\t\treturn fd_listen;\n     -+\n     -+\t/*\n     -+\t * We just bound a socket (descriptor) to a newly created unix\n     -+\t * domain socket in the filesystem.  Capture the inode number\n     -+\t * so we can later detect if/when someone else force-creates a\n     -+\t * new socket and effectively steals the path from us.  (Which\n     -+\t * would leave us listening to a socket that no client could\n     -+\t * reach.)\n     -+\t */\n     -+\tif (lstat(path, &st) < 0) {\n     ++\tuslg_opts.listen_backlog_size = LISTEN_BACKLOG;\n     ++\tuslg_opts.force_unlink_before_bind = 1;\n     ++\tuslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;\n     ++\tfd_listen = unix_stream_listen(buf_uniq.buf, &uslg_opts);\n     ++\tif (fd_listen < 0) {\n      +\t\tint saved_errno = errno;\n     ++\t\terror_errno(_(\"could not create listener socket '%s'\"),\n     ++\t\t\t    buf_uniq.buf);\n     ++\t\tstrbuf_release(&buf_uniq);\n     ++\t\terrno = saved_errno;\n     ++\t\treturn -1;\n     ++\t}\n      +\n     ++\tif (lstat(buf_uniq.buf, st_socket)) {\n     ++\t\tint saved_errno = errno;\n     ++\t\terror_errno(_(\"could not stat listener socket '%s'\"),\n     ++\t\t\t    buf_uniq.buf);\n      +\t\tclose(fd_listen);\n     -+\t\tunlink(path);\n     -+\n     ++\t\tunlink(buf_uniq.buf);\n     ++\t\tstrbuf_release(&buf_uniq);\n      +\t\terrno = saved_errno;\n     -+\t\treturn error_errno(_(\"could not lstat listener socket '%s'\"),\n     -+\t\t\t\t   path);\n     ++\t\treturn -1;\n      +\t}\n      +\n      +\tif (set_socket_blocking_flag(fd_listen, 1)) {\n      +\t\tint saved_errno = errno;\n     -+\n     ++\t\terror_errno(_(\"could not set listener socket nonblocking '%s'\"),\n     ++\t\t\t    buf_uniq.buf);\n      +\t\tclose(fd_listen);\n     -+\t\tunlink(path);\n     ++\t\tunlink(buf_uniq.buf);\n     ++\t\tstrbuf_release(&buf_uniq);\n     ++\t\terrno = saved_errno;\n     ++\t\treturn -1;\n     ++\t}\n      +\n     ++\t/*\n     ++\t * Install it as the \"real\" socket so that clients will starting\n     ++\t * connecting to our socket.\n     ++\t */\n     ++\tif (rename(buf_uniq.buf, path)) {\n     ++\t\tint saved_errno = errno;\n     ++\t\terror_errno(_(\"could not create listener socket '%s'\"), path);\n     ++\t\tclose(fd_listen);\n     ++\t\tunlink(buf_uniq.buf);\n     ++\t\tstrbuf_release(&buf_uniq);\n      +\t\terrno = saved_errno;\n     -+\t\treturn error_errno(_(\"making listener socket nonblocking '%s'\"),\n     -+\t\t\t\t   path);\n     ++\t\treturn -1;\n      +\t}\n      +\n     -+\t*inode = st.st_ino;\n     ++\tstrbuf_release(&buf_uniq);\n     ++\ttrace2_data_string(\"ipc-server\", NULL, \"try-listen\", path);\n     ++\treturn fd_listen;\n     ++}\n     ++\n     ++static int setup_listener_socket(const char *path, struct stat *st_socket,\n     ++\t\t\t\t const struct ipc_server_opts *ipc_opts)\n     ++{\n     ++\tint fd_listen;\n     ++\n     ++\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n     ++\tfd_listen = create_listener_socket(path, ipc_opts, st_socket);\n     ++\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n      +\n      +\treturn fd_listen;\n      +}\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +{\n      +\tstruct ipc_server_data *server_data;\n      +\tint fd_listen;\n     -+\tino_t inode_listen;\n     ++\tstruct stat st_listen;\n      +\tint sv[2];\n      +\tint k;\n      +\tint nr_threads = opts->nr_threads;\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\t\t\t   path);\n      +\t}\n      +\n     -+\tfd_listen = setup_listener_socket(path, &inode_listen, opts);\n     ++\tfd_listen = setup_listener_socket(path, &st_listen, opts);\n      +\tif (fd_listen < 0) {\n      +\t\tint saved_errno = errno;\n      +\t\tclose(sv[0]);\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\tserver_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;\n      +\tserver_data->accept_thread->server_data = server_data;\n      +\tserver_data->accept_thread->fd_listen = fd_listen;\n     -+\tserver_data->accept_thread->inode_listen = inode_listen;\n     ++\tserver_data->accept_thread->st_listen = st_listen;\n      +\tserver_data->accept_thread->fd_send_shutdown = sv[0];\n      +\tserver_data->accept_thread->fd_wait_shutdown = sv[1];\n      +\n\n-- \ngitgitgadget\n"},{"id":"415792","messageId":"0832f7d324da643d7a480111d693ff5559c2b7a7.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 04/14] pkt-line: optionally skip the flush packet in write_packetized_from_buf()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:37Z","receivedAt":"2021-02-01T19:47:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis function currently has only one caller: `apply_multi_file_filter()`\nin `convert.c`. That caller wants a flush packet to be written after\nwriting the payload.\n\nHowever, we are about to introduce a user that wants to write many\npackets before a final flush packet, so let's extend this function to\nprepare for that scenario.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n convert.c  | 2 +-\n pkt-line.c | 9 ++++++---\n pkt-line.h | 4 +++-\n 3 files changed, 10 insertions(+), 5 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07c..3f396a9b288 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -886,7 +886,7 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \tif (fd >= 0)\n \t\terr = write_packetized_from_fd(fd, process->in);\n \telse\n-\t\terr = write_packetized_from_buf(src, len, process->in);\n+\t\terr = write_packetized_from_buf(src, len, process->in, 1);\n \tif (err)\n \t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 5d86354cbeb..d91a1deda95 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -275,14 +275,17 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n \treturn err;\n }\n \n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n+int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,\n+\t\t\t      int flush_at_end)\n {\n \tstatic struct packet_scratch_space scratch;\n \n-\treturn write_packetized_from_buf2(src_in, len, fd_out, &scratch);\n+\treturn write_packetized_from_buf2(src_in, len, fd_out,\n+\t\t\t\t\t  flush_at_end, &scratch);\n }\n \n int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n+\t\t\t       int flush_at_end,\n \t\t\t       struct packet_scratch_space *scratch)\n {\n \tint err = 0;\n@@ -299,7 +302,7 @@ int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write, scratch);\n \t\tbytes_written += bytes_to_write;\n \t}\n-\tif (!err)\n+\tif (!err && flush_at_end)\n \t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\ndiff --git a/pkt-line.h b/pkt-line.h\nindex f1d5625e91f..ccf27549227 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -40,8 +40,10 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n int packet_flush_gently(int fd);\n int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n int write_packetized_from_fd(int fd_in, int fd_out);\n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n+int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,\n+\t\t\t      int flush_at_end);\n int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n+\t\t\t       int flush_at_end,\n \t\t\t       struct packet_scratch_space *scratch);\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"415793","messageId":"43bc4a26b79038a13d042f4538b467b1af94688b.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 05/14] pkt-line: (optionally) libify the packet readers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:38Z","receivedAt":"2021-02-01T19:48:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nSo far, the (possibly indirect) callers of `get_packet_data()` can ask\nthat function to return an error instead of `die()`ing upon end-of-file.\nHowever, random read errors will still cause the process to die.\n\nSo let's introduce an explicit option to tell the packet reader\nmachinery to please be nice and only return an error.\n\nThis change prepares pkt-line for use by long-running daemon processes.\nSuch processes should be able to serve multiple concurrent clients and\nand survive random IO errors.  If there is an error on one connection,\na daemon should be able to drop that connection and continue serving\nexisting and future connections.\n\nThis ability will be used by a Git-aware \"Internal FSMonitor\" feature\nin a later patch series.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n pkt-line.c | 19 +++++++++++++++++--\n pkt-line.h |  4 ++++\n 2 files changed, 21 insertions(+), 2 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex d91a1deda95..528493bca21 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -323,8 +323,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\t*src_size -= ret;\n \t} else {\n \t\tret = read_in_full(fd, dst, size);\n-\t\tif (ret < 0)\n+\t\tif (ret < 0) {\n+\t\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\t\treturn error_errno(_(\"read error\"));\n \t\t\tdie_errno(_(\"read error\"));\n+\t\t}\n \t}\n \n \t/* And complain if we didn't get enough bytes to satisfy the read. */\n@@ -332,6 +335,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n \t\t\treturn -1;\n \n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"the remote end hung up unexpectedly\"));\n \t\tdie(_(\"the remote end hung up unexpectedly\"));\n \t}\n \n@@ -360,6 +365,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \tlen = packet_length(linelen);\n \n \tif (len < 0) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length \"\n+\t\t\t\t       \"character: %.4s\"), linelen);\n \t\tdie(_(\"protocol error: bad line length character: %.4s\"), linelen);\n \t} else if (!len) {\n \t\tpacket_trace(\"0000\", 4, 0);\n@@ -374,12 +382,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \t\t*pktlen = 0;\n \t\treturn PACKET_READ_RESPONSE_END;\n \t} else if (len < 4) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n \t}\n \n \tlen -= 4;\n-\tif ((unsigned)len >= size)\n+\tif ((unsigned)len >= size) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n+\t}\n \n \tif (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) {\n \t\t*pktlen = -1;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex ccf27549227..7f31c892165 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -79,10 +79,14 @@ int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n  *\n  * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n  * ERR packet.\n+ *\n+ * With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except\n+ * an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect).\n  */\n #define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n #define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n #define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n+#define PACKET_READ_NEVER_DIE         (1u<<3)\n int packet_read(int fd, char **src_buffer, size_t *src_len, char\n \t\t*buffer, unsigned size, int options);\n \n-- \ngitgitgadget\n\n"},{"id":"415794","messageId":"6a389a3533512acedfa1769c64296c1e19b16221.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 06/14] pkt-line: accept additional options in read_packetized_to_strbuf()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:39Z","receivedAt":"2021-02-01T19:48:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `read_packetized_to_strbuf()` function reads packets into a strbuf\nuntil a flush packet has been received. So far, it has only one caller:\n`apply_multi_file_filter()` in `convert.c`. This caller really only\nneeds the `PACKET_READ_GENTLE_ON_EOF` option to be passed to\n`packet_read()` (which makes sense in the scenario where packets should\nbe read until a flush packet is received).\n\nWe are about to introduce a caller that wants to pass other options\nthrough to `packet_read()`, so let's extend the function signature\naccordingly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n convert.c  | 2 +-\n pkt-line.c | 4 ++--\n pkt-line.h | 6 +++++-\n 3 files changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 3f396a9b288..175c5cd51d5 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -903,7 +903,7 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \t\tif (err)\n \t\t\tgoto done;\n \n-\t\terr = read_packetized_to_strbuf(process->out, &nbuf) < 0;\n+\t\terr = read_packetized_to_strbuf(process->out, &nbuf, 0) < 0;\n \t\tif (err)\n \t\t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 528493bca21..f090fc56eef 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -461,7 +461,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)\n \treturn packet_read_line_generic(-1, src, src_len, dst_len);\n }\n \n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options)\n {\n \tint packet_len;\n \n@@ -477,7 +477,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n \t\t\t * that there is already room for the extra byte.\n \t\t\t */\n \t\t\tsb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,\n-\t\t\tPACKET_READ_GENTLE_ON_EOF);\n+\t\t\toptions | PACKET_READ_GENTLE_ON_EOF);\n \t\tif (packet_len <= 0)\n \t\t\tbreak;\n \t\tsb_out->len += packet_len;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 7f31c892165..150319a6f00 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -145,8 +145,12 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);\n \n /*\n  * Reads a stream of variable sized packets until a flush packet is detected.\n+ *\n+ * The options are augmented by PACKET_READ_GENTLE_ON_EOF and passed to\n+ * packet_read.\n  */\n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out,\n+\t\t\t\t  int options);\n \n /*\n  * Receive multiplexed output stream over git native protocol.\n-- \ngitgitgadget\n\n"},{"id":"415795","messageId":"2cca15a10ecec321731bf628e1317ff8d244dfd0.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 13/14] unix-socket: do not call die in unix_stream_connect()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:46Z","receivedAt":"2021-02-01T19:48:44Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nTeach `unix_stream_connect()` to return error rather than calling `die()`\nwhen a socket cannot be created.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 9726992f276..c7573df56a6 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -76,15 +76,17 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \n int unix_stream_connect(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1;\n+\tint saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n+\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to create socket\");\n+\t\tgoto fail;\n \n \tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n@@ -94,7 +96,8 @@ int unix_stream_connect(const char *path)\n fail:\n \tsaved_errno = errno;\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n+\tif (fd != -1)\n+\t\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n-- \ngitgitgadget\n\n"},{"id":"415796","messageId":"7a6a69dfc20c6ff190cb020931c46bf4d88bab59.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 11/14] unix-socket: add options to unix_stream_listen()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:44Z","receivedAt":"2021-02-01T19:48:44Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nUpdate `unix_stream_listen()` to take an options structure to override\ndefault behaviors.  This includes the size of the `listen()` backlog\nand whether it should always unlink the socket file before trying to\ncreate a new one.  Also eliminate calls to `die()` if it cannot create\na socket.\n\nNormally, `unix_stream_listen()` always tries to `unlink()` the\nsocket-path before calling `bind()`.  If there is an existing\nserver/daemon already bound and listening on that socket-path, our\n`unlink()` would have the effect of disassociating the existing\nserver's bound-socket-fd from the socket-path without notifying the\nexisting server.  The existing server could continue to service\nexisting connections (accepted-socket-fd's), but would not receive any\nfuther new connections (since clients rendezvous via the socket-path).\nThe existing server would effectively be offline but yet appear to be\nactive.\n\nFurthermore, `unix_stream_listen()` creates an opportunity for a brief\nrace condition for connecting clients if they try to connect in the\ninterval between the forced `unlink()` and the subsequent `bind()` (which\nrecreates the socket-path that is bound to a new socket-fd in the current\nprocess).\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache--daemon.c |  3 ++-\n unix-socket.c                      | 28 +++++++++++++++++++++-------\n unix-socket.h                      | 14 +++++++++++++-\n 3 files changed, 36 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c\nindex c61f123a3b8..4c6c89ab0de 100644\n--- a/builtin/credential-cache--daemon.c\n+++ b/builtin/credential-cache--daemon.c\n@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)\n \n static void serve_cache(const char *socket_path, int debug)\n {\n+\tstruct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n \tint fd;\n \n-\tfd = unix_stream_listen(socket_path);\n+\tfd = unix_stream_listen(socket_path, &opts);\n \tif (fd < 0)\n \t\tdie_errno(\"unable to bind to '%s'\", socket_path);\n \ndiff --git a/unix-socket.c b/unix-socket.c\nindex ef2aeb46bcd..8bcef18ea55 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -88,24 +88,35 @@ int unix_stream_connect(const char *path)\n \treturn -1;\n }\n \n-int unix_stream_listen(const char *path)\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1;\n+\tint saved_errno;\n+\tint bind_successful = 0;\n+\tint backlog;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n-\tunlink(path);\n-\n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n+\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to create socket\");\n+\t\tgoto fail;\n+\n+\tif (opts->force_unlink_before_bind)\n+\t\tunlink(path);\n \n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n+\tbind_successful = 1;\n \n-\tif (listen(fd, 5) < 0)\n+\tif (opts->listen_backlog_size > 0)\n+\t\tbacklog = opts->listen_backlog_size;\n+\telse\n+\t\tbacklog = 5;\n+\tif (listen(fd, backlog) < 0)\n \t\tgoto fail;\n \n \tunix_sockaddr_cleanup(&ctx);\n@@ -114,7 +125,10 @@ int unix_stream_listen(const char *path)\n fail:\n \tsaved_errno = errno;\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n+\tif (fd != -1)\n+\t\tclose(fd);\n+\tif (bind_successful)\n+\t\tunlink(path);\n \terrno = saved_errno;\n \treturn -1;\n }\ndiff --git a/unix-socket.h b/unix-socket.h\nindex e271aeec5a0..c28372ef48e 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -1,7 +1,19 @@\n #ifndef UNIX_SOCKET_H\n #define UNIX_SOCKET_H\n \n+struct unix_stream_listen_opts {\n+\tint listen_backlog_size;\n+\tunsigned int force_unlink_before_bind:1;\n+};\n+\n+#define UNIX_STREAM_LISTEN_OPTS_INIT \\\n+{ \\\n+\t.listen_backlog_size = 5, \\\n+\t.force_unlink_before_bind = 1, \\\n+}\n+\n int unix_stream_connect(const char *path);\n-int unix_stream_listen(const char *path);\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts);\n \n #endif /* UNIX_SOCKET_H */\n-- \ngitgitgadget\n\n"},{"id":"415797","messageId":"a7275b4bdc2a3cc285bd0e3bb62f3e1d6566c506.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 07/14] simple-ipc: design documentation for new IPC mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:40Z","receivedAt":"2021-02-01T19:48:44Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nBrief design documentation for new IPC mechanism allowing\nforeground Git client to talk with an existing daemon process\nat a known location using a named pipe or unix domain socket.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-simple-ipc.txt | 34 ++++++++++++++++++++++\n 1 file changed, 34 insertions(+)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n\ndiff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt\nnew file mode 100644\nindex 00000000000..670a5c163e3\n--- /dev/null\n+++ b/Documentation/technical/api-simple-ipc.txt\n@@ -0,0 +1,34 @@\n+simple-ipc API\n+==============\n+\n+The simple-ipc API is used to send an IPC message and response between\n+a (presumably) foreground Git client process to a background server or\n+daemon process.  The server process must already be running.  Multiple\n+client processes can simultaneously communicate with the server\n+process.\n+\n+Communication occurs over a named pipe on Windows and a Unix domain\n+socket on other platforms.  Clients and the server rendezvous at a\n+previously agreed-to application-specific pathname (which is outside\n+the scope of this design).\n+\n+This IPC mechanism differs from the existing `sub-process.c` model\n+(Documentation/technical/long-running-process-protocol.txt) and used\n+by applications like Git-LFS.  In the simple-ipc model the server is\n+assumed to be a very long-running system service.  In contrast, in the\n+LFS-style sub-process model the helper is started with the foreground\n+process and exits when the foreground process terminates.\n+\n+How the simple-ipc server is started is also outside the scope of the\n+IPC mechanism.  For example, the server might be started during\n+maintenance operations.\n+\n+The IPC protocol consists of a single request message from the client and\n+an optional request message from the server.  For simplicity, pkt-line\n+routines are used to hide chunking and buffering concerns.  Each side\n+terminates their message with a flush packet.\n+(Documentation/technical/protocol-common.txt)\n+\n+The actual format of the client and server messages is application\n+specific.  The IPC layer transmits and receives an opaque buffer without\n+any concern for the content within.\n-- \ngitgitgadget\n\n"},{"id":"415798","messageId":"72c1c209c380d5232e6356cdc338467288ee0425.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 14/14] simple-ipc: add Unix domain socket implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:47Z","receivedAt":"2021-02-01T19:48:59Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Unix domain socket based implementation of \"simple-ipc\".\n\nA set of `ipc_client` routines implement a client library to connect\nto an `ipc_server` over a Unix domain socket, send a simple request,\nand receive a single response.  Clients use blocking IO on the socket.\n\nA set of `ipc_server` routines implement a thread pool to listen for\nand concurrently service client connections.\n\nThe server creates a new Unix domain socket at a known location.  If a\nsocket already exists with that name, the server tries to determine if\nanother server is already listening on the socket or if the socket is\ndead.  If socket is busy, the server exits with an error rather than\nstealing the socket.  If the socket is dead, the server creates a new\none and starts up.\n\nIf while running, the server detects that its socket has been stolen\nby another server, it automatically exits.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |    2 +\n compat/simple-ipc/ipc-unix-socket.c | 1127 +++++++++++++++++++++++++++\n contrib/buildsystems/CMakeLists.txt |    2 +\n simple-ipc.h                        |    7 +-\n 4 files changed, 1137 insertions(+), 1 deletion(-)\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n\ndiff --git a/Makefile b/Makefile\nindex e7ba8853ea6..f2524c02ff0 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1681,6 +1681,8 @@ ifdef NO_UNIX_SOCKETS\n \tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n else\n \tLIB_OBJS += unix-socket.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-unix-socket.o\n endif\n \n ifdef USE_WIN32_IPC\ndiff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c\nnew file mode 100644\nindex 00000000000..844906d1af5\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-unix-socket.c\n@@ -0,0 +1,1127 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+#include \"unix-socket.h\"\n+\n+#ifdef NO_UNIX_SOCKETS\n+#error compat/simple-ipc/ipc-unix-socket.c requires Unix sockets\n+#endif\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\tstruct stat st;\n+\tstruct ipc_client_connection *connection_test = NULL;\n+\n+\toptions.wait_if_busy = 0;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (lstat(path, &st) == -1) {\n+\t\tswitch (errno) {\n+\t\tcase ENOENT:\n+\t\tcase ENOTDIR:\n+\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__INVALID_PATH;\n+\t\t}\n+\t}\n+\n+\t/* also complain if a plain file is in the way */\n+\tif ((st.st_mode & S_IFMT) != S_IFSOCK)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\t/*\n+\t * Just because the filesystem has a S_IFSOCK type inode\n+\t * at `path`, doesn't mean it that there is a server listening.\n+\t * Ping it to be sure.\n+\t */\n+\tstate = ipc_client_try_connect(path, &options, &connection_test);\n+\tipc_client_close_connection(connection_test);\n+\n+\treturn state;\n+}\n+\n+/*\n+ * This value was chosen at random.\n+ */\n+#define WAIT_STEP_MS (50)\n+\n+/*\n+ * Try to connect to the server.  If the server is just starting up or\n+ * is very busy, we may not get a connection the first time.\n+ */\n+static enum ipc_active_state connect_to_server(\n+\tconst char *path,\n+\tint timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tint wait_ms = 50;\n+\tint k;\n+\n+\t*pfd = -1;\n+\n+\tfor (k = 0; k < timeout_ms; k += wait_ms) {\n+\t\tint fd = unix_stream_connect(path);\n+\n+\t\tif (fd != -1) {\n+\t\t\t*pfd = fd;\n+\t\t\treturn IPC_STATE__LISTENING;\n+\t\t}\n+\n+\t\tif (errno == ENOENT) {\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ETIMEDOUT) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ECONNREFUSED) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\n+\tsleep_and_try_again:\n+\t\tsleep_millisec(wait_ms);\n+\t}\n+\n+\treturn IPC_STATE__NOT_LISTENING;\n+}\n+\n+/*\n+ * A randomly chosen timeout value.\n+ */\n+#define MY_CONNECTION_TIMEOUT_MS (1000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tstate = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf2(message, strlen(message),\n+\t\t\t\t       connection->fd, 1,\n+\t\t\t\t       &connection->scratch_write_buffer) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tif (read_packetized_to_strbuf(connection->fd, answer,\n+\t\t\t\t      PACKET_READ_NEVER_DIE) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, answer);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+static int set_socket_blocking_flag(int fd, int make_nonblocking)\n+{\n+\tint flags;\n+\n+\tflags = fcntl(fd, F_GETFL, NULL);\n+\n+\tif (flags < 0)\n+\t\treturn -1;\n+\n+\tif (make_nonblocking)\n+\t\tflags |= O_NONBLOCK;\n+\telse\n+\t\tflags &= ~O_NONBLOCK;\n+\n+\treturn fcntl(fd, F_SETFL, flags);\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_WORKER_THREAD_DATA,\n+\tMAGIC_ACCEPT_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_worker_thread_data *worker_thread_data;\n+};\n+\n+struct ipc_worker_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_worker_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+\tstruct packet_scratch_space scratch_write_buffer;\n+};\n+\n+struct ipc_accept_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_data *server_data;\n+\n+\tint fd_listen;\n+\tstruct stat st_listen;\n+\n+\tint fd_send_shutdown;\n+\tint fd_wait_shutdown;\n+\tpthread_t pthread_id;\n+};\n+\n+/*\n+ * With unix-sockets, the conceptual \"ipc-server\" is implemented as a single\n+ * controller \"accept-thread\" thread and a pool of \"worker-thread\" threads.\n+ * The former does the usual `accept()` loop and dispatches connections\n+ * to an idle worker thread.  The worker threads wait in an idle loop for\n+ * a new connection, communicate with the client and relay data to/from\n+ * the `application_cb` and then wait for another connection from the\n+ * server thread.  This avoids the overhead of constantly creating and\n+ * destroying threads.\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\n+\tstruct ipc_accept_thread_data *accept_thread;\n+\tstruct ipc_worker_thread_data *worker_thread_list;\n+\n+\tpthread_mutex_t work_available_mutex;\n+\tpthread_cond_t work_available_cond;\n+\n+\t/*\n+\t * Accepted but not yet processed client connections are kept\n+\t * in a circular buffer FIFO.  The queue is empty when the\n+\t * positions are equal.\n+\t */\n+\tint *fifo_fds;\n+\tint queue_size;\n+\tint back_pos;\n+\tint front_pos;\n+\n+\tint shutdown_requested;\n+\tint is_stopped;\n+};\n+\n+/*\n+ * Remove and return the oldest queued connection.\n+ *\n+ * Returns -1 if empty.\n+ */\n+static int fifo_dequeue(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint fd;\n+\n+\tif (server_data->back_pos == server_data->front_pos)\n+\t\treturn -1;\n+\n+\tfd = server_data->fifo_fds[server_data->front_pos];\n+\tserver_data->fifo_fds[server_data->front_pos] = -1;\n+\n+\tserver_data->front_pos++;\n+\tif (server_data->front_pos == server_data->queue_size)\n+\t\tserver_data->front_pos = 0;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Push a new fd onto the back of the queue.\n+ *\n+ * Drop it and return -1 if queue is already full.\n+ */\n+static int fifo_enqueue(struct ipc_server_data *server_data, int fd)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint next_back_pos;\n+\n+\tnext_back_pos = server_data->back_pos + 1;\n+\tif (next_back_pos == server_data->queue_size)\n+\t\tnext_back_pos = 0;\n+\n+\tif (next_back_pos == server_data->front_pos) {\n+\t\t/* Queue is full. Just drop it. */\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data->fifo_fds[server_data->back_pos] = fd;\n+\tserver_data->back_pos = next_back_pos;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Wait for a connection to be queued to the FIFO and return it.\n+ *\n+ * Returns -1 if someone has already requested a shutdown.\n+ */\n+static int worker_thread__wait_for_connection(\n+\tstruct ipc_worker_thread_data *worker_thread_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tint fd = -1;\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\tfor (;;) {\n+\t\tif (server_data->shutdown_requested)\n+\t\t\tbreak;\n+\n+\t\tfd = fifo_dequeue(server_data);\n+\t\tif (fd >= 0)\n+\t\t\tbreak;\n+\n+\t\tpthread_cond_wait(&server_data->work_available_cond,\n+\t\t\t\t  &server_data->work_available_mutex);\n+\t}\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tstruct packet_scratch_space *scratch =\n+\t\t&reply_data->worker_thread_data->scratch_write_buffer;\n+\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf2(response, response_len,\n+\t\t\t\t\t  reply_data->fd, 0, scratch);\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_WAIT_POLL_TIMEOUT_MS (10)\n+\n+/*\n+ * If the client hangs up without sending any data on the wire, just\n+ * quietly close the socket and ignore this client.\n+ *\n+ * This worker thread is committed to reading the IPC request data\n+ * from the client at the other end of this fd.  Wait here for the\n+ * client to actually put something on the wire -- because if the\n+ * client just does a ping (connect and hangup without sending any\n+ * data), our use of the pkt-line read routines will spew an error\n+ * message.\n+ *\n+ * Return -1 if the client hung up.\n+ * Return 0 if data (possibly incomplete) is ready.\n+ */\n+static int worker_thread__wait_for_io_start(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tstruct pollfd pollfd[1];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = fd;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\tint in_shutdown;\n+\n+\t\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\t\tin_shutdown = server_data->shutdown_requested;\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\t\t\t/*\n+\t\t\t * If a shutdown is already in progress and this\n+\t\t\t * client has not started talking yet, just drop it.\n+\t\t\t */\n+\t\t\tif (in_shutdown)\n+\t\t\t\tgoto cleanup;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLHUP)\n+\t\t\tgoto cleanup;\n+\n+\t\tif (pollfd[0].revents & POLLIN)\n+\t\t\treturn 0;\n+\n+\t\tgoto cleanup;\n+\t}\n+\n+cleanup:\n+\tclose(fd);\n+\treturn -1;\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ */\n+static int worker_thread__do_io(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\t/* ASSERT NOT holding lock */\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.worker_thread_data = worker_thread_data;\n+\n+\treply_data.fd = fd;\n+\n+\tret = read_packetized_to_strbuf(reply_data.fd, &buf,\n+\t\t\t\t\tPACKET_READ_NEVER_DIE);\n+\tif (ret >= 0) {\n+\t\tret = worker_thread_data->server_data->application_cb(\n+\t\t\tworker_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Block SIGPIPE on the current thread (so that we get EPIPE from\n+ * write() rather than an actual signal).\n+ *\n+ * Note that using sigchain_push() and _pop() to control SIGPIPE\n+ * around our IO calls is not thread safe:\n+ * [] It uses a global stack of handler frames.\n+ * [] It uses ALLOC_GROW() to resize it.\n+ * [] Finally, according to the `signal(2)` man-page:\n+ *    \"The effects of `signal()` in a multithreaded process are unspecified.\"\n+ */\n+static void thread_block_sigpipe(sigset_t *old_set)\n+{\n+\tsigset_t new_set;\n+\n+\tsigemptyset(&new_set);\n+\tsigaddset(&new_set, SIGPIPE);\n+\n+\tsigemptyset(old_set);\n+\tpthread_sigmask(SIG_BLOCK, &new_set, old_set);\n+}\n+\n+/*\n+ * Thread proc for an IPC worker thread.  It handles a series of\n+ * connections from clients.  It pulls the next fd from the queue\n+ * processes it, and then waits for the next client.\n+ *\n+ * Block SIGPIPE in this worker thread for the life of the thread.\n+ * This avoids stray (and sometimes delayed) SIGPIPE signals caused\n+ * by client errors and/or when we are under extremely heavy IO load.\n+ *\n+ * This means that the application callback will have SIGPIPE blocked.\n+ * The callback should not change it.\n+ */\n+static void *worker_thread_proc(void *_worker_thread_data)\n+{\n+\tstruct ipc_worker_thread_data *worker_thread_data = _worker_thread_data;\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tsigset_t old_set;\n+\tint fd, io;\n+\tint ret;\n+\n+\ttrace2_thread_start(\"ipc-worker\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tfd = worker_thread__wait_for_connection(worker_thread_data);\n+\t\tif (fd == -1)\n+\t\t\tbreak; /* in shutdown */\n+\n+\t\tio = worker_thread__wait_for_io_start(worker_thread_data, fd);\n+\t\tif (io == -1)\n+\t\t\tcontinue; /* client hung up without sending anything */\n+\n+\t\tret = worker_thread__do_io(worker_thread_data, fd);\n+\n+\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\ttrace2_data_string(\"ipc-worker\", NULL, \"queue_stop_async\",\n+\t\t\t\t\t   \"application_quit\");\n+\t\t\t/* The application told us to shutdown. */\n+\t\t\tipc_server_stop_async(server_data);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * Return 1 if someone deleted or stole the on-disk socket from us.\n+ */\n+static int socket_was_stolen(struct ipc_accept_thread_data *accept_thread_data)\n+{\n+\tstruct stat st;\n+\tstruct stat *ref_st = &accept_thread_data->st_listen;\n+\n+\tif (lstat(accept_thread_data->server_data->buf_path.buf, &st) == -1)\n+\t\treturn 1;\n+\n+\tif (st.st_ino != ref_st->st_ino)\n+\t\treturn 1;\n+\n+\t/* We might also consider the creation time on some platforms. */\n+\n+\treturn 0;\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)\n+\n+/*\n+ * Accept a new client connection on our socket.  This uses non-blocking\n+ * IO so that we can also wait for shutdown requests on our socket-pair\n+ * without actually spinning on a fast timeout.\n+ */\n+static int accept_thread__wait_for_connection(\n+\tstruct ipc_accept_thread_data *accept_thread_data)\n+{\n+\tstruct pollfd pollfd[2];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = accept_thread_data->fd_wait_shutdown;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tpollfd[1].fd = accept_thread_data->fd_listen;\n+\t\tpollfd[1].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\treturn result;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\t/*\n+\t\t\t * If someone deletes or force-creates a new unix\n+\t\t\t * domain socket at out path, all future clients\n+\t\t\t * will be routed elsewhere and we silently starve.\n+\t\t\t * If that happens, just queue a shutdown.\n+\t\t\t */\n+\t\t\tif (socket_was_stolen(\n+\t\t\t\t    accept_thread_data)) {\n+\t\t\t\ttrace2_data_string(\"ipc-accept\", NULL,\n+\t\t\t\t\t\t   \"queue_stop_async\",\n+\t\t\t\t\t\t   \"socket_stolen\");\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\taccept_thread_data->server_data);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLIN) {\n+\t\t\t/* shutdown message queued to socketpair */\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (pollfd[1].revents & POLLIN) {\n+\t\t\t/* a connection is available on fd_listen */\n+\n+\t\t\tint client_fd = accept(accept_thread_data->fd_listen,\n+\t\t\t\t\t       NULL, NULL);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\treturn client_fd;\n+\n+\t\t\t/*\n+\t\t\t * An error here is unlikely -- it probably\n+\t\t\t * indicates that the connecting process has\n+\t\t\t * already dropped the connection.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tBUG(\"unandled poll result errno=%d r[0]=%d r[1]=%d\",\n+\t\t    errno, pollfd[0].revents, pollfd[1].revents);\n+\t}\n+}\n+\n+/*\n+ * Thread proc for the IPC server \"accept thread\".  This waits for\n+ * an incoming socket connection, appends it to the queue of available\n+ * connections, and notifies a worker thread to process it.\n+ *\n+ * Block SIGPIPE in this thread for the life of the thread.  This\n+ * avoids any stray SIGPIPE signals when closing pipe fds under\n+ * extremely heavy loads (such as when the fifo queue is full and we\n+ * drop incomming connections).\n+ */\n+static void *accept_thread_proc(void *_accept_thread_data)\n+{\n+\tstruct ipc_accept_thread_data *accept_thread_data = _accept_thread_data;\n+\tstruct ipc_server_data *server_data = accept_thread_data->server_data;\n+\tsigset_t old_set;\n+\n+\ttrace2_thread_start(\"ipc-accept\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tint client_fd = accept_thread__wait_for_connection(\n+\t\t\taccept_thread_data);\n+\n+\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\tif (server_data->shutdown_requested) {\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\tclose(client_fd);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (client_fd < 0) {\n+\t\t\t/* ignore transient accept() errors */\n+\t\t}\n+\t\telse {\n+\t\t\tfifo_enqueue(server_data, client_fd);\n+\t\t\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\t\t}\n+\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * We can't predict the connection arrival rate relative to the worker\n+ * processing rate, therefore we allow the \"accept-thread\" to queue up\n+ * a generous number of connections, since we'd rather have the client\n+ * not unnecessarily timeout if we can avoid it.  (The assumption is\n+ * that this will be used for FSMonitor and a few second wait on a\n+ * connection is better than having the client timeout and do the full\n+ * computation itself.)\n+ *\n+ * The FIFO queue size is set to a multiple of the worker pool size.\n+ * This value chosen at random.\n+ */\n+#define FIFO_SCALE (100)\n+\n+/*\n+ * The backlog value for `listen(2)`.  This doesn't need to huge,\n+ * rather just large enough for our \"accept-thread\" to wake up and\n+ * queue incoming connections onto the FIFO without the kernel\n+ * dropping any.\n+ *\n+ * This value chosen at random.\n+ */\n+#define LISTEN_BACKLOG (50)\n+\n+/*\n+ * Create a unix domain socket at the given path to listen for\n+ * client connections.  The resulting socket will then appear\n+ * in the filesystem as an inode with S_IFSOCK.  The inode is\n+ * itself created as part of the `bind(2)` operation.\n+ *\n+ * The term \"socket\" is ambiguous in this context.  We want to open a\n+ * \"socket-fd\" that is bound to a \"socket-inode\" (path) on disk.  We\n+ * listen on \"socket-fd\" for new connections and clients try to\n+ * open/connect using the \"socket-inode\" pathname.\n+ *\n+ * Unix domain sockets have a fundamental design flaw because the\n+ * \"socket-inode\" persists until the pathname is deleted; closing the\n+ * listening \"socket-fd\" only closes the socket handle/descriptor, it\n+ * does not delete the inode/pathname.\n+ *\n+ * Well-behaving service daemons are expected to also delete the inode\n+ * before shutdown.  If a service crashes (or forgets) it can leave\n+ * the (now stale) inode in the filesystem.  This behaves like a stale\n+ * \".lock\" file and may prevent future service instances from starting\n+ * up correctly.  (Because they won't be able to bind.)\n+ *\n+ * When future service instances try to create the listener socket,\n+ * `bind(2)` will fail with EADDRINUSE -- because the inode already\n+ * exists.  However, the new instance cannot tell if it is a stale\n+ * inode *or* another service instance is already running.\n+ *\n+ * One possible solution is to blindly unlink the inode before\n+ * attempting to bind a new socket-fd and thus create a new\n+ * socket-inode.  Then `bind(2)` should always succeed.  However, if\n+ * there is an existing service instance, it would be orphaned -- it\n+ * would still be listening on a socket-fd that is still bound to an\n+ * (unlinked) socket-inode, but that socket-inode is no longer\n+ * associated with the pathname.  New client connections will arrive\n+ * at OUR new socket-inode -- rather than the existing server's\n+ * socket.  (I suppose it is up to the existing server to detect that\n+ * its socket-inode has been stolen and shutdown.)\n+ *\n+ * Another possible solution is to try to use the \".lock\" trick, but\n+ * bind() does not have a exclusive-create use bit like open() does,\n+ * so we cannot have multiple servers fighting/racing to create the\n+ * same file name without having losers lose without knowing that they\n+ * lost.\n+ *\n+ * We try to avoid such stealing and would rather fail to run than\n+ * steal an existing socket-inode (because we assume that the\n+ * existing server has more context and value to the clients than a\n+ * freshly started server).  However, if multiple servers are racing\n+ * to start, we don't care which one wins -- none of them have any\n+ * state information yet worth fighting for.\n+ *\n+ * Create a \"unique\" socket-inode (with our PID in it (and assume that\n+ * we can force-delete an existing socket with that name)).  Stat it\n+ * to get the inode number and ctime -- so that we can identify it as\n+ * the one we created.  Then use the atomic-rename trick to install it\n+ * in the real location.  (This will unlink an existing socket with\n+ * that pathname -- and thereby steal the real socket-inode from an\n+ * existing server.)\n+ *\n+ * Elsewhere, our thread will periodically poll the socket-inode to\n+ * see if someone else steals ours.\n+ */\n+static int create_listener_socket(const char *path,\n+\t\t\t\t  const struct ipc_server_opts *ipc_opts,\n+\t\t\t\t  struct stat *st_socket)\n+{\n+\tstruct stat st;\n+\tstruct strbuf buf_uniq = STRBUF_INIT;\n+\tint fd_listen;\n+\tstruct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n+\n+\tif (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {\n+\t\tint fd_client;\n+\t\t/*\n+\t\t * A socket-inode at `path` exists on disk, but we\n+\t\t * don't know whether it belongs to an active server\n+\t\t * or if the last server died without cleaning up.\n+\t\t *\n+\t\t * Poke it with a trivial connection to try to find out.\n+\t\t */\n+\t\ttrace2_data_string(\"ipc-server\", NULL, \"try-detect-server\",\n+\t\t\t\t   path);\n+\t\tfd_client = unix_stream_connect(path);\n+\t\tif (fd_client >= 0) {\n+\t\t\tclose(fd_client);\n+\t\t\terrno = EADDRINUSE;\n+\t\t\treturn error_errno(_(\"socket already in use '%s'\"),\n+\t\t\t\t\t   path);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Create pathname to our \"unique\" socket and set it up for\n+\t * business.\n+\t */\n+\tstrbuf_addf(&buf_uniq, \"%s.%d\", path, getpid());\n+\n+\tuslg_opts.listen_backlog_size = LISTEN_BACKLOG;\n+\tuslg_opts.force_unlink_before_bind = 1;\n+\tuslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;\n+\tfd_listen = unix_stream_listen(buf_uniq.buf, &uslg_opts);\n+\tif (fd_listen < 0) {\n+\t\tint saved_errno = errno;\n+\t\terror_errno(_(\"could not create listener socket '%s'\"),\n+\t\t\t    buf_uniq.buf);\n+\t\tstrbuf_release(&buf_uniq);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tif (lstat(buf_uniq.buf, st_socket)) {\n+\t\tint saved_errno = errno;\n+\t\terror_errno(_(\"could not stat listener socket '%s'\"),\n+\t\t\t    buf_uniq.buf);\n+\t\tclose(fd_listen);\n+\t\tunlink(buf_uniq.buf);\n+\t\tstrbuf_release(&buf_uniq);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tif (set_socket_blocking_flag(fd_listen, 1)) {\n+\t\tint saved_errno = errno;\n+\t\terror_errno(_(\"could not set listener socket nonblocking '%s'\"),\n+\t\t\t    buf_uniq.buf);\n+\t\tclose(fd_listen);\n+\t\tunlink(buf_uniq.buf);\n+\t\tstrbuf_release(&buf_uniq);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * Install it as the \"real\" socket so that clients will starting\n+\t * connecting to our socket.\n+\t */\n+\tif (rename(buf_uniq.buf, path)) {\n+\t\tint saved_errno = errno;\n+\t\terror_errno(_(\"could not create listener socket '%s'\"), path);\n+\t\tclose(fd_listen);\n+\t\tunlink(buf_uniq.buf);\n+\t\tstrbuf_release(&buf_uniq);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_release(&buf_uniq);\n+\ttrace2_data_string(\"ipc-server\", NULL, \"try-listen\", path);\n+\treturn fd_listen;\n+}\n+\n+static int setup_listener_socket(const char *path, struct stat *st_socket,\n+\t\t\t\t const struct ipc_server_opts *ipc_opts)\n+{\n+\tint fd_listen;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n+\tfd_listen = create_listener_socket(path, ipc_opts, st_socket);\n+\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n+\n+\treturn fd_listen;\n+}\n+\n+/*\n+ * Start IPC server in a pool of background threads.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct ipc_server_data *server_data;\n+\tint fd_listen;\n+\tstruct stat st_listen;\n+\tint sv[2];\n+\tint k;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\t/*\n+\t * Create a socketpair and set sv[1] to non-blocking.  This\n+\t * will used to send a shutdown message to the accept-thread\n+\t * and allows the accept-thread to wait on EITHER a client\n+\t * connection or a shutdown request without spinning.\n+\t */\n+\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)\n+\t\treturn error_errno(_(\"could not create socketpair for '%s'\"),\n+\t\t\t\t   path);\n+\n+\tif (set_socket_blocking_flag(sv[1], 1)) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn error_errno(_(\"making socketpair nonblocking '%s'\"),\n+\t\t\t\t   path);\n+\t}\n+\n+\tfd_listen = setup_listener_socket(path, &st_listen, opts);\n+\tif (fd_listen < 0) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tpthread_mutex_init(&server_data->work_available_mutex, NULL);\n+\tpthread_cond_init(&server_data->work_available_cond, NULL);\n+\n+\tserver_data->queue_size = nr_threads * FIFO_SCALE;\n+\tserver_data->fifo_fds = xcalloc(server_data->queue_size,\n+\t\t\t\t\tsizeof(*server_data->fifo_fds));\n+\n+\tserver_data->accept_thread =\n+\t\txcalloc(1, sizeof(*server_data->accept_thread));\n+\tserver_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;\n+\tserver_data->accept_thread->server_data = server_data;\n+\tserver_data->accept_thread->fd_listen = fd_listen;\n+\tserver_data->accept_thread->st_listen = st_listen;\n+\tserver_data->accept_thread->fd_send_shutdown = sv[0];\n+\tserver_data->accept_thread->fd_wait_shutdown = sv[1];\n+\n+\tif (pthread_create(&server_data->accept_thread->pthread_id, NULL,\n+\t\t\t   accept_thread_proc, server_data->accept_thread))\n+\t\tdie_errno(_(\"could not start accept_thread '%s'\"), path);\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_worker_thread_data *wtd;\n+\n+\t\twtd = xcalloc(1, sizeof(*wtd));\n+\t\twtd->magic = MAGIC_WORKER_THREAD_DATA;\n+\t\twtd->server_data = server_data;\n+\n+\t\tif (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc,\n+\t\t\t\t   wtd)) {\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start worker[0] for '%s'\"),\n+\t\t\t\t    path);\n+\t\t\t/*\n+\t\t\t * Limp along with the thread pool that we have.\n+\t\t\t */\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\twtd->next_thread = server_data->worker_thread_list;\n+\t\tserver_data->worker_thread_list = wtd;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+/*\n+ * Gently tell the IPC server treads to shutdown.\n+ * Can be run on any thread.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tint fd;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\n+\tserver_data->shutdown_requested = 1;\n+\n+\t/*\n+\t * Write a byte to the shutdown socket pair to wake up the\n+\t * accept-thread.\n+\t */\n+\tif (write(server_data->accept_thread->fd_send_shutdown, \"Q\", 1) < 0)\n+\t\terror_errno(\"could not write to fd_send_shutdown\");\n+\n+\t/*\n+\t * Drain the queue of existing connections.\n+\t */\n+\twhile ((fd = fifo_dequeue(server_data)) != -1)\n+\t\tclose(fd);\n+\n+\t/*\n+\t * Gently tell worker threads to stop processing new connections\n+\t * and exit.  (This does not abort in-process conversations.)\n+\t */\n+\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\ttrace2_region_leave(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Wait for all IPC server threads to stop.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tpthread_join(server_data->accept_thread->pthread_id, NULL);\n+\n+\tif (!server_data->shutdown_requested)\n+\t\tBUG(\"ipc-server: accept-thread stopped for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tpthread_join(wtd->pthread_id, NULL);\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tstruct ipc_accept_thread_data * accept_thread_data;\n+\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\taccept_thread_data = server_data->accept_thread;\n+\tif (accept_thread_data) {\n+\t\tif (accept_thread_data->fd_listen != -1) {\n+\t\t\t/*\n+\t\t\t * Only unlink the unix domain socket if we\n+\t\t\t * created it.  That is, if another daemon\n+\t\t\t * process force-created a new socket at this\n+\t\t\t * path, and effectively steals our path\n+\t\t\t * (which prevents us from receiving any\n+\t\t\t * future clients), we don't want to do the\n+\t\t\t * same thing to them.\n+\t\t\t */\n+\t\t\tif (!socket_was_stolen(\n+\t\t\t\t    accept_thread_data))\n+\t\t\t\tunlink(server_data->buf_path.buf);\n+\n+\t\t\tclose(accept_thread_data->fd_listen);\n+\t\t}\n+\t\tif (accept_thread_data->fd_send_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_send_shutdown);\n+\t\tif (accept_thread_data->fd_wait_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_wait_shutdown);\n+\n+\t\tfree(server_data->accept_thread);\n+\t}\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tpthread_cond_destroy(&server_data->work_available_cond);\n+\tpthread_mutex_destroy(&server_data->work_available_mutex);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tfree(server_data->fifo_fds);\n+\tfree(server_data);\n+}\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 4bd41054ee7..4c27a373414 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -248,6 +248,8 @@ endif()\n \n if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+else()\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c)\n endif()\n \n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\ndiff --git a/simple-ipc.h b/simple-ipc.h\nindex eb19b5da8b1..17b28bc1f83 100644\n--- a/simple-ipc.h\n+++ b/simple-ipc.h\n@@ -5,7 +5,7 @@\n  * See Documentation/technical/api-simple-ipc.txt\n  */\n \n-#if defined(GIT_WINDOWS_NATIVE)\n+#if defined(GIT_WINDOWS_NATIVE) || !defined(NO_UNIX_SOCKETS)\n #define SUPPORTS_SIMPLE_IPC\n #endif\n \n@@ -160,6 +160,11 @@ struct ipc_server_data;\n struct ipc_server_opts\n {\n \tint nr_threads;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n /*\n-- \ngitgitgadget\n"},{"id":"415799","messageId":"388366913d419bbc08f5b7658cf7fb7b9d6a9079.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 08/14] simple-ipc: add win32 implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:41Z","receivedAt":"2021-02-01T19:49:11Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Windows implementation of \"simple-ipc\" using named pipes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   5 +\n compat/simple-ipc/ipc-shared.c      |  28 ++\n compat/simple-ipc/ipc-win32.c       | 751 ++++++++++++++++++++++++++++\n config.mak.uname                    |   2 +\n contrib/buildsystems/CMakeLists.txt |   4 +\n simple-ipc.h                        | 225 +++++++++\n 6 files changed, 1015 insertions(+)\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n\ndiff --git a/Makefile b/Makefile\nindex 7b64106930a..c94d5847919 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1682,6 +1682,11 @@ else\n \tLIB_OBJS += unix-socket.o\n endif\n \n+ifdef USE_WIN32_IPC\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-win32.o\n+endif\n+\n ifdef NO_ICONV\n \tBASIC_CFLAGS += -DNO_ICONV\n endif\ndiff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c\nnew file mode 100644\nindex 00000000000..1edec815953\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-shared.c\n@@ -0,0 +1,28 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data)\n+{\n+\tstruct ipc_server_data *server_data = NULL;\n+\tint ret;\n+\n+\tret = ipc_server_run_async(&server_data, path, opts,\n+\t\t\t\t   application_cb, application_data);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = ipc_server_await(server_data);\n+\n+\tipc_server_free(server_data);\n+\n+\treturn ret;\n+}\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\ndiff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c\nnew file mode 100644\nindex 00000000000..7871c9d8527\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-win32.c\n@@ -0,0 +1,751 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+#error This file can only be compiled on Windows\n+#endif\n+\n+static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc)\n+{\n+\tint off = 0;\n+\tstruct strbuf realpath = STRBUF_INIT;\n+\n+\tif (!strbuf_realpath(&realpath, path, 0))\n+\t\treturn -1;\n+\n+\toff = swprintf(wpath, alloc, L\"\\\\\\\\.\\\\pipe\\\\\");\n+\tif (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0)\n+\t\treturn -1;\n+\n+\t/* Handle drive prefix */\n+\tif (wpath[off] && wpath[off + 1] == L':') {\n+\t\twpath[off + 1] = L'_';\n+\t\toff += 2;\n+\t}\n+\n+\tfor (; wpath[off]; off++)\n+\t\tif (wpath[off] == L'/')\n+\t\t\twpath[off] = L'\\\\';\n+\n+\tstrbuf_release(&realpath);\n+\treturn 0;\n+}\n+\n+static enum ipc_active_state get_active_state(wchar_t *pipe_path)\n+{\n+\tif (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT))\n+\t\treturn IPC_STATE__LISTENING;\n+\n+\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\tif (GetLastError() == ERROR_FILE_NOT_FOUND)\n+\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\treturn IPC_STATE__OTHER_ERROR;\n+}\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\twchar_t pipe_path[MAX_PATH];\n+\n+\tif (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\treturn get_active_state(pipe_path);\n+}\n+\n+#define WAIT_STEP_MS (50)\n+\n+static enum ipc_active_state connect_to_server(\n+\tconst wchar_t *wpath,\n+\tDWORD timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tDWORD t_start_ms, t_waited_ms;\n+\tDWORD step_ms;\n+\tHANDLE hPipe = INVALID_HANDLE_VALUE;\n+\tDWORD mode = PIPE_READMODE_BYTE;\n+\tDWORD gle;\n+\n+\t*pfd = -1;\n+\n+\tfor (;;) {\n+\t\thPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE,\n+\t\t\t\t    0, NULL, OPEN_EXISTING, 0, NULL);\n+\t\tif (hPipe != INVALID_HANDLE_VALUE)\n+\t\t\tbreak;\n+\n+\t\tgle = GetLastError();\n+\n+\t\tswitch (gle) {\n+\t\tcase ERROR_FILE_NOT_FOUND:\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tstep_ms = (timeout_ms < WAIT_STEP_MS) ?\n+\t\t\t\ttimeout_ms : WAIT_STEP_MS;\n+\t\t\tsleep_millisec(step_ms);\n+\n+\t\t\ttimeout_ms -= step_ms;\n+\t\t\tbreak; /* try again */\n+\n+\t\tcase ERROR_PIPE_BUSY:\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tt_start_ms = (DWORD)(getnanotime() / 1000000);\n+\n+\t\t\tif (!WaitNamedPipeW(wpath, timeout_ms)) {\n+\t\t\t\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * A pipe server instance became available.\n+\t\t\t * Race other client processes to connect to\n+\t\t\t * it.\n+\t\t\t *\n+\t\t\t * But first decrement our overall timeout so\n+\t\t\t * that we don't starve if we keep losing the\n+\t\t\t * race.  But also guard against special\n+\t\t\t * NPMWAIT_ values (0 and -1).\n+\t\t\t */\n+\t\t\tt_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms;\n+\t\t\tif (t_waited_ms < timeout_ms)\n+\t\t\t\ttimeout_ms -= t_waited_ms;\n+\t\t\telse\n+\t\t\t\ttimeout_ms = 1;\n+\t\t\tbreak; /* try again */\n+\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t}\n+\t}\n+\n+\tif (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t*pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);\n+\tif (*pfd < 0) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t/* fd now owns hPipe */\n+\n+\treturn IPC_STATE__LISTENING;\n+}\n+\n+/*\n+ * The default connection timeout for Windows clients.\n+ *\n+ * This is not currently part of the ipc_ API (nor the config settings)\n+ * because of differences between Windows and other platforms.\n+ *\n+ * This value was chosen at random.\n+ */\n+#define WINDOWS_CONNECTION_TIMEOUT_MS (30000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\twchar_t wpath[MAX_PATH];\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tif (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0)\n+\t\tstate = IPC_STATE__INVALID_PATH;\n+\telse\n+\t\tstate = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf2(message, strlen(message),\n+\t\t\t\t       connection->fd, 1,\n+\t\t\t\t       &connection->scratch_write_buffer) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tFlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));\n+\n+\tif (read_packetized_to_strbuf(connection->fd, answer,\n+\t\t\t\t      PACKET_READ_NEVER_DIE) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *response)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, response);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Duplicate the given pipe handle and wrap it in a file descriptor so\n+ * that we can use pkt-line on it.\n+ */\n+static int dup_fd_from_pipe(const HANDLE pipe)\n+{\n+\tHANDLE process = GetCurrentProcess();\n+\tHANDLE handle;\n+\tint fd;\n+\n+\tif (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE,\n+\t\t\t     DUPLICATE_SAME_ACCESS)) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\treturn -1;\n+\t}\n+\n+\tfd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY);\n+\tif (fd < 0) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\tCloseHandle(handle);\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * `handle` is now owned by `fd` and will be automatically closed\n+\t * when the descriptor is closed.\n+\t */\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_SERVER_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_server_thread_data *server_thread_data;\n+};\n+\n+struct ipc_server_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+\tHANDLE hPipe;\n+\tstruct packet_scratch_space scratch_write_buffer;\n+};\n+\n+/*\n+ * On Windows, the conceptual \"ipc-server\" is implemented as a pool of\n+ * n idential/peer \"server-thread\" threads.  That is, there is no\n+ * hierarchy of threads; and therefore no controller thread managing\n+ * the pool.  Each thread has an independent handle to the named pipe,\n+ * receives incoming connections, processes the client, and re-uses\n+ * the pipe for the next client connection.\n+ *\n+ * Therefore, the \"ipc-server\" only needs to maintain a list of the\n+ * spawned threads for eventual \"join\" purposes.\n+ *\n+ * A single \"stop-event\" is visible to all of the server threads to\n+ * tell them to shutdown (when idle).\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\twchar_t wpath[MAX_PATH];\n+\n+\tHANDLE hEventStopRequested;\n+\tstruct ipc_server_thread_data *thread_list;\n+\tint is_stopped;\n+};\n+\n+enum connect_result {\n+\tCR_CONNECTED = 0,\n+\tCR_CONNECT_PENDING,\n+\tCR_CONNECT_ERROR,\n+\tCR_WAIT_ERROR,\n+\tCR_SHUTDOWN,\n+};\n+\n+static enum connect_result queue_overlapped_connect(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tif (ConnectNamedPipe(server_thread_data->hPipe, lpo))\n+\t\tgoto failed;\n+\n+\tswitch (GetLastError()) {\n+\tcase ERROR_IO_PENDING:\n+\t\treturn CR_CONNECT_PENDING;\n+\n+\tcase ERROR_PIPE_CONNECTED:\n+\t\tSetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+failed:\n+\terror(_(\"ConnectNamedPipe failed for '%s' (%lu)\"),\n+\t      server_thread_data->server_data->buf_path.buf,\n+\t      GetLastError());\n+\treturn CR_CONNECT_ERROR;\n+}\n+\n+/*\n+ * Use Windows Overlapped IO to wait for a connection or for our event\n+ * to be signalled.\n+ */\n+static enum connect_result wait_for_connection(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tenum connect_result r;\n+\tHANDLE waitHandles[2];\n+\tDWORD dwWaitResult;\n+\n+\tr = queue_overlapped_connect(server_thread_data, lpo);\n+\tif (r != CR_CONNECT_PENDING)\n+\t\treturn r;\n+\n+\twaitHandles[0] = server_thread_data->server_data->hEventStopRequested;\n+\twaitHandles[1] = lpo->hEvent;\n+\n+\tdwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);\n+\tswitch (dwWaitResult) {\n+\tcase WAIT_OBJECT_0 + 0:\n+\t\treturn CR_SHUTDOWN;\n+\n+\tcase WAIT_OBJECT_0 + 1:\n+\t\tResetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\treturn CR_WAIT_ERROR;\n+\t}\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tstruct packet_scratch_space *scratch =\n+\t\t&reply_data->server_thread_data->scratch_write_buffer;\n+\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf2(response, response_len,\n+\t\t\t\t\t  reply_data->fd, 0, scratch);\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ *\n+ * Simple-IPC only contains one round trip, so we flush and close\n+ * here after the response.\n+ */\n+static int do_io(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.server_thread_data = server_thread_data;\n+\n+\treply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe);\n+\tif (reply_data.fd < 0)\n+\t\treturn error(_(\"could not create fd from pipe for '%s'\"),\n+\t\t\t     server_thread_data->server_data->buf_path.buf);\n+\n+\tret = read_packetized_to_strbuf(reply_data.fd, &buf,\n+\t\t\t\t\tPACKET_READ_NEVER_DIE);\n+\tif (ret >= 0) {\n+\t\tret = server_thread_data->server_data->application_cb(\n+\t\t\tserver_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\n+\t\tFlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd)));\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Handle IPC request and response with this connected client.  And reset\n+ * the pipe to prepare for the next client.\n+ */\n+static int use_connection(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tint ret;\n+\n+\tret = do_io(server_thread_data);\n+\n+\tFlushFileBuffers(server_thread_data->hPipe);\n+\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Thread proc for an IPC server worker thread.  It handles a series of\n+ * connections from clients.  It cleans and reuses the hPipe between each\n+ * client.\n+ */\n+static void *server_thread_proc(void *_server_thread_data)\n+{\n+\tstruct ipc_server_thread_data *server_thread_data = _server_thread_data;\n+\tHANDLE hEventConnected = INVALID_HANDLE_VALUE;\n+\tOVERLAPPED oConnect;\n+\tenum connect_result cr;\n+\tint ret;\n+\n+\tassert(server_thread_data->hPipe != INVALID_HANDLE_VALUE);\n+\n+\ttrace2_thread_start(\"ipc-server\");\n+\ttrace2_data_string(\"ipc-server\", NULL, \"pipe\",\n+\t\t\t   server_thread_data->server_data->buf_path.buf);\n+\n+\thEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL);\n+\n+\tmemset(&oConnect, 0, sizeof(oConnect));\n+\toConnect.hEvent = hEventConnected;\n+\n+\tfor (;;) {\n+\t\tcr = wait_for_connection(server_thread_data, &oConnect);\n+\n+\t\tswitch (cr) {\n+\t\tcase CR_SHUTDOWN:\n+\t\t\tgoto finished;\n+\n+\t\tcase CR_CONNECTED:\n+\t\t\tret = use_connection(server_thread_data);\n+\t\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\tserver_thread_data->server_data);\n+\t\t\t\tgoto finished;\n+\t\t\t}\n+\t\t\tif (ret > 0) {\n+\t\t\t\t/*\n+\t\t\t\t * Ignore (transient) IO errors with this\n+\t\t\t\t * client and reset for the next client.\n+\t\t\t\t */\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase CR_CONNECT_PENDING:\n+\t\t\t/* By construction, this should not happen. */\n+\t\t\tBUG(\"ipc-server[%s]: unexpeced CR_CONNECT_PENDING\",\n+\t\t\t    server_thread_data->server_data->buf_path.buf);\n+\n+\t\tcase CR_CONNECT_ERROR:\n+\t\tcase CR_WAIT_ERROR:\n+\t\t\t/*\n+\t\t\t * Ignore these theoretical errors.\n+\t\t\t */\n+\t\t\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tBUG(\"unandled case after wait_for_connection\");\n+\t\t}\n+\t}\n+\n+finished:\n+\tCloseHandle(server_thread_data->hPipe);\n+\tCloseHandle(hEventConnected);\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+static HANDLE create_new_pipe(wchar_t *wpath, int is_first)\n+{\n+\tHANDLE hPipe;\n+\tDWORD dwOpenMode, dwPipeMode;\n+\tLPSECURITY_ATTRIBUTES lpsa = NULL;\n+\n+\tdwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |\n+\t\tFILE_FLAG_OVERLAPPED;\n+\n+\tdwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |\n+\t\tPIPE_REJECT_REMOTE_CLIENTS;\n+\n+\tif (is_first) {\n+\t\tdwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE;\n+\n+\t\t/*\n+\t\t * On Windows, the first server pipe instance gets to\n+\t\t * set the ACL / Security Attributes on the named\n+\t\t * pipe; subsequent instances inherit and cannot\n+\t\t * change them.\n+\t\t *\n+\t\t * TODO Should we allow the application layer to\n+\t\t * specify security attributes, such as `LocalService`\n+\t\t * or `LocalSystem`, when we create the named pipe?\n+\t\t * This question is probably not important when the\n+\t\t * daemon is started by a foreground user process and\n+\t\t * only needs to talk to the current user, but may be\n+\t\t * if the daemon is run via the Control Panel as a\n+\t\t * System Service.\n+\t\t */\n+\t}\n+\n+\thPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,\n+\t\t\t\t PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);\n+\n+\treturn hPipe;\n+}\n+\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct ipc_server_data *server_data;\n+\twchar_t wpath[MAX_PATH];\n+\tHANDLE hPipeFirst = INVALID_HANDLE_VALUE;\n+\tint k;\n+\tint ret = 0;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\tret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));\n+\tif (ret < 0)\n+\t\treturn error(\n+\t\t\t_(\"could not create normalized wchar_t path for '%s'\"),\n+\t\t\tpath);\n+\n+\thPipeFirst = create_new_pipe(wpath, 1);\n+\tif (hPipeFirst == INVALID_HANDLE_VALUE)\n+\t\treturn error(_(\"IPC server already running on '%s'\"), path);\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tserver_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL);\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\twcscpy(server_data->wpath, wpath);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_server_thread_data *std;\n+\n+\t\tstd = xcalloc(1, sizeof(*std));\n+\t\tstd->magic = MAGIC_SERVER_THREAD_DATA;\n+\t\tstd->server_data = server_data;\n+\t\tstd->hPipe = INVALID_HANDLE_VALUE;\n+\n+\t\tstd->hPipe = (k == 0)\n+\t\t\t? hPipeFirst\n+\t\t\t: create_new_pipe(server_data->wpath, 0);\n+\n+\t\tif (std->hPipe == INVALID_HANDLE_VALUE) {\n+\t\t\t/*\n+\t\t\t * If we've reached a pipe instance limit for\n+\t\t\t * this path, just use fewer threads.\n+\t\t\t */\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (pthread_create(&std->pthread_id, NULL,\n+\t\t\t\t   server_thread_proc, std)) {\n+\t\t\t/*\n+\t\t\t * Likewise, if we're out of threads, just use\n+\t\t\t * fewer threads than requested.\n+\t\t\t *\n+\t\t\t * However, we just give up if we can't even get\n+\t\t\t * one thread.  This should not happen.\n+\t\t\t */\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start thread[0] for '%s'\"),\n+\t\t\t\t    path);\n+\n+\t\t\tCloseHandle(std->hPipe);\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tstd->next_thread = server_data->thread_list;\n+\t\tserver_data->thread_list = std;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Gently tell all of the ipc_server threads to shutdown.\n+\t * This will be seen the next time they are idle (and waiting\n+\t * for a connection).\n+\t *\n+\t * We DO NOT attempt to force them to drop an active connection.\n+\t */\n+\tSetEvent(server_data->hEventStopRequested);\n+\treturn 0;\n+}\n+\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tDWORD dwWaitResult;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\tdwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE);\n+\tif (dwWaitResult != WAIT_OBJECT_0)\n+\t\treturn error(_(\"wait for hEvent failed for '%s'\"),\n+\t\t\t     server_data->buf_path.buf);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tpthread_join(std->pthread_id, NULL);\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tif (server_data->hEventStopRequested != INVALID_HANDLE_VALUE)\n+\t\tCloseHandle(server_data->hEventStopRequested);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tfree(server_data);\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 198ab1e58f8..76087cff678 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows)\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \t# USE_NED_ALLOCATOR = YesPlease\n@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \tUSE_NED_ALLOCATOR = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex c151dd7257f..4bd41054ee7 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n \tlist(APPEND compat_SOURCES unix-socket.c)\n endif()\n \n+if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+endif()\n+\n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\n \n #header checks\ndiff --git a/simple-ipc.h b/simple-ipc.h\nnew file mode 100644\nindex 00000000000..eb19b5da8b1\n--- /dev/null\n+++ b/simple-ipc.h\n@@ -0,0 +1,225 @@\n+#ifndef GIT_SIMPLE_IPC_H\n+#define GIT_SIMPLE_IPC_H\n+\n+/*\n+ * See Documentation/technical/api-simple-ipc.txt\n+ */\n+\n+#if defined(GIT_WINDOWS_NATIVE)\n+#define SUPPORTS_SIMPLE_IPC\n+#endif\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+#include \"pkt-line.h\"\n+\n+/*\n+ * Simple IPC Client Side API.\n+ */\n+\n+enum ipc_active_state {\n+\t/*\n+\t * The pipe/socket exists and the daemon is waiting for connections.\n+\t */\n+\tIPC_STATE__LISTENING = 0,\n+\n+\t/*\n+\t * The pipe/socket exists, but the daemon is not listening.\n+\t * Perhaps it is very busy.\n+\t * Perhaps the daemon died without deleting the path.\n+\t * Perhaps it is shutting down and draining existing clients.\n+\t * Perhaps it is dead, but other clients are lingering and\n+\t * still holding a reference to the pathname.\n+\t */\n+\tIPC_STATE__NOT_LISTENING,\n+\n+\t/*\n+\t * The requested pathname is bogus and no amount of retries\n+\t * will fix that.\n+\t */\n+\tIPC_STATE__INVALID_PATH,\n+\n+\t/*\n+\t * The requested pathname is not found.  This usually means\n+\t * that there is no daemon present.\n+\t */\n+\tIPC_STATE__PATH_NOT_FOUND,\n+\n+\tIPC_STATE__OTHER_ERROR,\n+};\n+\n+struct ipc_client_connect_options {\n+\t/*\n+\t * Spin under timeout if the server is running but can't\n+\t * accept our connection yet.  This should always be set\n+\t * unless you just want to poke the server and see if it\n+\t * is alive.\n+\t */\n+\tunsigned int wait_if_busy:1;\n+\n+\t/*\n+\t * Spin under timeout if the pipe/socket is not yet present\n+\t * on the file system.  This is useful if we just started\n+\t * the service and need to wait for it to become ready.\n+\t */\n+\tunsigned int wait_if_not_found:1;\n+};\n+\n+#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n+\t.wait_if_busy = 0, \\\n+\t.wait_if_not_found = 0, \\\n+}\n+\n+/*\n+ * Determine if a server is listening on this named pipe or socket using\n+ * platform-specific logic.  This might just probe the filesystem or it\n+ * might make a trivial connection to the server using this pathname.\n+ */\n+enum ipc_active_state ipc_get_active_state(const char *path);\n+\n+struct ipc_client_connection {\n+\tint fd;\n+\tstruct packet_scratch_space scratch_write_buffer;\n+};\n+\n+/*\n+ * Try to connect to the daemon on the named pipe or socket.\n+ *\n+ * Returns IPC_STATE__LISTENING and a connection handle.\n+ *\n+ * Otherwise, returns info to help decide whether to retry or to\n+ * spawn/respawn the server.\n+ */\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection);\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection);\n+\n+/*\n+ * Used by the client to synchronously send and receive a message with\n+ * the server on the provided client connection.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer);\n+\n+/*\n+ * Used by the client to synchronously connect and send and receive a\n+ * message to the server listening at the given path.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer);\n+\n+/*\n+ * Simple IPC Server Side API.\n+ */\n+\n+struct ipc_server_reply_data;\n+\n+typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *,\n+\t\t\t\t  const char *response,\n+\t\t\t\t  size_t response_len);\n+\n+/*\n+ * Prototype for an application-supplied callback to process incoming\n+ * client IPC messages and compose a reply.  The `application_cb` should\n+ * use the provided `reply_cb` and `reply_data` to send an IPC response\n+ * back to the client.  The `reply_cb` callback can be called multiple\n+ * times for chunking purposes.  A reply message is optional and may be\n+ * omitted if not necessary for the application.\n+ *\n+ * The return value from the application callback is ignored.\n+ * The value `SIMPLE_IPC_QUIT` can be used to shutdown the server.\n+ */\n+typedef int (ipc_server_application_cb)(void *application_data,\n+\t\t\t\t\tconst char *request,\n+\t\t\t\t\tipc_server_reply_cb *reply_cb,\n+\t\t\t\t\tstruct ipc_server_reply_data *reply_data);\n+\n+#define SIMPLE_IPC_QUIT -2\n+\n+/*\n+ * Opaque instance data to represent an IPC server instance.\n+ */\n+struct ipc_server_data;\n+\n+/*\n+ * Control parameters for the IPC server instance.\n+ * Use this to hide platform-specific settings.\n+ */\n+struct ipc_server_opts\n+{\n+\tint nr_threads;\n+};\n+\n+/*\n+ * Start an IPC server instance in one or more background threads\n+ * and return a handle to the pool.\n+ *\n+ * Returns 0 if the asynchronous server pool was started successfully.\n+ * Returns -1 if not.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data);\n+\n+/*\n+ * Gently signal the IPC server pool to shutdown.  No new client\n+ * connections will be accepted, but existing connections will be\n+ * allowed to complete.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data);\n+\n+/*\n+ * Block the calling thread until all threads in the IPC server pool\n+ * have completed and been joined.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data);\n+\n+/*\n+ * Close and free all resource handles associated with the IPC server\n+ * pool.\n+ */\n+void ipc_server_free(struct ipc_server_data *server_data);\n+\n+/*\n+ * Run an IPC server instance and block the calling thread of the\n+ * current process.  It does not return until the IPC server has\n+ * either shutdown or had an unrecoverable error.\n+ *\n+ * The IPC server handles incoming IPC messages from client processes\n+ * and may use one or more background threads as necessary.\n+ *\n+ * Returns 0 after the server has completed successfully.\n+ * Returns -1 if the server cannot be started.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ *\n+ * Note that `ipc_server_run()` is a synchronous wrapper around the\n+ * above asynchronous routines.  It effectively hides all of the\n+ * server state and thread details from the caller and presents a\n+ * simple synchronous interface.\n+ */\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data);\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\n+#endif /* GIT_SIMPLE_IPC_H */\n-- \ngitgitgadget\n\n"},{"id":"415800","messageId":"f5d5445cf42e2f107c5f137922e9887ea46d730d.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 10/14] unix-socket: elimiate static unix_stream_socket() helper function","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:43Z","receivedAt":"2021-02-01T19:49:21Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nThe static helper function `unix_stream_socket()` calls `die()`.  This is not\nappropriate for all callers.  Eliminate the wrapper function and move the\nexisting error handling to the callers in preparation for adapting specific\ncallers.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 17 +++++++----------\n 1 file changed, 7 insertions(+), 10 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 19ed48be990..ef2aeb46bcd 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,14 +1,6 @@\n #include \"cache.h\"\n #include \"unix-socket.h\"\n \n-static int unix_stream_socket(void)\n-{\n-\tint fd = socket(AF_UNIX, SOCK_STREAM, 0);\n-\tif (fd < 0)\n-\t\tdie_errno(\"unable to create socket\");\n-\treturn fd;\n-}\n-\n static int chdir_len(const char *orig, int len)\n {\n \tchar *path = xmemdupz(orig, len);\n@@ -79,7 +71,10 @@ int unix_stream_connect(const char *path)\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tdie_errno(\"unable to create socket\");\n+\n \tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \tunix_sockaddr_cleanup(&ctx);\n@@ -103,7 +98,9 @@ int unix_stream_listen(const char *path)\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tdie_errno(\"unable to create socket\");\n \n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n-- \ngitgitgadget\n\n"},{"id":"415801","messageId":"f0bebf1cdb31f94cb111df100b3bcb5e2d93a91e.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 09/14] simple-ipc: add t/helper/test-simple-ipc and t0052","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:42Z","receivedAt":"2021-02-01T19:49:34Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate unit tests for \"simple-ipc\".  These are currently only enabled\non Windows.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                   |   1 +\n t/helper/test-simple-ipc.c | 485 +++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0052-simple-ipc.sh      | 129 ++++++++++\n 5 files changed, 617 insertions(+)\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\ndiff --git a/Makefile b/Makefile\nindex c94d5847919..e7ba8853ea6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -740,6 +740,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o\n TEST_BUILTINS_OBJS += test-sha1.o\n TEST_BUILTINS_OBJS += test-sha256.o\n TEST_BUILTINS_OBJS += test-sigchain.o\n+TEST_BUILTINS_OBJS += test-simple-ipc.o\n TEST_BUILTINS_OBJS += test-strcmp-offset.o\n TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\ndiff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c\nnew file mode 100644\nindex 00000000000..4960e79cf18\n--- /dev/null\n+++ b/t/helper/test-simple-ipc.c\n@@ -0,0 +1,485 @@\n+/*\n+ * test-simple-ipc.c: verify that the Inter-Process Communication works.\n+ */\n+\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"simple-ipc.h\"\n+#include \"parse-options.h\"\n+#include \"thread-utils.h\"\n+\n+#ifndef SUPPORTS_SIMPLE_IPC\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tdie(\"simple IPC not available on this platform\");\n+}\n+#else\n+\n+/*\n+ * The test daemon defines an \"application callback\" that supports a\n+ * series of commands (see `test_app_cb()`).\n+ *\n+ * Unknown commands are caught here and we send an error message back\n+ * to the client process.\n+ */\n+static int app__unhandled_command(const char *command,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n+\n+\tstrbuf_addf(&buf, \"unhandled command: %s\", command);\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a single very large buffer.  This is to ensure that\n+ * long response are properly handled -- whether the chunking occurs\n+ * in the kernel or in the (probably pkt-line) layer.\n+ */\n+#define BIG_ROWS (10000)\n+static int app__big_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t    struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < BIG_ROWS; row++)\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a series of lines.  This is to ensure that we can incrementally\n+ * compute the response and chunk it to the client.\n+ */\n+#define CHUNK_ROWS (10000)\n+static int app__chunk_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t      struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < CHUNK_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Slowly reply with a series of lines.  This is to model an expensive to\n+ * compute chunked response (which might happen if this callback is running\n+ * in a thread and is fighting for a lock with other threads).\n+ */\n+#define SLOW_ROWS     (1000)\n+#define SLOW_DELAY_MS (10)\n+static int app__slow_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t     struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < SLOW_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t\tsleep_millisec(SLOW_DELAY_MS);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * The client sent a command followed by a (possibly very) large buffer.\n+ */\n+static int app__sendbytes_command(const char *received,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\tconst char *p = \"?\";\n+\tint len_ballast = 0;\n+\tint k;\n+\tint errs = 0;\n+\tint ret;\n+\n+\tif (skip_prefix(received, \"sendbytes \", &p))\n+\t\tlen_ballast = strlen(p);\n+\n+\t/*\n+\t * Verify that the ballast is n copies of a single letter.\n+\t * And that the multi-threaded IO layer didn't cross the streams.\n+\t */\n+\tfor (k = 1; k < len_ballast; k++)\n+\t\tif (p[k] != p[0])\n+\t\t\terrs++;\n+\n+\tif (errs)\n+\t\tstrbuf_addf(&buf_resp, \"errs:%d\\n\", errs);\n+\telse\n+\t\tstrbuf_addf(&buf_resp, \"rcvd:%c%08d\\n\", p[0], len_ballast);\n+\n+\tret = reply_cb(reply_data, buf_resp.buf, buf_resp.len);\n+\n+\tstrbuf_release(&buf_resp);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * An arbitrary fixed address to verify that the application instance\n+ * data is handled properly.\n+ */\n+static int my_app_data = 42;\n+\n+static ipc_server_application_cb test_app_cb;\n+\n+/*\n+ * This is \"application callback\" that sits on top of the \"ipc-server\".\n+ * It completely defines the set of command verbs supported by this\n+ * application.\n+ */\n+static int test_app_cb(void *application_data,\n+\t\t       const char *command,\n+\t\t       ipc_server_reply_cb *reply_cb,\n+\t\t       struct ipc_server_reply_data *reply_data)\n+{\n+\t/*\n+\t * Verify that we received the application-data that we passed\n+\t * when we started the ipc-server.  (We have several layers of\n+\t * callbacks calling callbacks and it's easy to get things mixed\n+\t * up (especially when some are \"void*\").)\n+\t */\n+\tif (application_data != (void*)&my_app_data)\n+\t\tBUG(\"application_cb: application_data pointer wrong\");\n+\n+\tif (!strcmp(command, \"quit\")) {\n+\t\t/*\n+\t\t * Tell ipc-server to hangup with an empty reply.\n+\t\t */\n+\t\treturn SIMPLE_IPC_QUIT;\n+\t}\n+\n+\tif (!strcmp(command, \"ping\")) {\n+\t\tconst char *answer = \"pong\";\n+\t\treturn reply_cb(reply_data, answer, strlen(answer));\n+\t}\n+\n+\tif (!strcmp(command, \"big\"))\n+\t\treturn app__big_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"chunk\"))\n+\t\treturn app__chunk_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"slow\"))\n+\t\treturn app__slow_command(reply_cb, reply_data);\n+\n+\tif (starts_with(command, \"sendbytes \"))\n+\t\treturn app__sendbytes_command(command, reply_cb, reply_data);\n+\n+\treturn app__unhandled_command(command, reply_cb, reply_data);\n+}\n+\n+/*\n+ * This process will run as a simple-ipc server and listen for IPC commands\n+ * from client processes.\n+ */\n+static int daemon__run_server(const char *path, int argc, const char **argv)\n+{\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = 5\n+\t};\n+\n+\tconst char * const daemon_usage[] = {\n+\t\tN_(\"test-helper simple-ipc daemon [<options>\"),\n+\t\tNULL\n+\t};\n+\tstruct option daemon_options[] = {\n+\t\tOPT_INTEGER(0, \"threads\", &opts.nr_threads,\n+\t\t\t    N_(\"number of threads in server thread pool\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);\n+\n+\tif (opts.nr_threads < 1)\n+\t\topts.nr_threads = 1;\n+\n+\t/*\n+\t * Synchronously run the ipc-server.  We don't need any application\n+\t * instance data, so pass an arbitrary pointer (that we'll later\n+\t * verify made the round trip).\n+\t */\n+\treturn ipc_server_run(path, &opts, test_app_cb, (void*)&my_app_data);\n+}\n+\n+/*\n+ * This process will run a quick probe to see if a simple-ipc server\n+ * is active on this path.\n+ *\n+ * Returns 0 if the server is alive.\n+ */\n+static int client__probe_server(const char *path)\n+{\n+\tenum ipc_active_state s;\n+\n+\ts = ipc_get_active_state(path);\n+\tswitch (s) {\n+\tcase IPC_STATE__LISTENING:\n+\t\treturn 0;\n+\n+\tcase IPC_STATE__NOT_LISTENING:\n+\t\treturn error(\"no server listening at '%s'\", path);\n+\n+\tcase IPC_STATE__PATH_NOT_FOUND:\n+\t\treturn error(\"path not found '%s'\", path);\n+\n+\tcase IPC_STATE__INVALID_PATH:\n+\t\treturn error(\"invalid pipe/socket name '%s'\", path);\n+\n+\tcase IPC_STATE__OTHER_ERROR:\n+\tdefault:\n+\t\treturn error(\"other error for '%s'\", path);\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command to an already-running server daemon and print the\n+ * response.\n+ *\n+ * argv[2] contains a simple (1 word) command verb that `test_app_cb()`\n+ * (in the daemon process) will understand.\n+ */\n+static int client__send_ipc(int argc, const char **argv, const char *path)\n+{\n+\tconst char *command = argc > 2 ? argv[2] : \"(no command)\";\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (!ipc_client_send_command(path, &options, command, &buf)) {\n+\t\tprintf(\"%s\\n\", buf.buf);\n+\t\tfflush(stdout);\n+\t\tstrbuf_release(&buf);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"failed to send '%s' to '%s'\", command, path);\n+}\n+\n+/*\n+ * Send an IPC command followed by ballast to confirm that a large\n+ * message can be sent and that the kernel or pkt-line layers will\n+ * properly chunk it and that the daemon receives the entire message.\n+ */\n+static int do_sendbytes(int bytecount, char byte, const char *path)\n+{\n+\tstruct strbuf buf_send = STRBUF_INIT;\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\n+\tstrbuf_addstr(&buf_send, \"sendbytes \");\n+\tstrbuf_addchars(&buf_send, byte, bytecount);\n+\n+\tif (!ipc_client_send_command(path, &options, buf_send.buf, &buf_resp)) {\n+\t\tstrbuf_rtrim(&buf_resp);\n+\t\tprintf(\"sent:%c%08d %s\\n\", byte, bytecount, buf_resp.buf);\n+\t\tfflush(stdout);\n+\t\tstrbuf_release(&buf_send);\n+\t\tstrbuf_release(&buf_resp);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"client failed to sendbytes(%d, '%c') to '%s'\",\n+\t\t     bytecount, byte, path);\n+}\n+\n+/*\n+ * Send an IPC command with ballast to an already-running server daemon.\n+ */\n+static int client__sendbytes(int argc, const char **argv, const char *path)\n+{\n+\tint bytecount = 1024;\n+\tchar *string = \"x\";\n+\tconst char * const sendbytes_usage[] = {\n+\t\tN_(\"test-helper simple-ipc sendbytes [<options>]\"),\n+\t\tNULL\n+\t};\n+\tstruct option sendbytes_options[] = {\n+\t\tOPT_INTEGER(0, \"bytecount\", &bytecount, N_(\"number of bytes\")),\n+\t\tOPT_STRING(0, \"byte\", &string, N_(\"byte\"), N_(\"ballast\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, sendbytes_options, sendbytes_usage, 0);\n+\n+\treturn do_sendbytes(bytecount, string[0], path);\n+}\n+\n+struct multiple_thread_data {\n+\tpthread_t pthread_id;\n+\tstruct multiple_thread_data *next;\n+\tconst char *path;\n+\tint bytecount;\n+\tint batchsize;\n+\tint sum_errors;\n+\tint sum_good;\n+\tchar letter;\n+};\n+\n+static void *multiple_thread_proc(void *_multiple_thread_data)\n+{\n+\tstruct multiple_thread_data *d = _multiple_thread_data;\n+\tint k;\n+\n+\ttrace2_thread_start(\"multiple\");\n+\n+\tfor (k = 0; k < d->batchsize; k++) {\n+\t\tif (do_sendbytes(d->bytecount + k, d->letter, d->path))\n+\t\t\td->sum_errors++;\n+\t\telse\n+\t\t\td->sum_good++;\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * Start a client-side thread pool.  Each thread sends a series of\n+ * IPC requests.  Each request is on a new connection to the server.\n+ */\n+static int client__multiple(int argc, const char **argv, const char *path)\n+{\n+\tstruct multiple_thread_data *list = NULL;\n+\tint k;\n+\tint nr_threads = 5;\n+\tint bytecount = 1;\n+\tint batchsize = 10;\n+\tint sum_join_errors = 0;\n+\tint sum_thread_errors = 0;\n+\tint sum_good = 0;\n+\n+\tconst char * const multiple_usage[] = {\n+\t\tN_(\"test-helper simple-ipc multiple [<options>]\"),\n+\t\tNULL\n+\t};\n+\tstruct option multiple_options[] = {\n+\t\tOPT_INTEGER(0, \"bytecount\", &bytecount, N_(\"number of bytes\")),\n+\t\tOPT_INTEGER(0, \"threads\", &nr_threads, N_(\"number of threads\")),\n+\t\tOPT_INTEGER(0, \"batchsize\", &batchsize, N_(\"number of requests per thread\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, multiple_options, multiple_usage, 0);\n+\n+\tif (bytecount < 1)\n+\t\tbytecount = 1;\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\tif (batchsize < 1)\n+\t\tbatchsize = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct multiple_thread_data *d = xcalloc(1, sizeof(*d));\n+\t\td->next = list;\n+\t\td->path = path;\n+\t\td->bytecount = bytecount + batchsize*(k/26);\n+\t\td->batchsize = batchsize;\n+\t\td->sum_errors = 0;\n+\t\td->sum_good = 0;\n+\t\td->letter = 'A' + (k % 26);\n+\n+\t\tif (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) {\n+\t\t\twarning(\"failed to create thread[%d] skipping remainder\", k);\n+\t\t\tfree(d);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tlist = d;\n+\t}\n+\n+\twhile (list) {\n+\t\tstruct multiple_thread_data *d = list;\n+\n+\t\tif (pthread_join(d->pthread_id, NULL))\n+\t\t\tsum_join_errors++;\n+\n+\t\tsum_thread_errors += d->sum_errors;\n+\t\tsum_good += d->sum_good;\n+\n+\t\tlist = d->next;\n+\t\tfree(d);\n+\t}\n+\n+\tprintf(\"client (good %d) (join %d), (errors %d)\\n\",\n+\t       sum_good, sum_join_errors, sum_thread_errors);\n+\n+\treturn (sum_join_errors + sum_thread_errors) ? 1 : 0;\n+}\n+\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tconst char *path = \"ipc-test\";\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"SUPPORTS_SIMPLE_IPC\"))\n+\t\treturn 0;\n+\n+\t/* Use '!!' on all dispatch functions to map from `error()` style\n+\t * (returns -1) style to `test_must_fail` style (expects 1) and\n+\t * get less confusing shell error messages.\n+\t */\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"is-active\"))\n+\t\treturn !!client__probe_server(path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"daemon\"))\n+\t\treturn !!daemon__run_server(path, argc, argv);\n+\n+\t/*\n+\t * Client commands follow.  Ensure a server is running before\n+\t * going any further.\n+\t */\n+\tif (client__probe_server(path))\n+\t\treturn 1;\n+\n+\tif ((argc == 2 || argc == 3) && !strcmp(argv[1], \"send\"))\n+\t\treturn !!client__send_ipc(argc, argv, path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"sendbytes\"))\n+\t\treturn !!client__sendbytes(argc, argv, path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"multiple\"))\n+\t\treturn !!client__multiple(argc, argv, path);\n+\n+\tdie(\"Unhandled argv[1]: '%s'\", argv[1]);\n+}\n+#endif\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 9d6d14d9293..a409655f03b 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -64,6 +64,7 @@ static struct test_cmd cmds[] = {\n \t{ \"sha1\", cmd__sha1 },\n \t{ \"sha256\", cmd__sha256 },\n \t{ \"sigchain\", cmd__sigchain },\n+\t{ \"simple-ipc\", cmd__simple_ipc },\n \t{ \"strcmp-offset\", cmd__strcmp_offset },\n \t{ \"string-list\", cmd__string_list },\n \t{ \"submodule-config\", cmd__submodule_config },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex a6470ff62c4..564eb3c8e91 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -54,6 +54,7 @@ int cmd__sha1(int argc, const char **argv);\n int cmd__oid_array(int argc, const char **argv);\n int cmd__sha256(int argc, const char **argv);\n int cmd__sigchain(int argc, const char **argv);\n+int cmd__simple_ipc(int argc, const char **argv);\n int cmd__strcmp_offset(int argc, const char **argv);\n int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\ndiff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\nnew file mode 100755\nindex 00000000000..69588354545\n--- /dev/null\n+++ b/t/t0052-simple-ipc.sh\n@@ -0,0 +1,129 @@\n+#!/bin/sh\n+\n+test_description='simple command server'\n+\n+. ./test-lib.sh\n+\n+test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {\n+\tskip_all='simple IPC not supported on this platform'\n+\ttest_done\n+}\n+\n+stop_simple_IPC_server () {\n+\ttest -n \"$SIMPLE_IPC_PID\" || return 0\n+\n+\tkill \"$SIMPLE_IPC_PID\" &&\n+\tSIMPLE_IPC_PID=\n+}\n+\n+test_expect_success 'start simple command server' '\n+\t{ test-tool simple-ipc daemon --threads=8 & } &&\n+\tSIMPLE_IPC_PID=$! &&\n+\ttest_atexit stop_simple_IPC_server &&\n+\n+\tsleep 1 &&\n+\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'simple command server' '\n+\ttest-tool simple-ipc send ping >actual &&\n+\techo pong >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'servers cannot share the same path' '\n+\ttest_must_fail test-tool simple-ipc daemon &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'big response' '\n+\ttest-tool simple-ipc send big >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'chunk response' '\n+\ttest-tool simple-ipc send chunk >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'slow response' '\n+\ttest-tool simple-ipc send slow >actual &&\n+\ttest_line_count -ge 100 actual &&\n+\tgrep -q \"big: [0]*99\\$\" actual\n+'\n+\n+# Send an IPC with n=100,000 bytes of ballast.  This should be large enough\n+# to force both the kernel and the pkt-line layer to chunk the message to the\n+# daemon and for the daemon to receive it in chunks.\n+#\n+test_expect_success 'sendbytes' '\n+\ttest-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual &&\n+\tgrep \"sent:A00100000 rcvd:A00100000\" actual\n+'\n+\n+# Start a series of <threads> client threads that each make <batchsize>\n+# IPC requests to the server.  Each (<threads> * <batchsize>) request\n+# will open a new connection to the server and randomly bind to a server\n+# thread.  Each client thread exits after completing its batch.  So the\n+# total number of live client threads will be smaller than the total.\n+# Each request will send a message containing at least <bytecount> bytes\n+# of ballast.  (Responses are small.)\n+#\n+# The purpose here is to test threading in the server and responding to\n+# many concurrent client requests (regardless of whether they come from\n+# 1 client process or many).  And to test that the server side of the\n+# named pipe/socket is stable.  (On Windows this means that the server\n+# pipe is properly recycled.)\n+#\n+# On Windows it also lets us adjust the connection timeout in the\n+# `ipc_client_send_command()`.\n+#\n+# Note it is easy to drive the system into failure by requesting an\n+# insane number of threads on client or server and/or increasing the\n+# per-thread batchsize or the per-request bytecount (ballast).\n+# On Windows these failures look like \"pipe is busy\" errors.\n+# So I've chosen fairly conservative values for now.\n+#\n+# We expect output of the form \"sent:<letter><length> ...\"\n+# With terms (7, 19, 13) we expect:\n+#   <letter> in [A-G]\n+#   <length> in [19+0 .. 19+(13-1)]\n+# and (7 * 13) successful responses.\n+#\n+test_expect_success 'stress test threads' '\n+\ttest-tool simple-ipc multiple \\\n+\t\t--threads=7 \\\n+\t\t--bytecount=19 \\\n+\t\t--batchsize=13 \\\n+\t\t>actual &&\n+\ttest_line_count = 92 actual &&\n+\tgrep \"good 91\" actual &&\n+\tgrep \"sent:A\" <actual >actual_a &&\n+\tcat >expect_a <<-EOF &&\n+\t\tsent:A00000019 rcvd:A00000019\n+\t\tsent:A00000020 rcvd:A00000020\n+\t\tsent:A00000021 rcvd:A00000021\n+\t\tsent:A00000022 rcvd:A00000022\n+\t\tsent:A00000023 rcvd:A00000023\n+\t\tsent:A00000024 rcvd:A00000024\n+\t\tsent:A00000025 rcvd:A00000025\n+\t\tsent:A00000026 rcvd:A00000026\n+\t\tsent:A00000027 rcvd:A00000027\n+\t\tsent:A00000028 rcvd:A00000028\n+\t\tsent:A00000029 rcvd:A00000029\n+\t\tsent:A00000030 rcvd:A00000030\n+\t\tsent:A00000031 rcvd:A00000031\n+\tEOF\n+\ttest_cmp expect_a actual_a\n+'\n+\n+test_expect_success '`quit` works' '\n+\ttest-tool simple-ipc send quit &&\n+\ttest_must_fail test-tool simple-ipc is-active &&\n+\ttest_must_fail test-tool simple-ipc send ping\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"415802","messageId":"745b6d5fb74699b7fe7e32080b18779aa4a82547.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 12/14] unix-socket: add no-chdir option to unix_stream_listen()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:45Z","receivedAt":"2021-02-01T19:49:45Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCalls to `chdir()` are dangerous in a multi-threaded context.  If\n`unix_stream_listen()` is given a socket pathname that is too big to\nfit in a `sockaddr_un` structure, it will `chdir()` to the parent\ndirectory of the requested socket pathname, create the socket using a\nrelative pathname, and then `chdir()` back.  This is not thread-safe.\n\nAdd `disallow_chdir` flag to `struct unix_sockaddr_context` and change\nall callers to pass an initialized context structure.\n\nTeach `unix_sockaddr_init()` to not allow calls to `chdir()` when flag\nis set.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 19 ++++++++++++++++---\n unix-socket.h |  2 ++\n 2 files changed, 18 insertions(+), 3 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 8bcef18ea55..9726992f276 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -11,8 +11,15 @@ static int chdir_len(const char *orig, int len)\n \n struct unix_sockaddr_context {\n \tchar *orig_dir;\n+\tunsigned int disallow_chdir:1;\n };\n \n+#define UNIX_SOCKADDR_CONTEXT_INIT \\\n+{ \\\n+\t.orig_dir=NULL, \\\n+\t.disallow_chdir=0, \\\n+}\n+\n static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n {\n \tif (!ctx->orig_dir)\n@@ -32,7 +39,11 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n {\n \tint size = strlen(path) + 1;\n \n-\tctx->orig_dir = NULL;\n+\tif (ctx->disallow_chdir && size > sizeof(sa->sun_path)) {\n+\t\terrno = ENAMETOOLONG;\n+\t\treturn -1;\n+\t}\n+\n \tif (size > sizeof(sa->sun_path)) {\n \t\tconst char *slash = find_last_dir_sep(path);\n \t\tconst char *dir;\n@@ -67,7 +78,7 @@ int unix_stream_connect(const char *path)\n {\n \tint fd, saved_errno;\n \tstruct sockaddr_un sa;\n-\tstruct unix_sockaddr_context ctx;\n+\tstruct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n@@ -96,7 +107,9 @@ int unix_stream_listen(const char *path,\n \tint bind_successful = 0;\n \tint backlog;\n \tstruct sockaddr_un sa;\n-\tstruct unix_sockaddr_context ctx;\n+\tstruct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;\n+\n+\tctx.disallow_chdir = opts->disallow_chdir;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\ndiff --git a/unix-socket.h b/unix-socket.h\nindex c28372ef48e..5b0e8ccef10 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -4,12 +4,14 @@\n struct unix_stream_listen_opts {\n \tint listen_backlog_size;\n \tunsigned int force_unlink_before_bind:1;\n+\tunsigned int disallow_chdir:1;\n };\n \n #define UNIX_STREAM_LISTEN_OPTS_INIT \\\n { \\\n \t.listen_backlog_size = 5, \\\n \t.force_unlink_before_bind = 1, \\\n+\t.disallow_chdir = 0, \\\n }\n \n int unix_stream_connect(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"415803","messageId":"e671894b4c0419138e66270aa9699053bdd504be.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 03/14] pkt-line: add write_packetized_from_buf2() that takes scratch buffer","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:36Z","receivedAt":"2021-02-01T19:49:47Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate version of `write_packetized_from_buf()` that takes a scratch buffer\nargument rather than assuming a static buffer.  This will be used later as\nwe make packet-line writing more thread-safe.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n pkt-line.c | 9 ++++++++-\n pkt-line.h | 2 ++\n 2 files changed, 10 insertions(+), 1 deletion(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex 14af049cd9c..5d86354cbeb 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -278,6 +278,13 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n {\n \tstatic struct packet_scratch_space scratch;\n+\n+\treturn write_packetized_from_buf2(src_in, len, fd_out, &scratch);\n+}\n+\n+int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n+\t\t\t       struct packet_scratch_space *scratch)\n+{\n \tint err = 0;\n \tsize_t bytes_written = 0;\n \tsize_t bytes_to_write;\n@@ -289,7 +296,7 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n \t\t\tbytes_to_write = len - bytes_written;\n \t\tif (bytes_to_write == 0)\n \t\t\tbreak;\n-\t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write, &scratch);\n+\t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write, scratch);\n \t\tbytes_written += bytes_to_write;\n \t}\n \tif (!err)\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 4ccd6f88926..f1d5625e91f 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -41,6 +41,8 @@ int packet_flush_gently(int fd);\n int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n int write_packetized_from_fd(int fd_in, int fd_out);\n int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n+int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n+\t\t\t       struct packet_scratch_space *scratch);\n \n /*\n  * Read a packetized line into the buffer, which must be at least size bytes\n-- \ngitgitgadget\n\n"},{"id":"415804","messageId":"3b03a8ff7a72c101f82a685cc6f34a5dd37a9c4b.1612208747.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v2 02/14] pkt-line: promote static buffer in packet_write_gently() to callers","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-01T19:45:35Z","receivedAt":"2021-02-01T19:49:54Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nMove the static buffer used in `packet_write_gently()` to its callers.\nThis is a first step to make packet writing more thread-safe.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n pkt-line.c | 33 ++++++++++++++++++++++++---------\n pkt-line.h | 10 ++++++++--\n 2 files changed, 32 insertions(+), 11 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex d633005ef74..14af049cd9c 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -194,26 +194,34 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)\n \treturn status;\n }\n \n-static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n+/*\n+ * Use the provided scratch space to build a combined <hdr><buf> buffer\n+ * and write it to the file descriptor (in one write if possible).\n+ */\n+static int packet_write_gently(const int fd_out, const char *buf, size_t size,\n+\t\t\t       struct packet_scratch_space *scratch)\n {\n-\tstatic char packet_write_buffer[LARGE_PACKET_MAX];\n \tsize_t packet_size;\n \n-\tif (size > sizeof(packet_write_buffer) - 4)\n+\tif (size > sizeof(scratch->buffer) - 4)\n \t\treturn error(_(\"packet write failed - data exceeds max packet size\"));\n \n \tpacket_trace(buf, size, 1);\n \tpacket_size = size + 4;\n-\tset_packet_header(packet_write_buffer, packet_size);\n-\tmemcpy(packet_write_buffer + 4, buf, size);\n-\tif (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)\n+\n+\tset_packet_header(scratch->buffer, packet_size);\n+\tmemcpy(scratch->buffer + 4, buf, size);\n+\n+\tif (write_in_full(fd_out, scratch->buffer, packet_size) < 0)\n \t\treturn error(_(\"packet write failed\"));\n \treturn 0;\n }\n \n void packet_write(int fd_out, const char *buf, size_t size)\n {\n-\tif (packet_write_gently(fd_out, buf, size))\n+\tstatic struct packet_scratch_space scratch;\n+\n+\tif (packet_write_gently(fd_out, buf, size, &scratch))\n \t\tdie_errno(_(\"packet write failed\"));\n }\n \n@@ -244,6 +252,12 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \n int write_packetized_from_fd(int fd_in, int fd_out)\n {\n+\t/*\n+\t * TODO We could save a memcpy() if we essentially inline\n+\t * TODO packet_write_gently() here and change the xread()\n+\t * TODO to pass &buf[4].\n+\t */\n+\tstatic struct packet_scratch_space scratch;\n \tstatic char buf[LARGE_PACKET_DATA_MAX];\n \tint err = 0;\n \tssize_t bytes_to_write;\n@@ -254,7 +268,7 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n \t\t\treturn COPY_READ_ERROR;\n \t\tif (bytes_to_write == 0)\n \t\t\tbreak;\n-\t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n+\t\terr = packet_write_gently(fd_out, buf, bytes_to_write, &scratch);\n \t}\n \tif (!err)\n \t\terr = packet_flush_gently(fd_out);\n@@ -263,6 +277,7 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n \n int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n {\n+\tstatic struct packet_scratch_space scratch;\n \tint err = 0;\n \tsize_t bytes_written = 0;\n \tsize_t bytes_to_write;\n@@ -274,7 +289,7 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n \t\t\tbytes_to_write = len - bytes_written;\n \t\tif (bytes_to_write == 0)\n \t\t\tbreak;\n-\t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n+\t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write, &scratch);\n \t\tbytes_written += bytes_to_write;\n \t}\n \tif (!err)\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 8c90daa59ef..4ccd6f88926 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -5,6 +5,13 @@\n #include \"strbuf.h\"\n #include \"sideband.h\"\n \n+#define LARGE_PACKET_MAX 65520\n+#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)\n+\n+struct packet_scratch_space {\n+\tchar buffer[LARGE_PACKET_MAX];\n+};\n+\n /*\n  * Write a packetized stream, where each line is preceded by\n  * its length (including the header) as a 4-byte hex number.\n@@ -213,8 +220,7 @@ enum packet_read_status packet_reader_read(struct packet_reader *reader);\n enum packet_read_status packet_reader_peek(struct packet_reader *reader);\n \n #define DEFAULT_PACKET_MAX 1000\n-#define LARGE_PACKET_MAX 65520\n-#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)\n+\n extern char packet_buffer[LARGE_PACKET_MAX];\n \n struct packet_writer {\n-- \ngitgitgadget\n\n"},{"id":"415837","messageId":"xmqq5z3bjuqs.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/14] Simple IPC Mechanism","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-01T22:20:11Z","receivedAt":"2021-02-01T22:21:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Here is version 2 of my \"Simple IPC\" series and addresses the following\n> review comments:\n> ...\n> Junio C Hamano (1):\n>   ci/install-depends: attempt to fix \"brew cask\" stuff\n\nHuh?\n"},{"id":"415845","messageId":"1be67634-4188-9ef3-306c-72b78ea856b5@jeffhostetler.com","threadId":"54978","inReplyTo":"xmqq5z3bjuqs.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 00/14] Simple IPC Mechanism","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-02-01T23:26:50Z","receivedAt":"2021-02-01T23:28:04Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/1/21 5:20 PM, Junio C Hamano wrote:\n> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> Here is version 2 of my \"Simple IPC\" series and addresses the following\n>> review comments:\n>> ...\n>> Junio C Hamano (1):\n>>    ci/install-depends: attempt to fix \"brew cask\" stuff\n> \n> Huh?\n> \n\nSorry.  I had to prepend that one to the patch series to get the\nCI builds to run.  I've been working rebased against \"v2.30.0\" and\nGitGitGadget references \"master\".\n\nJeff\n"},{"id":"415907","messageId":"YBkeYSA5UfQP1m/x@coredump.intra.peff.net","threadId":"54978","inReplyTo":"3b03a8ff7a72c101f82a685cc6f34a5dd37a9c4b.1612208747.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 02/14] pkt-line: promote static buffer in packet_write_gently() to callers","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-02T09:41:53Z","receivedAt":"2021-02-02T09:44:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Feb 01, 2021 at 07:45:35PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> -static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n> +/*\n> + * Use the provided scratch space to build a combined <hdr><buf> buffer\n> + * and write it to the file descriptor (in one write if possible).\n> + */\n> +static int packet_write_gently(const int fd_out, const char *buf, size_t size,\n> +\t\t\t       struct packet_scratch_space *scratch)\n\nThanks for addressing my stack space concern.\n\nThis solution does work (and I like wrapping it in a struct like this),\nthough I have to wonder if we're not just punting on the thread issues\nin an ever-so-slight way with things like this:\n\n>  void packet_write(int fd_out, const char *buf, size_t size)\n>  {\n> -\tif (packet_write_gently(fd_out, buf, size))\n> +\tstatic struct packet_scratch_space scratch;\n> +\n> +\tif (packet_write_gently(fd_out, buf, size, &scratch))\n>  \t\tdie_errno(_(\"packet write failed\"));\n>  }\n\nWhere we just moved it one step up the call stack.\n\n>  int write_packetized_from_fd(int fd_in, int fd_out)\n>  {\n> +\t/*\n> +\t * TODO We could save a memcpy() if we essentially inline\n> +\t * TODO packet_write_gently() here and change the xread()\n> +\t * TODO to pass &buf[4].\n> +\t */\n\nAnd comments like this make me wonder if the current crop of pktline\nfunctions are just mis-designed in the first place. There are two\nobvious directions here.\n\nOne, we can observe that the only reason we need the scratch space is to\nship out the whole thing in a single write():\n\n> [in packet_write_gently]\n> -\tset_packet_header(packet_write_buffer, packet_size);\n> -\tmemcpy(packet_write_buffer + 4, buf, size);\n> -\tif (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)\n> +\n> +\tset_packet_header(scratch->buffer, packet_size);\n> +\tmemcpy(scratch->buffer + 4, buf, size);\n> +\n> +\tif (write_in_full(fd_out, scratch->buffer, packet_size) < 0)\n>  \t\treturn error(_(\"packet write failed\"));\n\nWould it really be so bad to do:\n\n  char header[4];\n  set_packet_header(header, packet_size);\n  if (write_in_full(fd_out, header, 4) < 0 ||\n      write_in_full(fd_out, buf, size) < 0)\n          return error(...);\n\nI doubt that two syscalls is breaking the bank here, but if people are\nreally concerned, using writev() would be a much better solution.\nObviously we can't rely on it being available everywhere, but it's quite\neasy to emulate with a wrapper (and I'd be happy punt on any writev\nstuff until somebody actually measures a difference).\n\nThe other direction is that callers could be using a correctly-sized\nbuffer in the first place. I.e., something like:\n\n  struct packet_buffer {\n          char full_packet[LARGE_PACKET_MAX];\n  };\n  static inline char *packet_data(struct packet_buffer *pb)\n  {\n\treturn pb->full_packet + 4;\n  }\n\nThat lets people work with the oversized buffer in a natural-ish way\nthat would be hard to get wrong, like:\n\n  memcpy(packet_data(pb), some_other_buf, len);\n\n(though if we wanted to go even further, we could provide accessors that\nactually do the writing and sanity-check the lengths; the downside is\nthat I'm not sure how callers typically get the bytes into these bufs in\nthe first place).\n\nThat's a much bigger change, of course, and I'd guess you'd much prefer\nto focus on the actual point of your series. ;)\n\n-Peff\n"},{"id":"415908","messageId":"YBke/evrHFAC0se1@coredump.intra.peff.net","threadId":"54978","inReplyTo":"e671894b4c0419138e66270aa9699053bdd504be.1612208747.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 03/14] pkt-line: add write_packetized_from_buf2() that takes scratch buffer","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-02T09:44:29Z","receivedAt":"2021-02-02T09:45:50Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Feb 01, 2021 at 07:45:36PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n> Create version of `write_packetized_from_buf()` that takes a scratch buffer\n> argument rather than assuming a static buffer.  This will be used later as\n> we make packet-line writing more thread-safe.\n\nOK, this is extending the changes from the first patch...\n\n>  int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n>  {\n>  \tstatic struct packet_scratch_space scratch;\n> +\n> +\treturn write_packetized_from_buf2(src_in, len, fd_out, &scratch);\n> +}\n> +\n> +int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n> +\t\t\t       struct packet_scratch_space *scratch)\n\nOof, that name. I know we are guilty of a lot of \"foo_1()\" helpers for\nfoo(), but they are usually internal static functions that don't get\nspread around. This one is a public function.\n\nSomething like \"_with_scratch\" might be a bit more descriptive. Though\ngiven that there is exactly one caller of the original currently, I'd be\ntempted to say that it should just learn the scratch-space argument.\n\n(All of this is moot, of course, if you follow either of my suggestions\nfrom the earlier patch to drop the need for this scratch space\nentirely).\n\n-Peff\n"},{"id":"415909","messageId":"YBkf/KOv+YBZ5hQF@coredump.intra.peff.net","threadId":"54978","inReplyTo":"0832f7d324da643d7a480111d693ff5559c2b7a7.1612208747.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 04/14] pkt-line: optionally skip the flush packet in write_packetized_from_buf()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-02T09:48:44Z","receivedAt":"2021-02-02T09:49:44Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Feb 01, 2021 at 07:45:37PM +0000, Johannes Schindelin via GitGitGadget wrote:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> This function currently has only one caller: `apply_multi_file_filter()`\n> in `convert.c`. That caller wants a flush packet to be written after\n> writing the payload.\n> \n> However, we are about to introduce a user that wants to write many\n> packets before a final flush packet, so let's extend this function to\n> prepare for that scenario.\n\nI think this is a sign that the function is not very well-designed in\nthe first place. It seems like the code would be easier to understand\noverall if that caller just explicitly did the flush itself. It even\nalready does so in other cases!\n\nSomething like (untested):\n\n convert.c  | 4 ++++\n pkt-line.c | 4 ----\n 2 files changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07..3968ac37b9 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -890,6 +890,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \tif (err)\n \t\tgoto done;\n \n+\terr = packet_flush_gently(process->in);\n+\tif (err)\n+\t\tgoto done;\n+\n \terr = subprocess_read_status(process->out, &filter_status);\n \tif (err)\n \t\tgoto done;\ndiff --git a/pkt-line.c b/pkt-line.c\nindex d633005ef7..014520a9c2 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -256,8 +256,6 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n \t\t\tbreak;\n \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\n \n@@ -277,8 +275,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n \t\tbytes_written += bytes_to_write;\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\n \n\n-Peff\n"},{"id":"415910","messageId":"YBkhTdodhWkW9bF4@coredump.intra.peff.net","threadId":"54978","inReplyTo":"f5d5445cf42e2f107c5f137922e9887ea46d730d.1612208747.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 10/14] unix-socket: elimiate static unix_stream_socket() helper function","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-02T09:54:21Z","receivedAt":"2021-02-02T09:55:47Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Feb 01, 2021 at 07:45:43PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n> The static helper function `unix_stream_socket()` calls `die()`.  This is not\n> appropriate for all callers.  Eliminate the wrapper function and move the\n> existing error handling to the callers in preparation for adapting specific\n> callers.\n\nThanks, this looks good.\n\n> -static int unix_stream_socket(void)\n> -{\n> -\tint fd = socket(AF_UNIX, SOCK_STREAM, 0);\n> -\tif (fd < 0)\n> -\t\tdie_errno(\"unable to create socket\");\n> -\treturn fd;\n> -}\n\nThis could become a one-liner:\n\n  return socket(AF_UNIX, SOCK_STREAM, 0);\n\nto keep the details abstracted. But it's local to this file, the callers\nare already necessarily full of bsd-socket arcana, and it's not like the\nmagic words there have ever changed in 30+ years. Putting it inline\nseems quite reasonable. :)\n\n-Peff\n"},{"id":"415911","messageId":"YBkiO7hl71YwrifV@coredump.intra.peff.net","threadId":"54978","inReplyTo":"f5d5445cf42e2f107c5f137922e9887ea46d730d.1612208747.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 10/14] unix-socket: elimiate static unix_stream_socket() helper function","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-02T09:58:19Z","receivedAt":"2021-02-02T09:59:29Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Feb 01, 2021 at 07:45:43PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n>  static int chdir_len(const char *orig, int len)\n>  {\n>  \tchar *path = xmemdupz(orig, len);\n> @@ -79,7 +71,10 @@ int unix_stream_connect(const char *path)\n>  \n>  \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n>  \t\treturn -1;\n> -\tfd = unix_stream_socket();\n> +\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n> +\tif (fd < 0)\n> +\t\tdie_errno(\"unable to create socket\");\n> +\n\nReading the next patch, I suddenly realized that these are die calls,\nand not just passing along the error (which you then fix in the next\npatch). It seems like that should be happening here in this patch.\nCallers must already be ready to handle an error (we return -1 in the\ncontext above).\n\n> @@ -103,7 +98,9 @@ int unix_stream_listen(const char *path)\n>  \n>  \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n>  \t\treturn -1;\n> -\tfd = unix_stream_socket();\n> +\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n> +\tif (fd < 0)\n> +\t\tdie_errno(\"unable to create socket\");\n\nDitto here.\n\n-Peff\n"},{"id":"415913","messageId":"YBkmD14Nqqxe4pxG@coredump.intra.peff.net","threadId":"54978","inReplyTo":"7a6a69dfc20c6ff190cb020931c46bf4d88bab59.1612208747.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-02T10:14:39Z","receivedAt":"2021-02-02T10:15:29Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Feb 01, 2021 at 07:45:44PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n> Update `unix_stream_listen()` to take an options structure to override\n> default behaviors.  This includes the size of the `listen()` backlog\n> and whether it should always unlink the socket file before trying to\n> create a new one.  Also eliminate calls to `die()` if it cannot create\n> a socket.\n\nI sent a follow-up on the previous patch, but I think this part about\nthe die() should be folded in there.\n\nLikewise I think it would probably be easier to follow if we added the\nbacklog parameter and the unlink options in separate patches. The\nbacklog thing is small, but the unlink part is subtle and requires\nexplanation. That's a good sign it might do better in its own commit.\n\n> Normally, `unix_stream_listen()` always tries to `unlink()` the\n> socket-path before calling `bind()`.  If there is an existing\n> server/daemon already bound and listening on that socket-path, our\n> `unlink()` would have the effect of disassociating the existing\n> server's bound-socket-fd from the socket-path without notifying the\n> existing server.  The existing server could continue to service\n> existing connections (accepted-socket-fd's), but would not receive any\n> futher new connections (since clients rendezvous via the socket-path).\n> The existing server would effectively be offline but yet appear to be\n> active.\n> \n> Furthermore, `unix_stream_listen()` creates an opportunity for a brief\n> race condition for connecting clients if they try to connect in the\n> interval between the forced `unlink()` and the subsequent `bind()` (which\n> recreates the socket-path that is bound to a new socket-fd in the current\n> process).\n\nOK. I'm still not sure of the endgame here for writing non-racy code to\nestablish the socket (which is going to require either some atomic\nrenaming or some dot-locking in the caller).  But it's plausible to me\nthat this option will be a useful primitive.\n\nThe implementation looks correct, though here are a few small\nobservations/questions/nits:\n\n> -int unix_stream_listen(const char *path)\n> +int unix_stream_listen(const char *path,\n> +\t\t       const struct unix_stream_listen_opts *opts)\n>  {\n> -\tint fd, saved_errno;\n> +\tint fd = -1;\n> +\tint saved_errno;\n> +\tint bind_successful = 0;\n> +\tint backlog;\n>  \tstruct sockaddr_un sa;\n>  \tstruct unix_sockaddr_context ctx;\n>  \n> -\tunlink(path);\n> -\n>  \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n>  \t\treturn -1;\n\nWe can return directly here, because we know there is nothing to clean\nup. Which I thought mean that here...\n\n> +\n>  \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n>  \tif (fd < 0)\n> -\t\tdie_errno(\"unable to create socket\");\n> +\t\tgoto fail;\n\n...we are in the same boat. We did not create a socket, so we can just\nreturn. That makes our cleanup code a bit simpler. But we can't do that,\nbecause unix_sockaddr_init() may have done things that need cleaning up\n(like chdir). So what you have here is correct.\n\nIMHO that is all the more reason to push this (and the similar code in\nunix_stream_connect() added in patch 13) into the previous patch.\n\n> +\tif (opts->force_unlink_before_bind)\n> +\t\tunlink(path);\n>  \n>  \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n>  \t\tgoto fail;\n> +\tbind_successful = 1;\n\nAnd this one needs to mark a flag explicitly, because we have no other\nvisible way of knowing we need to do the unlink. Makes sense.\n\n> -\tif (listen(fd, 5) < 0)\n> +\tif (opts->listen_backlog_size > 0)\n> +\t\tbacklog = opts->listen_backlog_size;\n> +\telse\n> +\t\tbacklog = 5;\n> +\tif (listen(fd, backlog) < 0)\n\nThe default-to-5 is a bit funny here. We already set the default to 5 in\nUNIX_STREAM_LISTEN_OPTS_INIT. Should it be \"0\" there, so callers can\ntreat that as \"use the default\", which we fill in here? It probably\ndoesn't matter much in practice, but it seems cleaner to have only one\nspot with the magic number.\n\n> @@ -114,7 +125,10 @@ int unix_stream_listen(const char *path)\n>  fail:\n>  \tsaved_errno = errno;\n>  \tunix_sockaddr_cleanup(&ctx);\n> -\tclose(fd);\n> +\tif (fd != -1)\n> +\t\tclose(fd);\n> +\tif (bind_successful)\n> +\t\tunlink(path);\n>  \terrno = saved_errno;\n>  \treturn -1;\n>  }\n\nShould we unlink before closing? I usually try to undo actions in the\nreverse order that they were done. I thought at first it might even\nmatter here, such that we'd atomically relinquish the name without\nhaving a moment where it still points to a closed socket (which might be\nless confusing to somebody else trying to connect). But I guess there\nwill always be such a moment, because it's not like we would ever\naccept() or service a request.\n\n-Peff\n"},{"id":"415914","messageId":"YBko7m8TJgTm/7lQ@coredump.intra.peff.net","threadId":"54978","inReplyTo":"745b6d5fb74699b7fe7e32080b18779aa4a82547.1612208747.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 12/14] unix-socket: add no-chdir option to unix_stream_listen()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-02T10:26:54Z","receivedAt":"2021-02-02T10:27:52Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Feb 01, 2021 at 07:45:45PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n> Calls to `chdir()` are dangerous in a multi-threaded context.  If\n> `unix_stream_listen()` is given a socket pathname that is too big to\n> fit in a `sockaddr_un` structure, it will `chdir()` to the parent\n> directory of the requested socket pathname, create the socket using a\n> relative pathname, and then `chdir()` back.  This is not thread-safe.\n> \n> Add `disallow_chdir` flag to `struct unix_sockaddr_context` and change\n> all callers to pass an initialized context structure.\n> \n> Teach `unix_sockaddr_init()` to not allow calls to `chdir()` when flag\n> is set.\n\nMakes sense, and it fits nicely into the options pattern you set up in\nthe earlier patch.\n\n>  struct unix_sockaddr_context {\n>  \tchar *orig_dir;\n> +\tunsigned int disallow_chdir:1;\n>  };\n>  \n> +#define UNIX_SOCKADDR_CONTEXT_INIT \\\n> +{ \\\n> +\t.orig_dir=NULL, \\\n> +\t.disallow_chdir=0, \\\n> +}\n\nIt is really just zero-initializing, so \"{ 0 }\" would be OK (I think we\nare relaxed about allowing 0 as NULL in initializers). But I don't mind\nit being written out (but do mind whitespace around the \"=\").\n\nHowever, the point of unix_sockaddr_init() is that it's supposed to\ninitialize the struct. And I don't think we need to carry disallow_chdir\naround; the cleanup function knows from orig_dir whether it's supposed\nto do any cleanup, so only the init function has to care. So would:\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 19ed48be99..0eb14faf54 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -36,16 +36,23 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n }\n \n static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n-\t\t\t      struct unix_sockaddr_context *ctx)\n+\t\t\t      struct unix_sockaddr_context *ctx,\n+\t\t\t      int disallow_chdir)\n {\n \tint size = strlen(path) + 1;\n \n \tctx->orig_dir = NULL;\n \tif (size > sizeof(sa->sun_path)) {\n-\t\tconst char *slash = find_last_dir_sep(path);\n+\t\tconst char *slash;\n \t\tconst char *dir;\n \t\tstruct strbuf cwd = STRBUF_INIT;\n \n+\t\tif (disallow_chdir) {\n+\t\t\terrno = ENAMETOOLONG;\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tslash = find_last_dir_sep(path);\n \t\tif (!slash) {\n \t\t\terrno = ENAMETOOLONG;\n \t\t\treturn -1;\n\nmake it more obvious? There are only two callers, and this is all\nfile-local, so I don't mind adding the extra parameter there. And you\nwould not need an initializer at all.\n\n>  #define UNIX_STREAM_LISTEN_OPTS_INIT \\\n>  { \\\n>  \t.listen_backlog_size = 5, \\\n>  \t.force_unlink_before_bind = 1, \\\n> +\t.disallow_chdir = 0, \\\n>  }\n\nI don't know if we care, but some options are positive \"do this unlink\"\nand some are negative \"do not do this chdir\". Those could be made\nconsistent (and flip the initializer value to keep the same defaults).\n\nThere is actually value in making struct defaults generally \"0\" unless\nwe have reason not to, because callers sometimes zero-initialize without\nthinking about it. I doubt that would happen for this particular struct,\nand I'm deep into bike-shedding anyway, so I'm OK either way. But\nsomething like:\n\n  struct unix_stream_listen_opts_init {\n\tint listen_backlog_size;\n\tint disallow_unlink;\n\tint disallow_chdir;\n  };\n\nwould work with just a \"{ 0 }\" zero-initializer. :)\n\n-Peff\n"},{"id":"415958","messageId":"e9f21318-8083-61e1-9d26-bc9cd5947d28@jeffhostetler.com","threadId":"54978","inReplyTo":"YBkeYSA5UfQP1m/x@coredump.intra.peff.net","subject":"Re: [PATCH v2 02/14] pkt-line: promote static buffer in packet_write_gently() to callers","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-02-02T20:33:57Z","receivedAt":"2021-02-02T20:34:55Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/2/21 4:41 AM, Jeff King wrote:\n> On Mon, Feb 01, 2021 at 07:45:35PM +0000, Jeff Hostetler via GitGitGadget wrote:\n> \n>> -static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n>> +/*\n>> + * Use the provided scratch space to build a combined <hdr><buf> buffer\n>> + * and write it to the file descriptor (in one write if possible).\n>> + */\n>> +static int packet_write_gently(const int fd_out, const char *buf, size_t size,\n>> +\t\t\t       struct packet_scratch_space *scratch)\n> \n> Thanks for addressing my stack space concern.\n> \n> This solution does work (and I like wrapping it in a struct like this),\n> though I have to wonder if we're not just punting on the thread issues\n> in an ever-so-slight way with things like this:\n> \n>>   void packet_write(int fd_out, const char *buf, size_t size)\n>>   {\n>> -\tif (packet_write_gently(fd_out, buf, size))\n>> +\tstatic struct packet_scratch_space scratch;\n>> +\n>> +\tif (packet_write_gently(fd_out, buf, size, &scratch))\n>>   \t\tdie_errno(_(\"packet write failed\"));\n>>   }\n> \n> Where we just moved it one step up the call stack.\n> \n>>   int write_packetized_from_fd(int fd_in, int fd_out)\n>>   {\n>> +\t/*\n>> +\t * TODO We could save a memcpy() if we essentially inline\n>> +\t * TODO packet_write_gently() here and change the xread()\n>> +\t * TODO to pass &buf[4].\n>> +\t */\n> \n> And comments like this make me wonder if the current crop of pktline\n> functions are just mis-designed in the first place. There are two\n> obvious directions here.\n> \n> One, we can observe that the only reason we need the scratch space is to\n> ship out the whole thing in a single write():\n> \n>> [in packet_write_gently]\n>> -\tset_packet_header(packet_write_buffer, packet_size);\n>> -\tmemcpy(packet_write_buffer + 4, buf, size);\n>> -\tif (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)\n>> +\n>> +\tset_packet_header(scratch->buffer, packet_size);\n>> +\tmemcpy(scratch->buffer + 4, buf, size);\n>> +\n>> +\tif (write_in_full(fd_out, scratch->buffer, packet_size) < 0)\n>>   \t\treturn error(_(\"packet write failed\"));\n> \n> Would it really be so bad to do:\n> \n>    char header[4];\n>    set_packet_header(header, packet_size);\n>    if (write_in_full(fd_out, header, 4) < 0 ||\n>        write_in_full(fd_out, buf, size) < 0)\n>            return error(...);\n> \n> I doubt that two syscalls is breaking the bank here, but if people are\n> really concerned, using writev() would be a much better solution.\n> Obviously we can't rely on it being available everywhere, but it's quite\n> easy to emulate with a wrapper (and I'd be happy punt on any writev\n> stuff until somebody actually measures a difference).\n> \n> The other direction is that callers could be using a correctly-sized\n> buffer in the first place. I.e., something like:\n> \n>    struct packet_buffer {\n>            char full_packet[LARGE_PACKET_MAX];\n>    };\n>    static inline char *packet_data(struct packet_buffer *pb)\n>    {\n> \treturn pb->full_packet + 4;\n>    }\n> \n> That lets people work with the oversized buffer in a natural-ish way\n> that would be hard to get wrong, like:\n> \n>    memcpy(packet_data(pb), some_other_buf, len);\n> \n> (though if we wanted to go even further, we could provide accessors that\n> actually do the writing and sanity-check the lengths; the downside is\n> that I'm not sure how callers typically get the bytes into these bufs in\n> the first place).\n> \n> That's a much bigger change, of course, and I'd guess you'd much prefer\n> to focus on the actual point of your series. ;)\n> \n> -Peff\n> \n\nYeah, I had all of those thoughts and debates in my head.  I'm not sure\nthere is a clear winner here.  And I was trying to prevent this change\nfrom having a massive footprint and all that.  The FSMonitor stuff is\nenough to worry about...\n\nPersonally, I like the 2 syscall model (for now at least and not mess\nwith writev()).  There are only 3 calls to packet_write_gently() and\nthis fixes 2 of them without any local buffers.  I might as well update\nthe 1 caller of write_packetized_from_fd() to pass a buffer rather than\nhave a static buffer while we're at it.  Then all of those routines\nare fixed.\n\nLet me see what that looks like.\n\nThanks\nJeff\n"},{"id":"415963","messageId":"20210202213523.GD2091@szeder.dev","threadId":"54978","inReplyTo":"f0bebf1cdb31f94cb111df100b3bcb5e2d93a91e.1612208747.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 09/14] simple-ipc: add t/helper/test-simple-ipc and t0052","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2021-02-02T21:35:23Z","receivedAt":"2021-02-02T21:36:10Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Mon, Feb 01, 2021 at 07:45:42PM +0000, Jeff Hostetler via GitGitGadget wrote:\n> diff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\n> new file mode 100755\n> index 00000000000..69588354545\n> --- /dev/null\n> +++ b/t/t0052-simple-ipc.sh\n> @@ -0,0 +1,129 @@\n> +#!/bin/sh\n> +\n> +test_description='simple command server'\n> +\n> +. ./test-lib.sh\n> +\n> +test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {\n> +\tskip_all='simple IPC not supported on this platform'\n> +\ttest_done\n> +}\n> +\n> +stop_simple_IPC_server () {\n> +\ttest -n \"$SIMPLE_IPC_PID\" || return 0\n> +\n> +\tkill \"$SIMPLE_IPC_PID\" &&\n> +\tSIMPLE_IPC_PID=\n> +}\n> +\n> +test_expect_success 'start simple command server' '\n> +\t{ test-tool simple-ipc daemon --threads=8 & } &&\n> +\tSIMPLE_IPC_PID=$! &&\n> +\ttest_atexit stop_simple_IPC_server &&\n> +\n> +\tsleep 1 &&\n\nThis will certainly lead to occasional failures when the daemon takes\nlonger than that mere 1 second delay under heavy load or in CI jobs.\n\n> +\n> +\ttest-tool simple-ipc is-active\n> +'\n"},{"id":"415973","messageId":"nycvar.QRO.7.76.6.2102022340460.54@tvgsbejvaqbjf.bet","threadId":"54978","inReplyTo":"YBkeYSA5UfQP1m/x@coredump.intra.peff.net","subject":"Re: [PATCH v2 02/14] pkt-line: promote static buffer in packet_write_gently() to callers","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-02-02T22:54:43Z","receivedAt":"2021-02-02T22:56:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Tue, 2 Feb 2021, Jeff King wrote:\n\n> On Mon, Feb 01, 2021 at 07:45:35PM +0000, Jeff Hostetler via GitGitGadget wrote:\n>\n> > [in packet_write_gently]\n> > -\tset_packet_header(packet_write_buffer, packet_size);\n> > -\tmemcpy(packet_write_buffer + 4, buf, size);\n> > -\tif (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)\n> > +\n> > +\tset_packet_header(scratch->buffer, packet_size);\n> > +\tmemcpy(scratch->buffer + 4, buf, size);\n> > +\n> > +\tif (write_in_full(fd_out, scratch->buffer, packet_size) < 0)\n> >  \t\treturn error(_(\"packet write failed\"));\n>\n> Would it really be so bad to do:\n>\n>   char header[4];\n>   set_packet_header(header, packet_size);\n>   if (write_in_full(fd_out, header, 4) < 0 ||\n>       write_in_full(fd_out, buf, size) < 0)\n>           return error(...);\n\nThere must have been a reason why the original code went out of its way to\ncopy the data. At least that's what I _assume_.\n\nI could see, for example, that these extra round-trips just for the\nheader, really have a negative impact on network operations.\n\n> I doubt that two syscalls is breaking the bank here, but if people are\n> really concerned, using writev() would be a much better solution.\n\nNo, because there is no equivalent for that on Windows. And since Windows\nis the primary target of our Simple IPC/FSMonitor work, that would break\nthe bank.\n\n> Obviously we can't rely on it being available everywhere, but it's quite\n> easy to emulate with a wrapper (and I'd be happy punt on any writev\n> stuff until somebody actually measures a difference).\n>\n> The other direction is that callers could be using a correctly-sized\n> buffer in the first place. I.e., something like:\n>\n>   struct packet_buffer {\n>           char full_packet[LARGE_PACKET_MAX];\n>   };\n>   static inline char *packet_data(struct packet_buffer *pb)\n>   {\n> \treturn pb->full_packet + 4;\n>   }\n\nOr we change it to\n\n\tstruct packet_buffer {\n\t\tchar count[4];\n\t\tchar payload[LARGE_PACKET_MAX - 4];\n\t};\n\nand then ask the callers to allocate one of those beauties\nNot sure how well we can guarantee that the compiler won't pad this,\nthough.\n\nAnd then there is `write_packetized_from_buf()` whose `src` parameter can\ncome from `convert_to_git()` that _definitely_ would not be of the desired\nform.\n\nSo I guess if we can get away with the 2-syscall version, that's kind of\nbetter than that.\n\nCiao,\nDscho\n\n>\n> That lets people work with the oversized buffer in a natural-ish way\n> that would be hard to get wrong, like:\n>\n>   memcpy(packet_data(pb), some_other_buf, len);\n>\n> (though if we wanted to go even further, we could provide accessors that\n> actually do the writing and sanity-check the lengths; the downside is\n> that I'm not sure how callers typically get the bytes into these bufs in\n> the first place).\n>\n> That's a much bigger change, of course, and I'd guess you'd much prefer\n> to focus on the actual point of your series. ;)\n>\n> -Peff\n>\n"},{"id":"415974","messageId":"nycvar.QRO.7.76.6.2102022355590.54@tvgsbejvaqbjf.bet","threadId":"54978","inReplyTo":"YBkf/KOv+YBZ5hQF@coredump.intra.peff.net","subject":"Re: [PATCH v2 04/14] pkt-line: optionally skip the flush packet in write_packetized_from_buf()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-02-02T22:56:12Z","receivedAt":"2021-02-02T22:58:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\n\nOn Tue, 2 Feb 2021, Jeff King wrote:\n\n> On Mon, Feb 01, 2021 at 07:45:37PM +0000, Johannes Schindelin via GitGitGadget wrote:\n>\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > This function currently has only one caller: `apply_multi_file_filter()`\n> > in `convert.c`. That caller wants a flush packet to be written after\n> > writing the payload.\n> >\n> > However, we are about to introduce a user that wants to write many\n> > packets before a final flush packet, so let's extend this function to\n> > prepare for that scenario.\n>\n> I think this is a sign that the function is not very well-designed in\n> the first place. It seems like the code would be easier to understand\n> overall if that caller just explicitly did the flush itself. It even\n> already does so in other cases!\n>\n> Something like (untested):\n\nFine by me.\n\nThanks,\nDscho\n\n>\n>  convert.c  | 4 ++++\n>  pkt-line.c | 4 ----\n>  2 files changed, 4 insertions(+), 4 deletions(-)\n>\n> diff --git a/convert.c b/convert.c\n> index ee360c2f07..3968ac37b9 100644\n> --- a/convert.c\n> +++ b/convert.c\n> @@ -890,6 +890,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n>  \tif (err)\n>  \t\tgoto done;\n>\n> +\terr = packet_flush_gently(process->in);\n> +\tif (err)\n> +\t\tgoto done;\n> +\n>  \terr = subprocess_read_status(process->out, &filter_status);\n>  \tif (err)\n>  \t\tgoto done;\n> diff --git a/pkt-line.c b/pkt-line.c\n> index d633005ef7..014520a9c2 100644\n> --- a/pkt-line.c\n> +++ b/pkt-line.c\n> @@ -256,8 +256,6 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n>  \t\t\tbreak;\n>  \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n>  \t}\n> -\tif (!err)\n> -\t\terr = packet_flush_gently(fd_out);\n>  \treturn err;\n>  }\n>\n> @@ -277,8 +275,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n>  \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n>  \t\tbytes_written += bytes_to_write;\n>  \t}\n> -\tif (!err)\n> -\t\terr = packet_flush_gently(fd_out);\n>  \treturn err;\n>  }\n>\n>\n> -Peff\n>\n"},{"id":"415975","messageId":"nycvar.QRO.7.76.6.2102030003300.54@tvgsbejvaqbjf.bet","threadId":"54978","inReplyTo":"1be67634-4188-9ef3-306c-72b78ea856b5@jeffhostetler.com","subject":"Re: [PATCH v2 00/14] Simple IPC Mechanism","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-02-02T23:07:32Z","receivedAt":"2021-02-02T23:09:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio & Jeff,\n\nOn Mon, 1 Feb 2021, Jeff Hostetler wrote:\n\n> On 2/1/21 5:20 PM, Junio C Hamano wrote:\n> > \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n> > > Here is version 2 of my \"Simple IPC\" series and addresses the following\n> > > review comments:\n> > > ...\n> > > Junio C Hamano (1):\n> > >    ci/install-depends: attempt to fix \"brew cask\" stuff\n> >\n> > Huh?\n> >\n>\n> Sorry.  I had to prepend that one to the patch series to get the\n> CI builds to run.  I've been working rebased against \"v2.30.0\" and\n> GitGitGadget references \"master\".\n\nThe idea being that we want to be able to merge this branch as-is into Git\nfor Windows (and also into microsoft/git), and therefore do not want to\nbase it on a later commit that is not reachable from git-for-windows/git's\n`main` branch.\n\nMaybe it is time to merge `jc/macos-install-dependencies-fix` down to\n`maint`? Then we could base Simple IPC/FSMonitor on `maint` instead, and\nwould still have the benefit we want.\n\nCiao,\nDscho\n"},{"id":"416023","messageId":"YBooReQcjsi41VsA@coredump.intra.peff.net","threadId":"54978","inReplyTo":"20210202213523.GD2091@szeder.dev","subject":"Re: [PATCH v2 09/14] simple-ipc: add t/helper/test-simple-ipc and t0052","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-03T04:36:21Z","receivedAt":"2021-02-03T04:37:21Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 02, 2021 at 10:35:23PM +0100, SZEDER Gábor wrote:\n\n> > +test_expect_success 'start simple command server' '\n> > +\t{ test-tool simple-ipc daemon --threads=8 & } &&\n> > +\tSIMPLE_IPC_PID=$! &&\n> > +\ttest_atexit stop_simple_IPC_server &&\n> > +\n> > +\tsleep 1 &&\n> \n> This will certainly lead to occasional failures when the daemon takes\n> longer than that mere 1 second delay under heavy load or in CI jobs.\n\nYeah. The robust thing is to have the server indicate when it's ready to\nreceive requests. There's some prior art in t/lib-git-daemon.sh using a\nfifo to get a line to the caller. It's ugly, but AFAIK pretty\nbulletproof.\n\n-Peff\n"},{"id":"416025","messageId":"YBosBuyCmBzYiRfv@coredump.intra.peff.net","threadId":"54978","inReplyTo":"nycvar.QRO.7.76.6.2102022340460.54@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 02/14] pkt-line: promote static buffer in packet_write_gently() to callers","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-03T04:52:22Z","receivedAt":"2021-02-03T04:53:21Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 02, 2021 at 11:54:43PM +0100, Johannes Schindelin wrote:\n\n> > Would it really be so bad to do:\n> >\n> >   char header[4];\n> >   set_packet_header(header, packet_size);\n> >   if (write_in_full(fd_out, header, 4) < 0 ||\n> >       write_in_full(fd_out, buf, size) < 0)\n> >           return error(...);\n> \n> There must have been a reason why the original code went out of its way to\n> copy the data. At least that's what I _assume_.\n\nHaving looked at the history, including the original mailing list\nthreads, it doesn't seem to be.\n\n> I could see, for example, that these extra round-trips just for the\n> header, really have a negative impact on network operations.\n\nKeep in mind these won't be network round-trips. They're just syscall\nround-trips. The OS would keep writing without an ACK while filling a\nTCP window. The worst case may be an extra packet on the wire, though\nthe OS may end up coalescing the writes into a single packet anyway.\n\n> > I doubt that two syscalls is breaking the bank here, but if people are\n> > really concerned, using writev() would be a much better solution.\n> \n> No, because there is no equivalent for that on Windows. And since Windows\n> is the primary target of our Simple IPC/FSMonitor work, that would break\n> the bank.\n\nAre you concerned about the performance implications, or just\nportability? Falling back to two writes (and wrapping that in a\nfunction) would be easy for the latter. For the former, there's WSASend,\nbut I have no idea what kind of difficulties/caveats we might run into.\n\n> > The other direction is that callers could be using a correctly-sized\n> > buffer in the first place. I.e., something like:\n> >\n> >   struct packet_buffer {\n> >           char full_packet[LARGE_PACKET_MAX];\n> >   };\n> >   static inline char *packet_data(struct packet_buffer *pb)\n> >   {\n> > \treturn pb->full_packet + 4;\n> >   }\n> \n> Or we change it to\n> \n> \tstruct packet_buffer {\n> \t\tchar count[4];\n> \t\tchar payload[LARGE_PACKET_MAX - 4];\n> \t};\n> \n> and then ask the callers to allocate one of those beauties\n> Not sure how well we can guarantee that the compiler won't pad this,\n> though.\n\nYeah, I almost suggested the same, but wasn't sure about padding. I\nthink the standard allows there to be arbitrary padding between the two,\nso it's really up to the ABI to define. I'd be surprised if this struct\nis a problem in practice (we already have some structs which assume\n4-byte alignment, and nobody seems to have complained).\n\n> And then there is `write_packetized_from_buf()` whose `src` parameter can\n> come from `convert_to_git()` that _definitely_ would not be of the desired\n> form.\n\nYep. It really does need to either use two writes or to copy, because\nit's slicing up a much larger buffer (it wouldn't be the end of the\nworld for it to allocate a single LARGE_PACKET_MAX heap buffer for the\nduration of its run, though).\n\n> So I guess if we can get away with the 2-syscall version, that's kind of\n> better than that.\n\nI do prefer it, because then the whole thing just becomes an\nimplementation detail that callers don't need to care about.\n\n-Peff\n"},{"id":"416176","messageId":"xmqqmtwj647t.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"nycvar.QRO.7.76.6.2102030003300.54@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 00/14] Simple IPC Mechanism","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-04T19:08:22Z","receivedAt":"2021-02-04T19:10:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> The idea being that we want to be able to merge this branch as-is into Git\n> for Windows (and also into microsoft/git), and therefore do not want to\n> base it on a later commit that is not reachable from git-for-windows/git's\n> `main` branch.\n>\n> Maybe it is time to merge `jc/macos-install-dependencies-fix` down to\n> `maint`? Then we could base Simple IPC/FSMonitor on `maint` instead, and\n> would still have the benefit we want.\n\nNow you mention it, if we ever have an update to the current 'maint'\nbranch, we'd trigger this known failure in macOS build without a\ngood reason, even though we have a fix that has been in use on the\n'master' and above.\n\nI definitely qshould merge the jc/macos-install-dependencies-fix\ntopic down to 'maint' now.\n\nAre there other topics that deserve to be in 'maint' that are\n\"obviously correct\" people can think of?\n\nThanks.\n\n"},{"id":"416258","messageId":"nycvar.QRO.7.76.6.2102051356210.54@tvgsbejvaqbjf.bet","threadId":"54978","inReplyTo":"xmqqmtwj647t.fsf@gitster.c.googlers.com","subject":"candidate branches for `maint`, was Re: [PATCH v2 00/14] Simple IPC Mechanism","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-02-05T13:19:19Z","receivedAt":"2021-02-05T13:24:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Thu, 4 Feb 2021, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n> > The idea being that we want to be able to merge this branch as-is into Git\n> > for Windows (and also into microsoft/git), and therefore do not want to\n> > base it on a later commit that is not reachable from git-for-windows/git's\n> > `main` branch.\n> >\n> > Maybe it is time to merge `jc/macos-install-dependencies-fix` down to\n> > `maint`? Then we could base Simple IPC/FSMonitor on `maint` instead, and\n> > would still have the benefit we want.\n>\n> Now you mention it, if we ever have an update to the current 'maint'\n> branch, we'd trigger this known failure in macOS build without a\n> good reason, even though we have a fix that has been in use on the\n> 'master' and above.\n>\n> I definitely qshould merge the jc/macos-install-dependencies-fix\n> topic down to 'maint' now.\n>\n> Are there other topics that deserve to be in 'maint' that are\n> \"obviously correct\" people can think of?\n\nI looked over the branches merged into `master` that are not in `maint`,\nand from a cursory look, these seem to be good candidates:\n\n- pk/subsub-fetch-fix-take-2\n- rs/rebase-commit-validation\n- en/stash-apply-sparse-checkout\n- ds/for-each-repo-noopfix\n- pb/mergetool-tool-help-fix\n- jk/t5516-deflake\n- jc/macos-install-dependencies-fix\n- jk/forbid-lf-in-git-url\n- jk/log-cherry-pick-duplicate-patches\n- js/skip-dashed-built-ins-from-config-mak\n\nFrom a cursory look, it might seem as if ds/maintenance-prefetch-cleanup\nwould also be a good candidate, but it is not based on `maint`, although\nit _does_ appear to fix an issue introduced in v2.30.0-rc0~23^2~8.\n\nThere is another candidate that I am not _quite_ sure about:\ndl/p4-encode-after-kw-expansion. It _seems_ as if it would be good to\napply on the maintenance train, but I am uncertain how important a bug fix\nit is.\n\nThere are also a couple test updates that might be nice to have in\n`maint`:\n\n- nk/perf-fsmonitor-cleanup\n- mt/t4129-with-setgid-dir\n- ad/t4129-setfacl-target-fix\n\nFinally, there are documentation updates that I would probably merge, if I\nwas tasked with updating `maint`:\n\n- ta/doc-typofix\n- pb/doc-modules-git-work-tree-typofix\n- jc/sign-off\n- vv/send-email-with-less-secure-apps-access\n- ug/doc-lose-dircache\n- ab/gettext-charset-comment-fix\n- bc/doc-status-short\n- tb/local-clone-race-doc\n- ab/fsck-doc-fix\n- jt/packfile-as-uri-doc\n\nCiao,\nDscho\n"},{"id":"416274","messageId":"352af03f-916f-9104-d2d7-ed7457d37911@jeffhostetler.com","threadId":"54978","inReplyTo":"YBkf/KOv+YBZ5hQF@coredump.intra.peff.net","subject":"Re: [PATCH v2 04/14] pkt-line: optionally skip the flush packet in write_packetized_from_buf()","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-02-05T18:30:53Z","receivedAt":"2021-02-05T18:38:24Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/2/21 4:48 AM, Jeff King wrote:\n> On Mon, Feb 01, 2021 at 07:45:37PM +0000, Johannes Schindelin via GitGitGadget wrote:\n> \n>> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>>\n>> This function currently has only one caller: `apply_multi_file_filter()`\n>> in `convert.c`. That caller wants a flush packet to be written after\n>> writing the payload.\n>>\n>> However, we are about to introduce a user that wants to write many\n>> packets before a final flush packet, so let's extend this function to\n>> prepare for that scenario.\n> \n> I think this is a sign that the function is not very well-designed in\n> the first place. It seems like the code would be easier to understand\n> overall if that caller just explicitly did the flush itself. It even\n> already does so in other cases!\n> \n\nI agree.  I'll move flush to the caller and rename the write packetized\nfunction slightly to guard against new callers assuming the old behavior\nduring the transition.\n\nJeff\n\n\n> Something like (untested):\n> \n>   convert.c  | 4 ++++\n>   pkt-line.c | 4 ----\n>   2 files changed, 4 insertions(+), 4 deletions(-)\n> \n> diff --git a/convert.c b/convert.c\n> index ee360c2f07..3968ac37b9 100644\n> --- a/convert.c\n> +++ b/convert.c\n> @@ -890,6 +890,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n>   \tif (err)\n>   \t\tgoto done;\n>   \n> +\terr = packet_flush_gently(process->in);\n> +\tif (err)\n> +\t\tgoto done;\n> +\n>   \terr = subprocess_read_status(process->out, &filter_status);\n>   \tif (err)\n>   \t\tgoto done;\n> diff --git a/pkt-line.c b/pkt-line.c\n> index d633005ef7..014520a9c2 100644\n> --- a/pkt-line.c\n> +++ b/pkt-line.c\n> @@ -256,8 +256,6 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n>   \t\t\tbreak;\n>   \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n>   \t}\n> -\tif (!err)\n> -\t\terr = packet_flush_gently(fd_out);\n>   \treturn err;\n>   }\n>   \n> @@ -277,8 +275,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n>   \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n>   \t\tbytes_written += bytes_to_write;\n>   \t}\n> -\tif (!err)\n> -\t\terr = packet_flush_gently(fd_out);\n>   \treturn err;\n>   }\n>   \n> \n> -Peff\n> \n"},{"id":"416278","messageId":"20210205193847.GG2091@szeder.dev","threadId":"54978","inReplyTo":"f0bebf1cdb31f94cb111df100b3bcb5e2d93a91e.1612208747.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 09/14] simple-ipc: add t/helper/test-simple-ipc and t0052","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2021-02-05T19:38:47Z","receivedAt":"2021-02-05T19:43:05Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Mon, Feb 01, 2021 at 07:45:42PM +0000, Jeff Hostetler via GitGitGadget wrote:\n> Create unit tests for \"simple-ipc\".  These are currently only enabled\n> on Windows.\n\n> diff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\n\n> +test_expect_success '`quit` works' '\n> +\ttest-tool simple-ipc send quit &&\n> +\ttest_must_fail test-tool simple-ipc is-active &&\n> +\ttest_must_fail test-tool simple-ipc send ping\n> +'\n\nThis test is flaky as well, and it did actually fail in CI:\n\n  expecting success of 0052.9 '`quit` works': \n  \ttest-tool simple-ipc send quit &&\n  \ttest_must_fail test-tool simple-ipc is-active &&\n  \ttest_must_fail test-tool simple-ipc send ping\n  \n  +test-tool simple-ipc send quit\n  +test_must_fail test-tool simple-ipc is-active\n  test_must_fail: command succeeded: test-tool simple-ipc is-active\n  error: last command exited with $?=1\n  not ok 9 - `quit` works\n\n> +\n> +test_done\n> -- \n> gitgitgadget\n> \n"},{"id":"416281","messageId":"xmqq35ya1e84.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"nycvar.QRO.7.76.6.2102051356210.54@tvgsbejvaqbjf.bet","subject":"Re: candidate branches for `maint`, was Re: [PATCH v2 00/14] Simple IPC Mechanism","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-05T19:55:39Z","receivedAt":"2021-02-05T19:57:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> Are there other topics that deserve to be in 'maint' that are\n>> \"obviously correct\" people can think of?\n>\n> I looked over the branches merged into `master` that are not in `maint`,\n> and from a cursory look, these seem to be good candidates:\n> ...\n> There are also a couple test updates that might be nice to have in\n> `maint`:\n> ...\n> Finally, there are documentation updates that I would probably merge, if I\n> was tasked with updating `maint`:\n> ...\n\n\nYour list more or less matches what the ML (merge later) script on\nthe todo/ branch produces when it is fed the RelNotes (the script\njust greps for \"merge laster to maint\" comments and shows the result\nin way a bit easier to use for me).\n\nThe ones that are in RelNotes but not in your list are \n\n    ab/branch-sort # 7 (11 days ago) \n    ar/t6016-modernise # 1 (3 weeks ago) \n    dl/p4-encode-after-kw-expansion # 1 (3 weeks ago) \n    fc/t6030-bisect-reset-removes-auxiliary-files # 1 (4 weeks ago) \n    ma/doc-pack-format-varint-for-sizes # 1 (3 weeks ago) \n    ma/more-opaque-lock-file # 5 (11 days ago) \n    ma/t1300-cleanup # 3 (3 weeks ago) \n    zh/arg-help-format # 2 (3 weeks ago) \n\nand I think all of them are safe to merge down.\n\nThanks for being an independent source I can rely on to sanity check\nwhat is in RelNotes.  Very much appreciated.\n\n\n"},{"id":"416328","messageId":"b7a6f741-c52a-db24-3349-dc69610ce21f@jeffhostetler.com","threadId":"54978","inReplyTo":"YBkmD14Nqqxe4pxG@coredump.intra.peff.net","subject":"Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-02-05T23:28:13Z","receivedAt":"2021-02-06T03:01:37Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/2/21 5:14 AM, Jeff King wrote:\n> On Mon, Feb 01, 2021 at 07:45:44PM +0000, Jeff Hostetler via GitGitGadget wrote:\n> \n>> From: Jeff Hostetler <jeffhost@microsoft.com>\n>>\n>> Update `unix_stream_listen()` to take an options structure to override\n>> default behaviors.  This includes the size of the `listen()` backlog\n>> and whether it should always unlink the socket file before trying to\n>> create a new one.  Also eliminate calls to `die()` if it cannot create\n>> a socket.\n> \n> I sent a follow-up on the previous patch, but I think this part about\n> the die() should be folded in there.\n> \n> Likewise I think it would probably be easier to follow if we added the\n> backlog parameter and the unlink options in separate patches. The\n> backlog thing is small, but the unlink part is subtle and requires\n> explanation. That's a good sign it might do better in its own commit.\n\nYes, that helped having them in 2 patches each with 1 concern.\n\n> \n>> Normally, `unix_stream_listen()` always tries to `unlink()` the\n>> socket-path before calling `bind()`.  If there is an existing\n>> server/daemon already bound and listening on that socket-path, our\n>> `unlink()` would have the effect of disassociating the existing\n>> server's bound-socket-fd from the socket-path without notifying the\n>> existing server.  The existing server could continue to service\n>> existing connections (accepted-socket-fd's), but would not receive any\n>> futher new connections (since clients rendezvous via the socket-path).\n>> The existing server would effectively be offline but yet appear to be\n>> active.\n>>\n>> Furthermore, `unix_stream_listen()` creates an opportunity for a brief\n>> race condition for connecting clients if they try to connect in the\n>> interval between the forced `unlink()` and the subsequent `bind()` (which\n>> recreates the socket-path that is bound to a new socket-fd in the current\n>> process).\n> \n> OK. I'm still not sure of the endgame here for writing non-racy code to\n> establish the socket (which is going to require either some atomic\n> renaming or some dot-locking in the caller).  But it's plausible to me\n> that this option will be a useful primitive.\n\nIn part 14/14 in `ipc-unix-sockets.c:create_listener_socket()` I have\ncode in the calling layer to (try to) handle both the startup races\nand basic collisions with existing long-running servers already using\nthe socket.\n\nBut you're right, it might be good to revisit that as a primitive at\nthis layer.  We only have 1 other caller right now and I don't know\nenough about `credential-cache--daemon` to know if it would benefit\nfrom this or not.\n\n> \n> The implementation looks correct, though here are a few small\n> observations/questions/nits:\n> \n>> -int unix_stream_listen(const char *path)\n>> +int unix_stream_listen(const char *path,\n>> +\t\t       const struct unix_stream_listen_opts *opts)\n>>   {\n>> -\tint fd, saved_errno;\n>> +\tint fd = -1;\n>> +\tint saved_errno;\n>> +\tint bind_successful = 0;\n>> +\tint backlog;\n>>   \tstruct sockaddr_un sa;\n>>   \tstruct unix_sockaddr_context ctx;\n>>   \n>> -\tunlink(path);\n>> -\n>>   \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n>>   \t\treturn -1;\n> \n> We can return directly here, because we know there is nothing to clean\n> up. Which I thought mean that here...\n> \n>> +\n>>   \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n>>   \tif (fd < 0)\n>> -\t\tdie_errno(\"unable to create socket\");\n>> +\t\tgoto fail;\n> \n> ...we are in the same boat. We did not create a socket, so we can just\n> return. That makes our cleanup code a bit simpler. But we can't do that,\n> because unix_sockaddr_init() may have done things that need cleaning up\n> (like chdir). So what you have here is correct.\n> \n> IMHO that is all the more reason to push this (and the similar code in\n> unix_stream_connect() added in patch 13) into the previous patch.\n\nAgreed.\n\n> \n>> +\tif (opts->force_unlink_before_bind)\n>> +\t\tunlink(path);\n>>   \n>>   \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n>>   \t\tgoto fail;\n>> +\tbind_successful = 1;\n> \n> And this one needs to mark a flag explicitly, because we have no other\n> visible way of knowing we need to do the unlink. Makes sense.\n> \n>> -\tif (listen(fd, 5) < 0)\n>> +\tif (opts->listen_backlog_size > 0)\n>> +\t\tbacklog = opts->listen_backlog_size;\n>> +\telse\n>> +\t\tbacklog = 5;\n>> +\tif (listen(fd, backlog) < 0)\n> \n> The default-to-5 is a bit funny here. We already set the default to 5 in\n> UNIX_STREAM_LISTEN_OPTS_INIT. Should it be \"0\" there, so callers can\n> treat that as \"use the default\", which we fill in here? It probably\n> doesn't matter much in practice, but it seems cleaner to have only one\n> spot with the magic number.\n\nI'll refactor this a bit.\n\n> \n>> @@ -114,7 +125,10 @@ int unix_stream_listen(const char *path)\n>>   fail:\n>>   \tsaved_errno = errno;\n>>   \tunix_sockaddr_cleanup(&ctx);\n>> -\tclose(fd);\n>> +\tif (fd != -1)\n>> +\t\tclose(fd);\n>> +\tif (bind_successful)\n>> +\t\tunlink(path);\n>>   \terrno = saved_errno;\n>>   \treturn -1;\n>>   }\n> \n> Should we unlink before closing? I usually try to undo actions in the\n> reverse order that they were done. I thought at first it might even\n> matter here, such that we'd atomically relinquish the name without\n> having a moment where it still points to a closed socket (which might be\n> less confusing to somebody else trying to connect). But I guess there\n> will always be such a moment, because it's not like we would ever\n> accept() or service a request.\n\nI'm not sure it matters, but it does look better to unwind things\nin reverse order.  And yes, unlinking first is a little bit safer.\n\n> \n> -Peff\n> \n"},{"id":"416513","messageId":"7a84352f-1b86-ff3d-27f0-131b873573b5@jeffhostetler.com","threadId":"54978","inReplyTo":"20210202213523.GD2091@szeder.dev","subject":"Re: [PATCH v2 09/14] simple-ipc: add t/helper/test-simple-ipc and t0052","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-02-09T15:45:41Z","receivedAt":"2021-02-09T15:46:57Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/2/21 4:35 PM, SZEDER Gábor wrote:\n> On Mon, Feb 01, 2021 at 07:45:42PM +0000, Jeff Hostetler via GitGitGadget wrote:\n>> diff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\n>> new file mode 100755\n>> index 00000000000..69588354545\n>> --- /dev/null\n>> +++ b/t/t0052-simple-ipc.sh\n>> @@ -0,0 +1,129 @@\n>> +#!/bin/sh\n>> +\n>> +test_description='simple command server'\n>> +\n>> +. ./test-lib.sh\n>> +\n>> +test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {\n>> +\tskip_all='simple IPC not supported on this platform'\n>> +\ttest_done\n>> +}\n>> +\n>> +stop_simple_IPC_server () {\n>> +\ttest -n \"$SIMPLE_IPC_PID\" || return 0\n>> +\n>> +\tkill \"$SIMPLE_IPC_PID\" &&\n>> +\tSIMPLE_IPC_PID=\n>> +}\n>> +\n>> +test_expect_success 'start simple command server' '\n>> +\t{ test-tool simple-ipc daemon --threads=8 & } &&\n>> +\tSIMPLE_IPC_PID=$! &&\n>> +\ttest_atexit stop_simple_IPC_server &&\n>> +\n>> +\tsleep 1 &&\n> \n> This will certainly lead to occasional failures when the daemon takes\n> longer than that mere 1 second delay under heavy load or in CI jobs.\n\n\nGood point.  Thanks!\n\n\n> \n>> +\n>> +\ttest-tool simple-ipc is-active\n>> +'\n"},{"id":"416514","messageId":"YCK5K/nK4tGnTvou@coredump.intra.peff.net","threadId":"54978","inReplyTo":"b7a6f741-c52a-db24-3349-dc69610ce21f@jeffhostetler.com","subject":"Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-09T16:32:43Z","receivedAt":"2021-02-09T16:33:29Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Feb 05, 2021 at 06:28:13PM -0500, Jeff Hostetler wrote:\n\n> > OK. I'm still not sure of the endgame here for writing non-racy code to\n> > establish the socket (which is going to require either some atomic\n> > renaming or some dot-locking in the caller).  But it's plausible to me\n> > that this option will be a useful primitive.\n> \n> In part 14/14 in `ipc-unix-sockets.c:create_listener_socket()` I have\n> code in the calling layer to (try to) handle both the startup races\n> and basic collisions with existing long-running servers already using\n> the socket.\n\nThere you make a temp socket and then try to rename it into place.  But\nbecause rename() overwrites the destination, it still seems like two\ncreating processes can race each other. Something like:\n\n  0. There's no \"foo\" socket (or maybe there is a stale one that\n     nobody's listening on).\n\n  1. Process A wants to become the listener. So it creates foo.A.\n\n  2. Process B likewise. It creates foo.B.\n\n  3. Process A renames foo.A to foo. It believes it will now service\n     clients.\n\n  4. Process B renames foo.B to foo. Now process A is stranded but\n     doesn't realize it.\n\nI.e., I don't think this is much different than an unlink+create\nstrategy. You've eliminated the window where a process C shows up during\nsteps 3 and 4 and sees no socket (because somebody else is in the midst\nof a non-atomic unlink+create operation). But there's no atomicity\nbetween the \"ping the socket\" and \"create the socket\" steps.\n\n> But you're right, it might be good to revisit that as a primitive at\n> this layer.  We only have 1 other caller right now and I don't know\n> enough about `credential-cache--daemon` to know if it would benefit\n> from this or not.\n\nYeah, having seen patch 14, it looks like your only new caller always\nsets the new unlink option to 1. So it might not be worth making it\noptional if you don't need it (especially because the rename trick,\nassuming it's portable, is superior to unlink+create; and you'd always\nbe fine with an unlink on the temp socket).\n\nThe call in credential-cache--daemon is definitely racy. It's pretty\nmuch the same thing: it pings the socket to see if it's alive, but is\nstill susceptible to the problem above. I was was never too concerned\nabout it, since the whole point of the daemon is to hang around until\nits contents expire. If it loses the race and nobody contacts it, the\nworst case is it waits 30 seconds for somebody to give it data before\nexiting. It would benefit slightly from switching to the rename\nstrategy, but the bigger race would remain.\n\n-Peff\n"},{"id":"416540","messageId":"87eb64b1-a61b-f2cc-f689-6ab0b5ee83d0@jeffhostetler.com","threadId":"54978","inReplyTo":"YCK5K/nK4tGnTvou@coredump.intra.peff.net","subject":"Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-02-09T17:39:22Z","receivedAt":"2021-02-09T17:40:56Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/9/21 11:32 AM, Jeff King wrote:\n> On Fri, Feb 05, 2021 at 06:28:13PM -0500, Jeff Hostetler wrote:\n> \n>>> OK. I'm still not sure of the endgame here for writing non-racy code to\n>>> establish the socket (which is going to require either some atomic\n>>> renaming or some dot-locking in the caller).  But it's plausible to me\n>>> that this option will be a useful primitive.\n>>\n>> In part 14/14 in `ipc-unix-sockets.c:create_listener_socket()` I have\n>> code in the calling layer to (try to) handle both the startup races\n>> and basic collisions with existing long-running servers already using\n>> the socket.\n> \n> There you make a temp socket and then try to rename it into place.  But\n> because rename() overwrites the destination, it still seems like two\n> creating processes can race each other. Something like:\n> \n>    0. There's no \"foo\" socket (or maybe there is a stale one that\n>       nobody's listening on).\n> \n>    1. Process A wants to become the listener. So it creates foo.A.\n> \n>    2. Process B likewise. It creates foo.B.\n> \n>    3. Process A renames foo.A to foo. It believes it will now service\n>       clients.\n> \n>    4. Process B renames foo.B to foo. Now process A is stranded but\n>       doesn't realize it.\n> \n\nYeah, in my version two processes could still create uniquely named\nsockets and then do the rename trick.  But they capture the inode\nnumber of the socket before they do that.  They periodically lstat\nthe socket to see if the inode number has changed and if so, assume\nit has been stolen from them.  (A bit of a hack, I admit.)\n\nAnd I was assuming that 2 servers starting at about the same time\nare effectively equivalent -- it doesn't matter which one dies, since\nthey both should have the same amount of cached state.  Unlike the\ncase where a long-running server (with lots of state) is replaced by\na newcomer.\n\n\n> I.e., I don't think this is much different than an unlink+create\n> strategy. You've eliminated the window where a process C shows up during\n> steps 3 and 4 and sees no socket (because somebody else is in the midst\n> of a non-atomic unlink+create operation). But there's no atomicity\n> between the \"ping the socket\" and \"create the socket\" steps.\n> \n>> But you're right, it might be good to revisit that as a primitive at\n>> this layer.  We only have 1 other caller right now and I don't know\n>> enough about `credential-cache--daemon` to know if it would benefit\n>> from this or not.\n> \n> Yeah, having seen patch 14, it looks like your only new caller always\n> sets the new unlink option to 1. So it might not be worth making it\n> optional if you don't need it (especially because the rename trick,\n> assuming it's portable, is superior to unlink+create; and you'd always\n> be fine with an unlink on the temp socket).\n\n\nI am wondering if we can use the .LOCK file magic to our advantage\nhere (in sort of an off-label use).  If we have the server create a\nlockfile \"<path>.LOCK\" and if successful leave it open/locked for the\nlife of the server (rather than immediately renaming it onto <path>)\nand let the normal shutdown code rollback/delete the lockfile in the\ncleanup/atexit.\n\nIf the server successfully creates the lockfile, then unlink+create\nthe socket at <path>.\n\nThat would give us the unique/exclusive creation (on the lock) that\nwe need.  Then wrap that with all the edge case cleanup code to\ncreate/delete/manage the peer socket.  Basically if the lock exists,\nthere should be a live server listening to the socket (unless there\nwas a crash...).\n\nAnd yes, then I don't think I need the `preserve_existing` bit in the\nopts struct.\n\n> \n> The call in credential-cache--daemon is definitely racy. It's pretty\n> much the same thing: it pings the socket to see if it's alive, but is\n> still susceptible to the problem above. I was was never too concerned\n> about it, since the whole point of the daemon is to hang around until\n> its contents expire. If it loses the race and nobody contacts it, the\n> worst case is it waits 30 seconds for somebody to give it data before\n> exiting. It would benefit slightly from switching to the rename\n> strategy, but the bigger race would remain.\n> \n> -Peff\n> \n"},{"id":"416636","messageId":"YCQB4SpYhYA/cKAa@coredump.intra.peff.net","threadId":"54978","inReplyTo":"87eb64b1-a61b-f2cc-f689-6ab0b5ee83d0@jeffhostetler.com","subject":"Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-10T15:55:13Z","receivedAt":"2021-02-10T15:56:17Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 09, 2021 at 12:39:22PM -0500, Jeff Hostetler wrote:\n\n> Yeah, in my version two processes could still create uniquely named\n> sockets and then do the rename trick.  But they capture the inode\n> number of the socket before they do that.  They periodically lstat\n> the socket to see if the inode number has changed and if so, assume\n> it has been stolen from them.  (A bit of a hack, I admit.)\n\nOK, that makes more sense. I saw the mention of the inode stuff in a\ncomment, but I didn't see it in the code (I guess if it's a periodic\ncheck it's not in that initial socket creation function).\n\n> And I was assuming that 2 servers starting at about the same time\n> are effectively equivalent -- it doesn't matter which one dies, since\n> they both should have the same amount of cached state.  Unlike the\n> case where a long-running server (with lots of state) is replaced by\n> a newcomer.\n\nYeah, I agree with that notion in general. I do think it would be easier\nto reason about if the creation were truly race-proof (probably with a\ndot-lock; see below), rather than the later \"check if we got replaced\"\nthing.  OTOH, that \"check\" strategy covers a variety of cases (including\nthat somebody tried to ping us, decided we weren't alive due to a\ntimeout or some other system reason, and then replaced our socket).\n\nAnother strategy there could be having the daemon just decide to quit if\nnobody contacts it for N time units. It is, after all, a cache. Even if\nnobody replaces the socket, it probably makes sense to eventually decide\nthat the memory we're holding isn't going to good use.\n\n> I am wondering if we can use the .LOCK file magic to our advantage\n> here (in sort of an off-label use).  If we have the server create a\n> lockfile \"<path>.LOCK\" and if successful leave it open/locked for the\n> life of the server (rather than immediately renaming it onto <path>)\n> and let the normal shutdown code rollback/delete the lockfile in the\n> cleanup/atexit.\n> \n> If the server successfully creates the lockfile, then unlink+create\n> the socket at <path>.\n\nI don't even think this is off-label. Though the normal use is for the\n.lock file to get renamed into place as the official file, there are a\nfew other places where we use it solely for mutual exclusion. You just\nalways end with rollback_lock_file(), and never \"commit\" it.\n\nSo something like:\n\n  1. Optimistically see if socket \"foo\" is present and accepting\n     connections.\n\n  2. If not, then take \"foo.lock\". If somebody else is holding it, loop\n     with a timeout waiting for them to come alive.\n\n  3. Assuming we got the lock, then either unlink+create the socket as\n     \"foo\", or rename-into-place. I don't think it matters that much\n     which.\n\n  4. Rollback \"foo.lock\", unlinking it.\n\nThen one process wins the lock and creates the socket, while any\nsimultaneous creators spin in step 2, and eventually connect to the\nwinner.\n\n> That would give us the unique/exclusive creation (on the lock) that\n> we need.  Then wrap that with all the edge case cleanup code to\n> create/delete/manage the peer socket.  Basically if the lock exists,\n> there should be a live server listening to the socket (unless there\n> was a crash...).\n\nI think you'd want to delete the lock as soon as you're done with the\nsetup. That reduces the chances that a dead server (e.g., killed by a\npower outage without the chance to clean up after itself) leaves a stale\nlock sitting around.\n\n-Peff\n"},{"id":"416664","messageId":"bca58290-3b23-d08e-e2d5-05ce13c5b842@jeffhostetler.com","threadId":"54978","inReplyTo":"YCQB4SpYhYA/cKAa@coredump.intra.peff.net","subject":"Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-02-10T21:31:23Z","receivedAt":"2021-02-10T21:32:12Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/10/21 10:55 AM, Jeff King wrote:\n> On Tue, Feb 09, 2021 at 12:39:22PM -0500, Jeff Hostetler wrote:\n> \n>> Yeah, in my version two processes could still create uniquely named\n>> sockets and then do the rename trick.  But they capture the inode\n>> number of the socket before they do that.  They periodically lstat\n>> the socket to see if the inode number has changed and if so, assume\n>> it has been stolen from them.  (A bit of a hack, I admit.)\n> \n> OK, that makes more sense. I saw the mention of the inode stuff in a\n> comment, but I didn't see it in the code (I guess if it's a periodic\n> check it's not in that initial socket creation function).\n> \n\nYeah, there's a very slow poll(2) loop in the listen/accept thread\nthat watches for that and new connections (and quit messages).\n\n>> And I was assuming that 2 servers starting at about the same time\n>> are effectively equivalent -- it doesn't matter which one dies, since\n>> they both should have the same amount of cached state.  Unlike the\n>> case where a long-running server (with lots of state) is replaced by\n>> a newcomer.\n> \n> Yeah, I agree with that notion in general. I do think it would be easier\n> to reason about if the creation were truly race-proof (probably with a\n> dot-lock; see below), rather than the later \"check if we got replaced\"\n> thing.  OTOH, that \"check\" strategy covers a variety of cases (including\n> that somebody tried to ping us, decided we weren't alive due to a\n> timeout or some other system reason, and then replaced our socket).\n> \n> Another strategy there could be having the daemon just decide to quit if\n> nobody contacts it for N time units. It is, after all, a cache. Even if\n> nobody replaces the socket, it probably makes sense to eventually decide\n> that the memory we're holding isn't going to good use.\n\nI have the poll(2) loop set to recheck the inode for theft every 60\nseconds (randomly chosen).\n\nAssuming the socket isn't stolen, I want to leave any thoughts of\nan auto-shutdown to the application layer above it.  My next patch\nseries will use this ipc mechanism to build a FSMonitor daemon that\nwill watch the filesystem for changes and then be able to quickly\nrespond to a `git status`, so it is important that it be allowed to\nrun without any clients for a while (such a during a build).  Yes,\nmemory concerns are important, so I do want it to auto-shutdown if\nthe socket is stolen (or the workdir is deleted).\n\n> \n>> I am wondering if we can use the .LOCK file magic to our advantage\n>> here (in sort of an off-label use).  If we have the server create a\n>> lockfile \"<path>.LOCK\" and if successful leave it open/locked for the\n>> life of the server (rather than immediately renaming it onto <path>)\n>> and let the normal shutdown code rollback/delete the lockfile in the\n>> cleanup/atexit.\n>>\n>> If the server successfully creates the lockfile, then unlink+create\n>> the socket at <path>.\n> \n> I don't even think this is off-label. Though the normal use is for the\n> .lock file to get renamed into place as the official file, there are a\n> few other places where we use it solely for mutual exclusion. You just\n> always end with rollback_lock_file(), and never \"commit\" it.\n> \n> So something like:\n> \n>    1. Optimistically see if socket \"foo\" is present and accepting\n>       connections.\n> \n>    2. If not, then take \"foo.lock\". If somebody else is holding it, loop\n>       with a timeout waiting for them to come alive.\n> \n>    3. Assuming we got the lock, then either unlink+create the socket as\n>       \"foo\", or rename-into-place. I don't think it matters that much\n>       which.\n> \n>    4. Rollback \"foo.lock\", unlinking it.\n> \n> Then one process wins the lock and creates the socket, while any\n> simultaneous creators spin in step 2, and eventually connect to the\n> winner.\n> \n>> That would give us the unique/exclusive creation (on the lock) that\n>> we need.  Then wrap that with all the edge case cleanup code to\n>> create/delete/manage the peer socket.  Basically if the lock exists,\n>> there should be a live server listening to the socket (unless there\n>> was a crash...).\n> \n> I think you'd want to delete the lock as soon as you're done with the\n> setup. That reduces the chances that a dead server (e.g., killed by a\n> power outage without the chance to clean up after itself) leaves a stale\n> lock sitting around.\n\nThanks this helps.  I've got a version now that is a slight variation on\nwhat you have here that seems to work nicely and has the short-lived\nlock file.  I'll post this shortly.\n\nJeff\n\n"},{"id":"416688","messageId":"YCSN260gqNV+DyTI@nand.local","threadId":"54978","inReplyTo":"6a389a3533512acedfa1769c64296c1e19b16221.1612208747.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 06/14] pkt-line: accept additional options in read_packetized_to_strbuf()","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2021-02-11T01:52:27Z","receivedAt":"2021-02-11T01:58:15Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Mon, Feb 01, 2021 at 07:45:39PM +0000, Johannes Schindelin via GitGitGadget wrote:\n> diff --git a/pkt-line.c b/pkt-line.c\n> index 528493bca21..f090fc56eef 100644\n> --- a/pkt-line.c\n> +++ b/pkt-line.c\n> @@ -461,7 +461,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)\n>  \treturn packet_read_line_generic(-1, src, src_len, dst_len);\n>  }\n>\n> -ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n> +ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options)\n>  {\n>  \tint packet_len;\n>\n> @@ -477,7 +477,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n>  \t\t\t * that there is already room for the extra byte.\n>  \t\t\t */\n>  \t\t\tsb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,\n> -\t\t\tPACKET_READ_GENTLE_ON_EOF);\n> +\t\t\toptions | PACKET_READ_GENTLE_ON_EOF);\n\nThis feels a little magical to me. Since read_packetized_to_strbuf only\nhas the one caller you mention, why not have the caller pass all of the\noptions (including PACKET_READ_GENTLE_ON_EOF)?\n\n>  \t\tif (packet_len <= 0)\n>  \t\t\tbreak;\n>  \t\tsb_out->len += packet_len;\n> diff --git a/pkt-line.h b/pkt-line.h\n> index 7f31c892165..150319a6f00 100644\n> --- a/pkt-line.h\n> +++ b/pkt-line.h\n> @@ -145,8 +145,12 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);\n>\n>  /*\n>   * Reads a stream of variable sized packets until a flush packet is detected.\n> + *\n> + * The options are augmented by PACKET_READ_GENTLE_ON_EOF and passed to\n> + * packet_read.\n\nObviously this comment will need updating if you take my suggestion.\n\nThanks,\nTaylor\n"},{"id":"416807","messageId":"2d6858b1625aa3c96688c6c6a9157c2d2b16f43e.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:02Z","receivedAt":"2021-02-13T00:10:01Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nTeach `packet_write_gently()` to write the pkt-line header and the actual\nbuffer in 2 separate calls to `write_in_full()` and avoid the need for a\nstatic buffer, thread-safe scratch space, or an excessively large stack\nbuffer.\n\nChange the API of `write_packetized_from_fd()` to accept a scratch space\nargument from its caller to avoid similar issues here.\n\nThese changes are intended to make it easier to use pkt-line routines in\na multi-threaded context with multiple concurrent writers writing to\ndifferent streams.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n convert.c  |  7 ++++---\n pkt-line.c | 28 +++++++++++++++++++---------\n pkt-line.h | 12 +++++++++---\n 3 files changed, 32 insertions(+), 15 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07ce..41012c2d301c 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -883,9 +883,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \tif (err)\n \t\tgoto done;\n \n-\tif (fd >= 0)\n-\t\terr = write_packetized_from_fd(fd, process->in);\n-\telse\n+\tif (fd >= 0) {\n+\t\tstruct packet_scratch_space scratch;\n+\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n+\t} else\n \t\terr = write_packetized_from_buf(src, len, process->in);\n \tif (err)\n \t\tgoto done;\ndiff --git a/pkt-line.c b/pkt-line.c\nindex d633005ef746..4cff2f7a68a5 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -196,17 +196,25 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)\n \n static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n {\n-\tstatic char packet_write_buffer[LARGE_PACKET_MAX];\n+\tchar header[4];\n \tsize_t packet_size;\n \n-\tif (size > sizeof(packet_write_buffer) - 4)\n+\tif (size > LARGE_PACKET_DATA_MAX)\n \t\treturn error(_(\"packet write failed - data exceeds max packet size\"));\n \n \tpacket_trace(buf, size, 1);\n \tpacket_size = size + 4;\n-\tset_packet_header(packet_write_buffer, packet_size);\n-\tmemcpy(packet_write_buffer + 4, buf, size);\n-\tif (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)\n+\n+\tset_packet_header(header, packet_size);\n+\n+\t/*\n+\t * Write the header and the buffer in 2 parts so that we do not need\n+\t * to allocate a buffer or rely on a static buffer.  This avoids perf\n+\t * and multi-threading issues.\n+\t */\n+\n+\tif (write_in_full(fd_out, header, 4) < 0 ||\n+\t    write_in_full(fd_out, buf, size) < 0)\n \t\treturn error(_(\"packet write failed\"));\n \treturn 0;\n }\n@@ -242,19 +250,21 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \tpacket_trace(data, len, 1);\n }\n \n-int write_packetized_from_fd(int fd_in, int fd_out)\n+int write_packetized_from_fd(int fd_in, int fd_out,\n+\t\t\t     struct packet_scratch_space *scratch)\n {\n-\tstatic char buf[LARGE_PACKET_DATA_MAX];\n \tint err = 0;\n \tssize_t bytes_to_write;\n \n \twhile (!err) {\n-\t\tbytes_to_write = xread(fd_in, buf, sizeof(buf));\n+\t\tbytes_to_write = xread(fd_in, scratch->buffer,\n+\t\t\t\t       sizeof(scratch->buffer));\n \t\tif (bytes_to_write < 0)\n \t\t\treturn COPY_READ_ERROR;\n \t\tif (bytes_to_write == 0)\n \t\t\tbreak;\n-\t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n+\t\terr = packet_write_gently(fd_out, scratch->buffer,\n+\t\t\t\t\t  bytes_to_write);\n \t}\n \tif (!err)\n \t\terr = packet_flush_gently(fd_out);\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 8c90daa59ef0..c0722aefe638 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -5,6 +5,13 @@\n #include \"strbuf.h\"\n #include \"sideband.h\"\n \n+#define LARGE_PACKET_MAX 65520\n+#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)\n+\n+struct packet_scratch_space {\n+\tchar buffer[LARGE_PACKET_DATA_MAX]; /* does not include header bytes */\n+};\n+\n /*\n  * Write a packetized stream, where each line is preceded by\n  * its length (including the header) as a 4-byte hex number.\n@@ -32,7 +39,7 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f\n void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n int packet_flush_gently(int fd);\n int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n-int write_packetized_from_fd(int fd_in, int fd_out);\n+int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n \n /*\n@@ -213,8 +220,7 @@ enum packet_read_status packet_reader_read(struct packet_reader *reader);\n enum packet_read_status packet_reader_peek(struct packet_reader *reader);\n \n #define DEFAULT_PACKET_MAX 1000\n-#define LARGE_PACKET_MAX 65520\n-#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)\n+\n extern char packet_buffer[LARGE_PACKET_MAX];\n \n struct packet_writer {\n-- \ngitgitgadget\n\n"},{"id":"416808","messageId":"91a9f63d66924d14a22feedf7b1d88fe298b90bc.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 02/12] pkt-line: do not issue flush packets in write_packetized_*()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:03Z","receivedAt":"2021-02-13T00:10:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nRemove the `packet_flush_gently()` call in `write_packetized_from_buf() and\n`write_packetized_from_fd()` and require the caller to call it if desired.\nRename both functions to `write_packetized_from_*_no_flush()` to prevent\nlater merge accidents.\n\n`write_packetized_from_buf()` currently only has one caller:\n`apply_multi_file_filter()` in `convert.c`.  It always wants a flush packet\nto be written after writing the payload.\n\nHowever, we are about to introduce a caller that wants to write many\npackets before a final flush packet, so let's make the caller responsible\nfor emitting the flush packet.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n convert.c  |  8 ++++++--\n pkt-line.c | 10 +++-------\n pkt-line.h |  4 ++--\n 3 files changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 41012c2d301c..bccf7afa8797 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -885,9 +885,13 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \n \tif (fd >= 0) {\n \t\tstruct packet_scratch_space scratch;\n-\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n+\t\terr = write_packetized_from_fd_no_flush(fd, process->in, &scratch);\n \t} else\n-\t\terr = write_packetized_from_buf(src, len, process->in);\n+\t\terr = write_packetized_from_buf_no_flush(src, len, process->in);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = packet_flush_gently(process->in);\n \tif (err)\n \t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 4cff2f7a68a5..3602b0d37092 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -250,8 +250,8 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \tpacket_trace(data, len, 1);\n }\n \n-int write_packetized_from_fd(int fd_in, int fd_out,\n-\t\t\t     struct packet_scratch_space *scratch)\n+int write_packetized_from_fd_no_flush(int fd_in, int fd_out,\n+\t\t\t\t      struct packet_scratch_space *scratch)\n {\n \tint err = 0;\n \tssize_t bytes_to_write;\n@@ -266,12 +266,10 @@ int write_packetized_from_fd(int fd_in, int fd_out,\n \t\terr = packet_write_gently(fd_out, scratch->buffer,\n \t\t\t\t\t  bytes_to_write);\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\n \n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out)\n {\n \tint err = 0;\n \tsize_t bytes_written = 0;\n@@ -287,8 +285,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n \t\tbytes_written += bytes_to_write;\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\n \ndiff --git a/pkt-line.h b/pkt-line.h\nindex c0722aefe638..a7149429ac35 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -39,8 +39,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f\n void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n int packet_flush_gently(int fd);\n int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n-int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n+int write_packetized_from_fd_no_flush(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);\n \n /*\n  * Read a packetized line into the buffer, which must be at least size bytes\n-- \ngitgitgadget\n\n"},{"id":"416809","messageId":"e05467def4e158a5f1cfa3aafffdb5c77097859a.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 03/12] pkt-line: (optionally) libify the packet readers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:04Z","receivedAt":"2021-02-13T00:10:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nSo far, the (possibly indirect) callers of `get_packet_data()` can ask\nthat function to return an error instead of `die()`ing upon end-of-file.\nHowever, random read errors will still cause the process to die.\n\nSo let's introduce an explicit option to tell the packet reader\nmachinery to please be nice and only return an error.\n\nThis change prepares pkt-line for use by long-running daemon processes.\nSuch processes should be able to serve multiple concurrent clients and\nand survive random IO errors.  If there is an error on one connection,\na daemon should be able to drop that connection and continue serving\nexisting and future connections.\n\nThis ability will be used by a Git-aware \"Internal FSMonitor\" feature\nin a later patch series.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n pkt-line.c | 19 +++++++++++++++++--\n pkt-line.h |  4 ++++\n 2 files changed, 21 insertions(+), 2 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex 3602b0d37092..83c46e6b46ee 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -304,8 +304,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\t*src_size -= ret;\n \t} else {\n \t\tret = read_in_full(fd, dst, size);\n-\t\tif (ret < 0)\n+\t\tif (ret < 0) {\n+\t\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\t\treturn error_errno(_(\"read error\"));\n \t\t\tdie_errno(_(\"read error\"));\n+\t\t}\n \t}\n \n \t/* And complain if we didn't get enough bytes to satisfy the read. */\n@@ -313,6 +316,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n \t\t\treturn -1;\n \n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"the remote end hung up unexpectedly\"));\n \t\tdie(_(\"the remote end hung up unexpectedly\"));\n \t}\n \n@@ -341,6 +346,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \tlen = packet_length(linelen);\n \n \tif (len < 0) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length \"\n+\t\t\t\t       \"character: %.4s\"), linelen);\n \t\tdie(_(\"protocol error: bad line length character: %.4s\"), linelen);\n \t} else if (!len) {\n \t\tpacket_trace(\"0000\", 4, 0);\n@@ -355,12 +363,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \t\t*pktlen = 0;\n \t\treturn PACKET_READ_RESPONSE_END;\n \t} else if (len < 4) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n \t}\n \n \tlen -= 4;\n-\tif ((unsigned)len >= size)\n+\tif ((unsigned)len >= size) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n+\t}\n \n \tif (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) {\n \t\t*pktlen = -1;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex a7149429ac35..2e472efaf2c5 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -75,10 +75,14 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou\n  *\n  * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n  * ERR packet.\n+ *\n+ * With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except\n+ * an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect).\n  */\n #define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n #define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n #define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n+#define PACKET_READ_NEVER_DIE         (1u<<3)\n int packet_read(int fd, char **src_buffer, size_t *src_len, char\n \t\t*buffer, unsigned size, int options);\n \n-- \ngitgitgadget\n\n"},{"id":"416810","messageId":"81e14bed955c6b50e155f6f73cb642d6c9f2fd73.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 04/12] pkt-line: add options argument to read_packetized_to_strbuf()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:05Z","receivedAt":"2021-02-13T00:10:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nUpdate the calling sequence of `read_packetized_to_strbuf()` to take\nan options argument and not assume a fixed set of options.  Update the\nonly existing caller accordingly to explicitly pass the\nformerly-assumed flags.\n\nThe `read_packetized_to_strbuf()` function calls `packet_read()` with\na fixed set of assumed options (`PACKET_READ_GENTLE_ON_EOF`).  This\nassumption has been fine for the single existing caller\n`apply_multi_file_filter()` in `convert.c`.\n\nIn a later commit we would like to add other callers to\n`read_packetized_to_strbuf()` that need a different set of options.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n convert.c  | 3 ++-\n pkt-line.c | 4 ++--\n pkt-line.h | 2 +-\n 3 files changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex bccf7afa8797..9f44f00d841f 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -908,7 +908,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \t\tif (err)\n \t\t\tgoto done;\n \n-\t\terr = read_packetized_to_strbuf(process->out, &nbuf) < 0;\n+\t\terr = read_packetized_to_strbuf(process->out, &nbuf,\n+\t\t\t\t\t\tPACKET_READ_GENTLE_ON_EOF) < 0;\n \t\tif (err)\n \t\t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 83c46e6b46ee..18ecad65e08c 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -442,7 +442,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)\n \treturn packet_read_line_generic(-1, src, src_len, dst_len);\n }\n \n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options)\n {\n \tint packet_len;\n \n@@ -458,7 +458,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n \t\t\t * that there is already room for the extra byte.\n \t\t\t */\n \t\t\tsb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,\n-\t\t\tPACKET_READ_GENTLE_ON_EOF);\n+\t\t\toptions);\n \t\tif (packet_len <= 0)\n \t\t\tbreak;\n \t\tsb_out->len += packet_len;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 2e472efaf2c5..e347fe46832a 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -142,7 +142,7 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);\n /*\n  * Reads a stream of variable sized packets until a flush packet is detected.\n  */\n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options);\n \n /*\n  * Receive multiplexed output stream over git native protocol.\n-- \ngitgitgadget\n\n"},{"id":"416811","messageId":"22eec60761a88107b2e337ce13eed1020352aa73.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 05/12] simple-ipc: design documentation for new IPC mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:06Z","receivedAt":"2021-02-13T00:10:18Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nBrief design documentation for new IPC mechanism allowing\nforeground Git client to talk with an existing daemon process\nat a known location using a named pipe or unix domain socket.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-simple-ipc.txt | 34 ++++++++++++++++++++++\n 1 file changed, 34 insertions(+)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n\ndiff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt\nnew file mode 100644\nindex 000000000000..670a5c163e39\n--- /dev/null\n+++ b/Documentation/technical/api-simple-ipc.txt\n@@ -0,0 +1,34 @@\n+simple-ipc API\n+==============\n+\n+The simple-ipc API is used to send an IPC message and response between\n+a (presumably) foreground Git client process to a background server or\n+daemon process.  The server process must already be running.  Multiple\n+client processes can simultaneously communicate with the server\n+process.\n+\n+Communication occurs over a named pipe on Windows and a Unix domain\n+socket on other platforms.  Clients and the server rendezvous at a\n+previously agreed-to application-specific pathname (which is outside\n+the scope of this design).\n+\n+This IPC mechanism differs from the existing `sub-process.c` model\n+(Documentation/technical/long-running-process-protocol.txt) and used\n+by applications like Git-LFS.  In the simple-ipc model the server is\n+assumed to be a very long-running system service.  In contrast, in the\n+LFS-style sub-process model the helper is started with the foreground\n+process and exits when the foreground process terminates.\n+\n+How the simple-ipc server is started is also outside the scope of the\n+IPC mechanism.  For example, the server might be started during\n+maintenance operations.\n+\n+The IPC protocol consists of a single request message from the client and\n+an optional request message from the server.  For simplicity, pkt-line\n+routines are used to hide chunking and buffering concerns.  Each side\n+terminates their message with a flush packet.\n+(Documentation/technical/protocol-common.txt)\n+\n+The actual format of the client and server messages is application\n+specific.  The IPC layer transmits and receives an opaque buffer without\n+any concern for the content within.\n-- \ngitgitgadget\n\n"},{"id":"416812","messageId":"171ec43ecfa45054afc378aca00c13282e438c47.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 06/12] simple-ipc: add win32 implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:07Z","receivedAt":"2021-02-13T00:10:45Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Windows implementation of \"simple-ipc\" using named pipes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   5 +\n compat/simple-ipc/ipc-shared.c      |  28 ++\n compat/simple-ipc/ipc-win32.c       | 749 ++++++++++++++++++++++++++++\n config.mak.uname                    |   2 +\n contrib/buildsystems/CMakeLists.txt |   4 +\n simple-ipc.h                        | 224 +++++++++\n 6 files changed, 1012 insertions(+)\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n\ndiff --git a/Makefile b/Makefile\nindex 4128b457e14b..40d5cab78d3f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1679,6 +1679,11 @@ else\n \tLIB_OBJS += unix-socket.o\n endif\n \n+ifdef USE_WIN32_IPC\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-win32.o\n+endif\n+\n ifdef NO_ICONV\n \tBASIC_CFLAGS += -DNO_ICONV\n endif\ndiff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c\nnew file mode 100644\nindex 000000000000..1edec8159532\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-shared.c\n@@ -0,0 +1,28 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data)\n+{\n+\tstruct ipc_server_data *server_data = NULL;\n+\tint ret;\n+\n+\tret = ipc_server_run_async(&server_data, path, opts,\n+\t\t\t\t   application_cb, application_data);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = ipc_server_await(server_data);\n+\n+\tipc_server_free(server_data);\n+\n+\treturn ret;\n+}\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\ndiff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c\nnew file mode 100644\nindex 000000000000..f0cfbf9d15c3\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-win32.c\n@@ -0,0 +1,749 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+#error This file can only be compiled on Windows\n+#endif\n+\n+static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc)\n+{\n+\tint off = 0;\n+\tstruct strbuf realpath = STRBUF_INIT;\n+\n+\tif (!strbuf_realpath(&realpath, path, 0))\n+\t\treturn -1;\n+\n+\toff = swprintf(wpath, alloc, L\"\\\\\\\\.\\\\pipe\\\\\");\n+\tif (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0)\n+\t\treturn -1;\n+\n+\t/* Handle drive prefix */\n+\tif (wpath[off] && wpath[off + 1] == L':') {\n+\t\twpath[off + 1] = L'_';\n+\t\toff += 2;\n+\t}\n+\n+\tfor (; wpath[off]; off++)\n+\t\tif (wpath[off] == L'/')\n+\t\t\twpath[off] = L'\\\\';\n+\n+\tstrbuf_release(&realpath);\n+\treturn 0;\n+}\n+\n+static enum ipc_active_state get_active_state(wchar_t *pipe_path)\n+{\n+\tif (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT))\n+\t\treturn IPC_STATE__LISTENING;\n+\n+\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\tif (GetLastError() == ERROR_FILE_NOT_FOUND)\n+\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\treturn IPC_STATE__OTHER_ERROR;\n+}\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\twchar_t pipe_path[MAX_PATH];\n+\n+\tif (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\treturn get_active_state(pipe_path);\n+}\n+\n+#define WAIT_STEP_MS (50)\n+\n+static enum ipc_active_state connect_to_server(\n+\tconst wchar_t *wpath,\n+\tDWORD timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tDWORD t_start_ms, t_waited_ms;\n+\tDWORD step_ms;\n+\tHANDLE hPipe = INVALID_HANDLE_VALUE;\n+\tDWORD mode = PIPE_READMODE_BYTE;\n+\tDWORD gle;\n+\n+\t*pfd = -1;\n+\n+\tfor (;;) {\n+\t\thPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE,\n+\t\t\t\t    0, NULL, OPEN_EXISTING, 0, NULL);\n+\t\tif (hPipe != INVALID_HANDLE_VALUE)\n+\t\t\tbreak;\n+\n+\t\tgle = GetLastError();\n+\n+\t\tswitch (gle) {\n+\t\tcase ERROR_FILE_NOT_FOUND:\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tstep_ms = (timeout_ms < WAIT_STEP_MS) ?\n+\t\t\t\ttimeout_ms : WAIT_STEP_MS;\n+\t\t\tsleep_millisec(step_ms);\n+\n+\t\t\ttimeout_ms -= step_ms;\n+\t\t\tbreak; /* try again */\n+\n+\t\tcase ERROR_PIPE_BUSY:\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tt_start_ms = (DWORD)(getnanotime() / 1000000);\n+\n+\t\t\tif (!WaitNamedPipeW(wpath, timeout_ms)) {\n+\t\t\t\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * A pipe server instance became available.\n+\t\t\t * Race other client processes to connect to\n+\t\t\t * it.\n+\t\t\t *\n+\t\t\t * But first decrement our overall timeout so\n+\t\t\t * that we don't starve if we keep losing the\n+\t\t\t * race.  But also guard against special\n+\t\t\t * NPMWAIT_ values (0 and -1).\n+\t\t\t */\n+\t\t\tt_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms;\n+\t\t\tif (t_waited_ms < timeout_ms)\n+\t\t\t\ttimeout_ms -= t_waited_ms;\n+\t\t\telse\n+\t\t\t\ttimeout_ms = 1;\n+\t\t\tbreak; /* try again */\n+\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t}\n+\t}\n+\n+\tif (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t*pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);\n+\tif (*pfd < 0) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t/* fd now owns hPipe */\n+\n+\treturn IPC_STATE__LISTENING;\n+}\n+\n+/*\n+ * The default connection timeout for Windows clients.\n+ *\n+ * This is not currently part of the ipc_ API (nor the config settings)\n+ * because of differences between Windows and other platforms.\n+ *\n+ * This value was chosen at random.\n+ */\n+#define WINDOWS_CONNECTION_TIMEOUT_MS (30000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\twchar_t wpath[MAX_PATH];\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tif (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0)\n+\t\tstate = IPC_STATE__INVALID_PATH;\n+\telse\n+\t\tstate = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n+\t\t\t\t\t       connection->fd) < 0 ||\n+\t    packet_flush_gently(connection->fd) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tFlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));\n+\n+\tif (read_packetized_to_strbuf(\n+\t\t    connection->fd, answer,\n+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *response)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, response);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Duplicate the given pipe handle and wrap it in a file descriptor so\n+ * that we can use pkt-line on it.\n+ */\n+static int dup_fd_from_pipe(const HANDLE pipe)\n+{\n+\tHANDLE process = GetCurrentProcess();\n+\tHANDLE handle;\n+\tint fd;\n+\n+\tif (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE,\n+\t\t\t     DUPLICATE_SAME_ACCESS)) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\treturn -1;\n+\t}\n+\n+\tfd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY);\n+\tif (fd < 0) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\tCloseHandle(handle);\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * `handle` is now owned by `fd` and will be automatically closed\n+\t * when the descriptor is closed.\n+\t */\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_SERVER_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_server_thread_data *server_thread_data;\n+};\n+\n+struct ipc_server_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+\tHANDLE hPipe;\n+};\n+\n+/*\n+ * On Windows, the conceptual \"ipc-server\" is implemented as a pool of\n+ * n idential/peer \"server-thread\" threads.  That is, there is no\n+ * hierarchy of threads; and therefore no controller thread managing\n+ * the pool.  Each thread has an independent handle to the named pipe,\n+ * receives incoming connections, processes the client, and re-uses\n+ * the pipe for the next client connection.\n+ *\n+ * Therefore, the \"ipc-server\" only needs to maintain a list of the\n+ * spawned threads for eventual \"join\" purposes.\n+ *\n+ * A single \"stop-event\" is visible to all of the server threads to\n+ * tell them to shutdown (when idle).\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\twchar_t wpath[MAX_PATH];\n+\n+\tHANDLE hEventStopRequested;\n+\tstruct ipc_server_thread_data *thread_list;\n+\tint is_stopped;\n+};\n+\n+enum connect_result {\n+\tCR_CONNECTED = 0,\n+\tCR_CONNECT_PENDING,\n+\tCR_CONNECT_ERROR,\n+\tCR_WAIT_ERROR,\n+\tCR_SHUTDOWN,\n+};\n+\n+static enum connect_result queue_overlapped_connect(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tif (ConnectNamedPipe(server_thread_data->hPipe, lpo))\n+\t\tgoto failed;\n+\n+\tswitch (GetLastError()) {\n+\tcase ERROR_IO_PENDING:\n+\t\treturn CR_CONNECT_PENDING;\n+\n+\tcase ERROR_PIPE_CONNECTED:\n+\t\tSetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+failed:\n+\terror(_(\"ConnectNamedPipe failed for '%s' (%lu)\"),\n+\t      server_thread_data->server_data->buf_path.buf,\n+\t      GetLastError());\n+\treturn CR_CONNECT_ERROR;\n+}\n+\n+/*\n+ * Use Windows Overlapped IO to wait for a connection or for our event\n+ * to be signalled.\n+ */\n+static enum connect_result wait_for_connection(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tenum connect_result r;\n+\tHANDLE waitHandles[2];\n+\tDWORD dwWaitResult;\n+\n+\tr = queue_overlapped_connect(server_thread_data, lpo);\n+\tif (r != CR_CONNECT_PENDING)\n+\t\treturn r;\n+\n+\twaitHandles[0] = server_thread_data->server_data->hEventStopRequested;\n+\twaitHandles[1] = lpo->hEvent;\n+\n+\tdwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);\n+\tswitch (dwWaitResult) {\n+\tcase WAIT_OBJECT_0 + 0:\n+\t\treturn CR_SHUTDOWN;\n+\n+\tcase WAIT_OBJECT_0 + 1:\n+\t\tResetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\treturn CR_WAIT_ERROR;\n+\t}\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf_no_flush(response, response_len,\n+\t\t\t\t\t\t  reply_data->fd);\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ *\n+ * Simple-IPC only contains one round trip, so we flush and close\n+ * here after the response.\n+ */\n+static int do_io(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.server_thread_data = server_thread_data;\n+\n+\treply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe);\n+\tif (reply_data.fd < 0)\n+\t\treturn error(_(\"could not create fd from pipe for '%s'\"),\n+\t\t\t     server_thread_data->server_data->buf_path.buf);\n+\n+\tret = read_packetized_to_strbuf(\n+\t\treply_data.fd, &buf,\n+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);\n+\tif (ret >= 0) {\n+\t\tret = server_thread_data->server_data->application_cb(\n+\t\t\tserver_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\n+\t\tFlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd)));\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Handle IPC request and response with this connected client.  And reset\n+ * the pipe to prepare for the next client.\n+ */\n+static int use_connection(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tint ret;\n+\n+\tret = do_io(server_thread_data);\n+\n+\tFlushFileBuffers(server_thread_data->hPipe);\n+\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Thread proc for an IPC server worker thread.  It handles a series of\n+ * connections from clients.  It cleans and reuses the hPipe between each\n+ * client.\n+ */\n+static void *server_thread_proc(void *_server_thread_data)\n+{\n+\tstruct ipc_server_thread_data *server_thread_data = _server_thread_data;\n+\tHANDLE hEventConnected = INVALID_HANDLE_VALUE;\n+\tOVERLAPPED oConnect;\n+\tenum connect_result cr;\n+\tint ret;\n+\n+\tassert(server_thread_data->hPipe != INVALID_HANDLE_VALUE);\n+\n+\ttrace2_thread_start(\"ipc-server\");\n+\ttrace2_data_string(\"ipc-server\", NULL, \"pipe\",\n+\t\t\t   server_thread_data->server_data->buf_path.buf);\n+\n+\thEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL);\n+\n+\tmemset(&oConnect, 0, sizeof(oConnect));\n+\toConnect.hEvent = hEventConnected;\n+\n+\tfor (;;) {\n+\t\tcr = wait_for_connection(server_thread_data, &oConnect);\n+\n+\t\tswitch (cr) {\n+\t\tcase CR_SHUTDOWN:\n+\t\t\tgoto finished;\n+\n+\t\tcase CR_CONNECTED:\n+\t\t\tret = use_connection(server_thread_data);\n+\t\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\tserver_thread_data->server_data);\n+\t\t\t\tgoto finished;\n+\t\t\t}\n+\t\t\tif (ret > 0) {\n+\t\t\t\t/*\n+\t\t\t\t * Ignore (transient) IO errors with this\n+\t\t\t\t * client and reset for the next client.\n+\t\t\t\t */\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase CR_CONNECT_PENDING:\n+\t\t\t/* By construction, this should not happen. */\n+\t\t\tBUG(\"ipc-server[%s]: unexpeced CR_CONNECT_PENDING\",\n+\t\t\t    server_thread_data->server_data->buf_path.buf);\n+\n+\t\tcase CR_CONNECT_ERROR:\n+\t\tcase CR_WAIT_ERROR:\n+\t\t\t/*\n+\t\t\t * Ignore these theoretical errors.\n+\t\t\t */\n+\t\t\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tBUG(\"unandled case after wait_for_connection\");\n+\t\t}\n+\t}\n+\n+finished:\n+\tCloseHandle(server_thread_data->hPipe);\n+\tCloseHandle(hEventConnected);\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+static HANDLE create_new_pipe(wchar_t *wpath, int is_first)\n+{\n+\tHANDLE hPipe;\n+\tDWORD dwOpenMode, dwPipeMode;\n+\tLPSECURITY_ATTRIBUTES lpsa = NULL;\n+\n+\tdwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |\n+\t\tFILE_FLAG_OVERLAPPED;\n+\n+\tdwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |\n+\t\tPIPE_REJECT_REMOTE_CLIENTS;\n+\n+\tif (is_first) {\n+\t\tdwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE;\n+\n+\t\t/*\n+\t\t * On Windows, the first server pipe instance gets to\n+\t\t * set the ACL / Security Attributes on the named\n+\t\t * pipe; subsequent instances inherit and cannot\n+\t\t * change them.\n+\t\t *\n+\t\t * TODO Should we allow the application layer to\n+\t\t * specify security attributes, such as `LocalService`\n+\t\t * or `LocalSystem`, when we create the named pipe?\n+\t\t * This question is probably not important when the\n+\t\t * daemon is started by a foreground user process and\n+\t\t * only needs to talk to the current user, but may be\n+\t\t * if the daemon is run via the Control Panel as a\n+\t\t * System Service.\n+\t\t */\n+\t}\n+\n+\thPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,\n+\t\t\t\t PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);\n+\n+\treturn hPipe;\n+}\n+\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct ipc_server_data *server_data;\n+\twchar_t wpath[MAX_PATH];\n+\tHANDLE hPipeFirst = INVALID_HANDLE_VALUE;\n+\tint k;\n+\tint ret = 0;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\tret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));\n+\tif (ret < 0)\n+\t\treturn error(\n+\t\t\t_(\"could not create normalized wchar_t path for '%s'\"),\n+\t\t\tpath);\n+\n+\thPipeFirst = create_new_pipe(wpath, 1);\n+\tif (hPipeFirst == INVALID_HANDLE_VALUE)\n+\t\treturn error(_(\"IPC server already running on '%s'\"), path);\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tserver_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL);\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\twcscpy(server_data->wpath, wpath);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_server_thread_data *std;\n+\n+\t\tstd = xcalloc(1, sizeof(*std));\n+\t\tstd->magic = MAGIC_SERVER_THREAD_DATA;\n+\t\tstd->server_data = server_data;\n+\t\tstd->hPipe = INVALID_HANDLE_VALUE;\n+\n+\t\tstd->hPipe = (k == 0)\n+\t\t\t? hPipeFirst\n+\t\t\t: create_new_pipe(server_data->wpath, 0);\n+\n+\t\tif (std->hPipe == INVALID_HANDLE_VALUE) {\n+\t\t\t/*\n+\t\t\t * If we've reached a pipe instance limit for\n+\t\t\t * this path, just use fewer threads.\n+\t\t\t */\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (pthread_create(&std->pthread_id, NULL,\n+\t\t\t\t   server_thread_proc, std)) {\n+\t\t\t/*\n+\t\t\t * Likewise, if we're out of threads, just use\n+\t\t\t * fewer threads than requested.\n+\t\t\t *\n+\t\t\t * However, we just give up if we can't even get\n+\t\t\t * one thread.  This should not happen.\n+\t\t\t */\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start thread[0] for '%s'\"),\n+\t\t\t\t    path);\n+\n+\t\t\tCloseHandle(std->hPipe);\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tstd->next_thread = server_data->thread_list;\n+\t\tserver_data->thread_list = std;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Gently tell all of the ipc_server threads to shutdown.\n+\t * This will be seen the next time they are idle (and waiting\n+\t * for a connection).\n+\t *\n+\t * We DO NOT attempt to force them to drop an active connection.\n+\t */\n+\tSetEvent(server_data->hEventStopRequested);\n+\treturn 0;\n+}\n+\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tDWORD dwWaitResult;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\tdwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE);\n+\tif (dwWaitResult != WAIT_OBJECT_0)\n+\t\treturn error(_(\"wait for hEvent failed for '%s'\"),\n+\t\t\t     server_data->buf_path.buf);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tpthread_join(std->pthread_id, NULL);\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tif (server_data->hEventStopRequested != INVALID_HANDLE_VALUE)\n+\t\tCloseHandle(server_data->hEventStopRequested);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tfree(server_data);\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 198ab1e58f83..76087cff6789 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows)\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \t# USE_NED_ALLOCATOR = YesPlease\n@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \tUSE_NED_ALLOCATOR = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex c151dd7257f3..4bd41054ee70 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n \tlist(APPEND compat_SOURCES unix-socket.c)\n endif()\n \n+if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+endif()\n+\n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\n \n #header checks\ndiff --git a/simple-ipc.h b/simple-ipc.h\nnew file mode 100644\nindex 000000000000..a3f96b42cca2\n--- /dev/null\n+++ b/simple-ipc.h\n@@ -0,0 +1,224 @@\n+#ifndef GIT_SIMPLE_IPC_H\n+#define GIT_SIMPLE_IPC_H\n+\n+/*\n+ * See Documentation/technical/api-simple-ipc.txt\n+ */\n+\n+#if defined(GIT_WINDOWS_NATIVE)\n+#define SUPPORTS_SIMPLE_IPC\n+#endif\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+#include \"pkt-line.h\"\n+\n+/*\n+ * Simple IPC Client Side API.\n+ */\n+\n+enum ipc_active_state {\n+\t/*\n+\t * The pipe/socket exists and the daemon is waiting for connections.\n+\t */\n+\tIPC_STATE__LISTENING = 0,\n+\n+\t/*\n+\t * The pipe/socket exists, but the daemon is not listening.\n+\t * Perhaps it is very busy.\n+\t * Perhaps the daemon died without deleting the path.\n+\t * Perhaps it is shutting down and draining existing clients.\n+\t * Perhaps it is dead, but other clients are lingering and\n+\t * still holding a reference to the pathname.\n+\t */\n+\tIPC_STATE__NOT_LISTENING,\n+\n+\t/*\n+\t * The requested pathname is bogus and no amount of retries\n+\t * will fix that.\n+\t */\n+\tIPC_STATE__INVALID_PATH,\n+\n+\t/*\n+\t * The requested pathname is not found.  This usually means\n+\t * that there is no daemon present.\n+\t */\n+\tIPC_STATE__PATH_NOT_FOUND,\n+\n+\tIPC_STATE__OTHER_ERROR,\n+};\n+\n+struct ipc_client_connect_options {\n+\t/*\n+\t * Spin under timeout if the server is running but can't\n+\t * accept our connection yet.  This should always be set\n+\t * unless you just want to poke the server and see if it\n+\t * is alive.\n+\t */\n+\tunsigned int wait_if_busy:1;\n+\n+\t/*\n+\t * Spin under timeout if the pipe/socket is not yet present\n+\t * on the file system.  This is useful if we just started\n+\t * the service and need to wait for it to become ready.\n+\t */\n+\tunsigned int wait_if_not_found:1;\n+};\n+\n+#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n+\t.wait_if_busy = 0, \\\n+\t.wait_if_not_found = 0, \\\n+}\n+\n+/*\n+ * Determine if a server is listening on this named pipe or socket using\n+ * platform-specific logic.  This might just probe the filesystem or it\n+ * might make a trivial connection to the server using this pathname.\n+ */\n+enum ipc_active_state ipc_get_active_state(const char *path);\n+\n+struct ipc_client_connection {\n+\tint fd;\n+};\n+\n+/*\n+ * Try to connect to the daemon on the named pipe or socket.\n+ *\n+ * Returns IPC_STATE__LISTENING and a connection handle.\n+ *\n+ * Otherwise, returns info to help decide whether to retry or to\n+ * spawn/respawn the server.\n+ */\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection);\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection);\n+\n+/*\n+ * Used by the client to synchronously send and receive a message with\n+ * the server on the provided client connection.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer);\n+\n+/*\n+ * Used by the client to synchronously connect and send and receive a\n+ * message to the server listening at the given path.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer);\n+\n+/*\n+ * Simple IPC Server Side API.\n+ */\n+\n+struct ipc_server_reply_data;\n+\n+typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *,\n+\t\t\t\t  const char *response,\n+\t\t\t\t  size_t response_len);\n+\n+/*\n+ * Prototype for an application-supplied callback to process incoming\n+ * client IPC messages and compose a reply.  The `application_cb` should\n+ * use the provided `reply_cb` and `reply_data` to send an IPC response\n+ * back to the client.  The `reply_cb` callback can be called multiple\n+ * times for chunking purposes.  A reply message is optional and may be\n+ * omitted if not necessary for the application.\n+ *\n+ * The return value from the application callback is ignored.\n+ * The value `SIMPLE_IPC_QUIT` can be used to shutdown the server.\n+ */\n+typedef int (ipc_server_application_cb)(void *application_data,\n+\t\t\t\t\tconst char *request,\n+\t\t\t\t\tipc_server_reply_cb *reply_cb,\n+\t\t\t\t\tstruct ipc_server_reply_data *reply_data);\n+\n+#define SIMPLE_IPC_QUIT -2\n+\n+/*\n+ * Opaque instance data to represent an IPC server instance.\n+ */\n+struct ipc_server_data;\n+\n+/*\n+ * Control parameters for the IPC server instance.\n+ * Use this to hide platform-specific settings.\n+ */\n+struct ipc_server_opts\n+{\n+\tint nr_threads;\n+};\n+\n+/*\n+ * Start an IPC server instance in one or more background threads\n+ * and return a handle to the pool.\n+ *\n+ * Returns 0 if the asynchronous server pool was started successfully.\n+ * Returns -1 if not.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data);\n+\n+/*\n+ * Gently signal the IPC server pool to shutdown.  No new client\n+ * connections will be accepted, but existing connections will be\n+ * allowed to complete.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data);\n+\n+/*\n+ * Block the calling thread until all threads in the IPC server pool\n+ * have completed and been joined.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data);\n+\n+/*\n+ * Close and free all resource handles associated with the IPC server\n+ * pool.\n+ */\n+void ipc_server_free(struct ipc_server_data *server_data);\n+\n+/*\n+ * Run an IPC server instance and block the calling thread of the\n+ * current process.  It does not return until the IPC server has\n+ * either shutdown or had an unrecoverable error.\n+ *\n+ * The IPC server handles incoming IPC messages from client processes\n+ * and may use one or more background threads as necessary.\n+ *\n+ * Returns 0 after the server has completed successfully.\n+ * Returns -1 if the server cannot be started.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ *\n+ * Note that `ipc_server_run()` is a synchronous wrapper around the\n+ * above asynchronous routines.  It effectively hides all of the\n+ * server state and thread details from the caller and presents a\n+ * simple synchronous interface.\n+ */\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data);\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\n+#endif /* GIT_SIMPLE_IPC_H */\n-- \ngitgitgadget\n\n"},{"id":"416813","messageId":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v2.git.1612208747.gitgitgadget@gmail.com","subject":"[PATCH v3 00/12] Simple IPC Mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:01Z","receivedAt":"2021-02-13T00:10:47Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"Here is version 3 of my \"Simple IPC\" series. It addresses the following\nreview comments from V2:\n\n[1] Convert packet_write_gently() to write the header length and then the\nactual buffer using 2 syscalls and avoid the need for a static or stack\nbuffer and update callers.\n\n[2] Added buffer argument to write_packetized_from_fd() to force (the one\ncaller) to provide a buffer and avoid the same thread issues discussed\nearlier.\n\n[3] Remove the implicit pkt-flush from write_packetized_from_buf(). (V2\nadded a flag to make it optional and I removed that too.) Updated the\nexisting callers to call packet_flush_gently() as desired. Renamed\nwrite_packetized_*() functions to have ..._no_flush() suffix to prevent\nfuture accidents with new (more limited) functionality.\n\n[4] Removed the \"force_unlink\" flag to the unix-socket options that I added\nin V1/V2.\n\n[5] Created a new unix_stream_server__listen_with_lock() wrapper function to\nsafely create a Unix domain socket while holding a lockfile and (hopefully)\neliminate the previously discussed race conditions. Added a little helper\nstruct and related routines to help manage the life of the socket.\n\n[6] Added test-tool simple-ipc start-daemon to launch a background instance\nof test-tool simple-ipc run-daemon and wait for the server to become ready\nbefore exiting. And updated t0052 to use it and avoid the problematic sleep\n1 in V1/V2. (There was discussion on the mailing list about using a FIFO in\nthe test like lib-git-daemon.sh, but there are issues with FIFO support on\nWindows that I didn't want to step into. (And I want to use the same \"run\"\nand \"start\" technique with the FSMonitor layer, so lets me explore that\nhere.)\n\n[7] Rebased onto v2.30.1 to get rid of a copy of Junio's \"brew cask\" commit\n(3831132ace) that I included in earlier versions of this series.\n\n[8] In response Gábor's comments about a CI test failure on \"quit works\"\n(https://lore.kernel.org/git/20210205193847.GG2091@szeder.dev/) I added a\ngenerous sleep and comments. I'm not completely happy with this solution,\nbut I'm not sure of a better solution right now.\n\n[9] In response to Taylor's comment on read_packetized_to_strbuf()\n(https://lore.kernel.org/git/YCSN260gqNV+DyTI@nand.local/), I've moved\nPACKET_READ_GENTLE_ON_EOF flag to all the callers as suggested.\n\ncc: Ævar Arnfjörð Bjarmason avarab@gmail.com cc: Jeff Hostetler\ngit@jeffhostetler.com cc: Jeff King peff@peff.net cc: Chris Torek\nchris.torek@gmail.com\n\nJeff Hostetler (9):\n  pkt-line: eliminate the need for static buffer in\n    packet_write_gently()\n  simple-ipc: design documentation for new IPC mechanism\n  simple-ipc: add win32 implementation\n  unix-socket: elimiate static unix_stream_socket() helper function\n  unix-socket: add backlog size option to unix_stream_listen()\n  unix-socket: disallow chdir() when creating unix domain sockets\n  unix-socket: create `unix_stream_server__listen_with_lock()`\n  simple-ipc: add Unix domain socket implementation\n  t0052: add simple-ipc tests and t/helper/test-simple-ipc tool\n\nJohannes Schindelin (3):\n  pkt-line: do not issue flush packets in write_packetized_*()\n  pkt-line: (optionally) libify the packet readers\n  pkt-line: add options argument to read_packetized_to_strbuf()\n\n Documentation/technical/api-simple-ipc.txt |  34 +\n Makefile                                   |   8 +\n builtin/credential-cache--daemon.c         |   3 +-\n builtin/credential-cache.c                 |   2 +-\n compat/simple-ipc/ipc-shared.c             |  28 +\n compat/simple-ipc/ipc-unix-socket.c        | 979 +++++++++++++++++++++\n compat/simple-ipc/ipc-win32.c              | 749 ++++++++++++++++\n config.mak.uname                           |   2 +\n contrib/buildsystems/CMakeLists.txt        |   6 +\n convert.c                                  |  16 +-\n pkt-line.c                                 |  57 +-\n pkt-line.h                                 |  20 +-\n simple-ipc.h                               | 235 +++++\n t/helper/test-simple-ipc.c                 | 713 +++++++++++++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t0052-simple-ipc.sh                      | 134 +++\n unix-socket.c                              | 168 +++-\n unix-socket.h                              |  47 +-\n 19 files changed, 3150 insertions(+), 53 deletions(-)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\n\nbase-commit: 773e25afc41b1b6533fa9ae2cd825d0b4a697fad\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/766\n\nRange-diff vs v2:\n\n  1:  4c6766d41834 <  -:  ------------ ci/install-depends: attempt to fix \"brew cask\" stuff\n  2:  3b03a8ff7a72 !  1:  2d6858b1625a pkt-line: promote static buffer in packet_write_gently() to callers\n     @@ Metadata\n      Author: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## Commit message ##\n     -    pkt-line: promote static buffer in packet_write_gently() to callers\n     +    pkt-line: eliminate the need for static buffer in packet_write_gently()\n      \n     -    Move the static buffer used in `packet_write_gently()` to its callers.\n     -    This is a first step to make packet writing more thread-safe.\n     +    Teach `packet_write_gently()` to write the pkt-line header and the actual\n     +    buffer in 2 separate calls to `write_in_full()` and avoid the need for a\n     +    static buffer, thread-safe scratch space, or an excessively large stack\n     +    buffer.\n     +\n     +    Change the API of `write_packetized_from_fd()` to accept a scratch space\n     +    argument from its caller to avoid similar issues here.\n     +\n     +    These changes are intended to make it easier to use pkt-line routines in\n     +    a multi-threaded context with multiple concurrent writers writing to\n     +    different streams.\n      \n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n     + ## convert.c ##\n     +@@ convert.c: static int apply_multi_file_filter(const char *path, const char *src, size_t len\n     + \tif (err)\n     + \t\tgoto done;\n     + \n     +-\tif (fd >= 0)\n     +-\t\terr = write_packetized_from_fd(fd, process->in);\n     +-\telse\n     ++\tif (fd >= 0) {\n     ++\t\tstruct packet_scratch_space scratch;\n     ++\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n     ++\t} else\n     + \t\terr = write_packetized_from_buf(src, len, process->in);\n     + \tif (err)\n     + \t\tgoto done;\n     +\n       ## pkt-line.c ##\n      @@ pkt-line.c: int packet_write_fmt_gently(int fd, const char *fmt, ...)\n     - \treturn status;\n     - }\n       \n     --static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n     -+/*\n     -+ * Use the provided scratch space to build a combined <hdr><buf> buffer\n     -+ * and write it to the file descriptor (in one write if possible).\n     -+ */\n     -+static int packet_write_gently(const int fd_out, const char *buf, size_t size,\n     -+\t\t\t       struct packet_scratch_space *scratch)\n     + static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n       {\n      -\tstatic char packet_write_buffer[LARGE_PACKET_MAX];\n     ++\tchar header[4];\n       \tsize_t packet_size;\n       \n      -\tif (size > sizeof(packet_write_buffer) - 4)\n     -+\tif (size > sizeof(scratch->buffer) - 4)\n     ++\tif (size > LARGE_PACKET_DATA_MAX)\n       \t\treturn error(_(\"packet write failed - data exceeds max packet size\"));\n       \n       \tpacket_trace(buf, size, 1);\n     @@ pkt-line.c: int packet_write_fmt_gently(int fd, const char *fmt, ...)\n      -\tmemcpy(packet_write_buffer + 4, buf, size);\n      -\tif (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)\n      +\n     -+\tset_packet_header(scratch->buffer, packet_size);\n     -+\tmemcpy(scratch->buffer + 4, buf, size);\n     ++\tset_packet_header(header, packet_size);\n      +\n     -+\tif (write_in_full(fd_out, scratch->buffer, packet_size) < 0)\n     ++\t/*\n     ++\t * Write the header and the buffer in 2 parts so that we do not need\n     ++\t * to allocate a buffer or rely on a static buffer.  This avoids perf\n     ++\t * and multi-threading issues.\n     ++\t */\n     ++\n     ++\tif (write_in_full(fd_out, header, 4) < 0 ||\n     ++\t    write_in_full(fd_out, buf, size) < 0)\n       \t\treturn error(_(\"packet write failed\"));\n       \treturn 0;\n       }\n     - \n     - void packet_write(int fd_out, const char *buf, size_t size)\n     - {\n     --\tif (packet_write_gently(fd_out, buf, size))\n     -+\tstatic struct packet_scratch_space scratch;\n     -+\n     -+\tif (packet_write_gently(fd_out, buf, size, &scratch))\n     - \t\tdie_errno(_(\"packet write failed\"));\n     - }\n     - \n      @@ pkt-line.c: void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n     + \tpacket_trace(data, len, 1);\n     + }\n       \n     - int write_packetized_from_fd(int fd_in, int fd_out)\n     +-int write_packetized_from_fd(int fd_in, int fd_out)\n     ++int write_packetized_from_fd(int fd_in, int fd_out,\n     ++\t\t\t     struct packet_scratch_space *scratch)\n       {\n     -+\t/*\n     -+\t * TODO We could save a memcpy() if we essentially inline\n     -+\t * TODO packet_write_gently() here and change the xread()\n     -+\t * TODO to pass &buf[4].\n     -+\t */\n     -+\tstatic struct packet_scratch_space scratch;\n     - \tstatic char buf[LARGE_PACKET_DATA_MAX];\n     +-\tstatic char buf[LARGE_PACKET_DATA_MAX];\n       \tint err = 0;\n       \tssize_t bytes_to_write;\n     -@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out)\n     + \n     + \twhile (!err) {\n     +-\t\tbytes_to_write = xread(fd_in, buf, sizeof(buf));\n     ++\t\tbytes_to_write = xread(fd_in, scratch->buffer,\n     ++\t\t\t\t       sizeof(scratch->buffer));\n     + \t\tif (bytes_to_write < 0)\n       \t\t\treturn COPY_READ_ERROR;\n       \t\tif (bytes_to_write == 0)\n       \t\t\tbreak;\n      -\t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n     -+\t\terr = packet_write_gently(fd_out, buf, bytes_to_write, &scratch);\n     ++\t\terr = packet_write_gently(fd_out, scratch->buffer,\n     ++\t\t\t\t\t  bytes_to_write);\n       \t}\n       \tif (!err)\n       \t\terr = packet_flush_gently(fd_out);\n     -@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out)\n     - \n     - int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n     - {\n     -+\tstatic struct packet_scratch_space scratch;\n     - \tint err = 0;\n     - \tsize_t bytes_written = 0;\n     - \tsize_t bytes_to_write;\n     -@@ pkt-line.c: int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n     - \t\t\tbytes_to_write = len - bytes_written;\n     - \t\tif (bytes_to_write == 0)\n     - \t\t\tbreak;\n     --\t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n     -+\t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write, &scratch);\n     - \t\tbytes_written += bytes_to_write;\n     - \t}\n     - \tif (!err)\n      \n       ## pkt-line.h ##\n      @@\n     @@ pkt-line.h\n      +#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)\n      +\n      +struct packet_scratch_space {\n     -+\tchar buffer[LARGE_PACKET_MAX];\n     ++\tchar buffer[LARGE_PACKET_DATA_MAX]; /* does not include header bytes */\n      +};\n      +\n       /*\n        * Write a packetized stream, where each line is preceded by\n        * its length (including the header) as a 4-byte hex number.\n     +@@ pkt-line.h: void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f\n     + void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n     + int packet_flush_gently(int fd);\n     + int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n     +-int write_packetized_from_fd(int fd_in, int fd_out);\n     ++int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n     + int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n     + \n     + /*\n      @@ pkt-line.h: enum packet_read_status packet_reader_read(struct packet_reader *reader);\n       enum packet_read_status packet_reader_peek(struct packet_reader *reader);\n       \n  3:  e671894b4c04 <  -:  ------------ pkt-line: add write_packetized_from_buf2() that takes scratch buffer\n  4:  0832f7d324da !  2:  91a9f63d6692 pkt-line: optionally skip the flush packet in write_packetized_from_buf()\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    pkt-line: optionally skip the flush packet in write_packetized_from_buf()\n     +    pkt-line: do not issue flush packets in write_packetized_*()\n      \n     -    This function currently has only one caller: `apply_multi_file_filter()`\n     -    in `convert.c`. That caller wants a flush packet to be written after\n     -    writing the payload.\n     +    Remove the `packet_flush_gently()` call in `write_packetized_from_buf() and\n     +    `write_packetized_from_fd()` and require the caller to call it if desired.\n     +    Rename both functions to `write_packetized_from_*_no_flush()` to prevent\n     +    later merge accidents.\n      \n     -    However, we are about to introduce a user that wants to write many\n     -    packets before a final flush packet, so let's extend this function to\n     -    prepare for that scenario.\n     +    `write_packetized_from_buf()` currently only has one caller:\n     +    `apply_multi_file_filter()` in `convert.c`.  It always wants a flush packet\n     +    to be written after writing the payload.\n     +\n     +    However, we are about to introduce a caller that wants to write many\n     +    packets before a final flush packet, so let's make the caller responsible\n     +    for emitting the flush packet.\n      \n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n       ## convert.c ##\n      @@ convert.c: static int apply_multi_file_filter(const char *path, const char *src, size_t len\n     - \tif (fd >= 0)\n     - \t\terr = write_packetized_from_fd(fd, process->in);\n     - \telse\n     + \n     + \tif (fd >= 0) {\n     + \t\tstruct packet_scratch_space scratch;\n     +-\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n     ++\t\terr = write_packetized_from_fd_no_flush(fd, process->in, &scratch);\n     + \t} else\n      -\t\terr = write_packetized_from_buf(src, len, process->in);\n     -+\t\terr = write_packetized_from_buf(src, len, process->in, 1);\n     ++\t\terr = write_packetized_from_buf_no_flush(src, len, process->in);\n     ++\tif (err)\n     ++\t\tgoto done;\n     ++\n     ++\terr = packet_flush_gently(process->in);\n       \tif (err)\n       \t\tgoto done;\n       \n      \n       ## pkt-line.c ##\n     -@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out)\n     - \treturn err;\n     +@@ pkt-line.c: void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n     + \tpacket_trace(data, len, 1);\n       }\n       \n     --int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n     -+int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,\n     -+\t\t\t      int flush_at_end)\n     +-int write_packetized_from_fd(int fd_in, int fd_out,\n     +-\t\t\t     struct packet_scratch_space *scratch)\n     ++int write_packetized_from_fd_no_flush(int fd_in, int fd_out,\n     ++\t\t\t\t      struct packet_scratch_space *scratch)\n       {\n     - \tstatic struct packet_scratch_space scratch;\n     - \n     --\treturn write_packetized_from_buf2(src_in, len, fd_out, &scratch);\n     -+\treturn write_packetized_from_buf2(src_in, len, fd_out,\n     -+\t\t\t\t\t  flush_at_end, &scratch);\n     + \tint err = 0;\n     + \tssize_t bytes_to_write;\n     +@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out,\n     + \t\terr = packet_write_gently(fd_out, scratch->buffer,\n     + \t\t\t\t\t  bytes_to_write);\n     + \t}\n     +-\tif (!err)\n     +-\t\terr = packet_flush_gently(fd_out);\n     + \treturn err;\n       }\n       \n     - int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n     -+\t\t\t       int flush_at_end,\n     - \t\t\t       struct packet_scratch_space *scratch)\n     +-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n     ++int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out)\n       {\n       \tint err = 0;\n     -@@ pkt-line.c: int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n     - \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write, scratch);\n     + \tsize_t bytes_written = 0;\n     +@@ pkt-line.c: int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n     + \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n       \t\tbytes_written += bytes_to_write;\n       \t}\n      -\tif (!err)\n     -+\tif (!err && flush_at_end)\n     - \t\terr = packet_flush_gently(fd_out);\n     +-\t\terr = packet_flush_gently(fd_out);\n       \treturn err;\n       }\n     + \n      \n       ## pkt-line.h ##\n     -@@ pkt-line.h: void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n     +@@ pkt-line.h: void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f\n     + void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n       int packet_flush_gently(int fd);\n       int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n     - int write_packetized_from_fd(int fd_in, int fd_out);\n     +-int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n      -int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n     -+int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,\n     -+\t\t\t      int flush_at_end);\n     - int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n     -+\t\t\t       int flush_at_end,\n     - \t\t\t       struct packet_scratch_space *scratch);\n     ++int write_packetized_from_fd_no_flush(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n     ++int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);\n       \n       /*\n     +  * Read a packetized line into the buffer, which must be at least size bytes\n  5:  43bc4a26b790 !  3:  e05467def4e1 pkt-line: (optionally) libify the packet readers\n     @@ pkt-line.c: enum packet_read_status packet_read_with_status(int fd, char **src_b\n       \t\t*pktlen = -1;\n      \n       ## pkt-line.h ##\n     -@@ pkt-line.h: int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,\n     +@@ pkt-line.h: int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou\n        *\n        * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n        * ERR packet.\n  6:  6a389a353351 !  4:  81e14bed955c pkt-line: accept additional options in read_packetized_to_strbuf()\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    pkt-line: accept additional options in read_packetized_to_strbuf()\n     +    pkt-line: add options argument to read_packetized_to_strbuf()\n      \n     -    The `read_packetized_to_strbuf()` function reads packets into a strbuf\n     -    until a flush packet has been received. So far, it has only one caller:\n     -    `apply_multi_file_filter()` in `convert.c`. This caller really only\n     -    needs the `PACKET_READ_GENTLE_ON_EOF` option to be passed to\n     -    `packet_read()` (which makes sense in the scenario where packets should\n     -    be read until a flush packet is received).\n     +    Update the calling sequence of `read_packetized_to_strbuf()` to take\n     +    an options argument and not assume a fixed set of options.  Update the\n     +    only existing caller accordingly to explicitly pass the\n     +    formerly-assumed flags.\n      \n     -    We are about to introduce a caller that wants to pass other options\n     -    through to `packet_read()`, so let's extend the function signature\n     -    accordingly.\n     +    The `read_packetized_to_strbuf()` function calls `packet_read()` with\n     +    a fixed set of assumed options (`PACKET_READ_GENTLE_ON_EOF`).  This\n     +    assumption has been fine for the single existing caller\n     +    `apply_multi_file_filter()` in `convert.c`.\n     +\n     +    In a later commit we would like to add other callers to\n     +    `read_packetized_to_strbuf()` that need a different set of options.\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n     +    Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## convert.c ##\n      @@ convert.c: static int apply_multi_file_filter(const char *path, const char *src, size_t len\n     @@ convert.c: static int apply_multi_file_filter(const char *path, const char *src,\n       \t\t\tgoto done;\n       \n      -\t\terr = read_packetized_to_strbuf(process->out, &nbuf) < 0;\n     -+\t\terr = read_packetized_to_strbuf(process->out, &nbuf, 0) < 0;\n     ++\t\terr = read_packetized_to_strbuf(process->out, &nbuf,\n     ++\t\t\t\t\t\tPACKET_READ_GENTLE_ON_EOF) < 0;\n       \t\tif (err)\n       \t\t\tgoto done;\n       \n     @@ pkt-line.c: ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n       \t\t\t */\n       \t\t\tsb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,\n      -\t\t\tPACKET_READ_GENTLE_ON_EOF);\n     -+\t\t\toptions | PACKET_READ_GENTLE_ON_EOF);\n     ++\t\t\toptions);\n       \t\tif (packet_len <= 0)\n       \t\t\tbreak;\n       \t\tsb_out->len += packet_len;\n      \n       ## pkt-line.h ##\n      @@ pkt-line.h: char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);\n     - \n       /*\n        * Reads a stream of variable sized packets until a flush packet is detected.\n     -+ *\n     -+ * The options are augmented by PACKET_READ_GENTLE_ON_EOF and passed to\n     -+ * packet_read.\n        */\n      -ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);\n     -+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out,\n     -+\t\t\t\t  int options);\n     ++ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options);\n       \n       /*\n        * Receive multiplexed output stream over git native protocol.\n  7:  a7275b4bdc2a =  5:  22eec60761a8 simple-ipc: design documentation for new IPC mechanism\n  8:  388366913d41 !  6:  171ec43ecfa4 simple-ipc: add win32 implementation\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +\n      +\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n      +\n     -+\tif (write_packetized_from_buf2(message, strlen(message),\n     -+\t\t\t\t       connection->fd, 1,\n     -+\t\t\t\t       &connection->scratch_write_buffer) < 0) {\n     ++\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n     ++\t\t\t\t\t       connection->fd) < 0 ||\n     ++\t    packet_flush_gently(connection->fd) < 0) {\n      +\t\tret = error(_(\"could not send IPC command\"));\n      +\t\tgoto done;\n      +\t}\n      +\n      +\tFlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));\n      +\n     -+\tif (read_packetized_to_strbuf(connection->fd, answer,\n     -+\t\t\t\t      PACKET_READ_NEVER_DIE) < 0) {\n     ++\tif (read_packetized_to_strbuf(\n     ++\t\t    connection->fd, answer,\n     ++\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {\n      +\t\tret = error(_(\"could not read IPC response\"));\n      +\t\tgoto done;\n      +\t}\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +\tstruct ipc_server_data *server_data;\n      +\tpthread_t pthread_id;\n      +\tHANDLE hPipe;\n     -+\tstruct packet_scratch_space scratch_write_buffer;\n      +};\n      +\n      +/*\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n      +\t\t       const char *response, size_t response_len)\n      +{\n     -+\tstruct packet_scratch_space *scratch =\n     -+\t\t&reply_data->server_thread_data->scratch_write_buffer;\n     -+\n      +\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n      +\t\tBUG(\"reply_cb called with wrong instance data\");\n      +\n     -+\treturn write_packetized_from_buf2(response, response_len,\n     -+\t\t\t\t\t  reply_data->fd, 0, scratch);\n     ++\treturn write_packetized_from_buf_no_flush(response, response_len,\n     ++\t\t\t\t\t\t  reply_data->fd);\n      +}\n      +\n      +/*\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +\t\treturn error(_(\"could not create fd from pipe for '%s'\"),\n      +\t\t\t     server_thread_data->server_data->buf_path.buf);\n      +\n     -+\tret = read_packetized_to_strbuf(reply_data.fd, &buf,\n     -+\t\t\t\t\tPACKET_READ_NEVER_DIE);\n     ++\tret = read_packetized_to_strbuf(\n     ++\t\treply_data.fd, &buf,\n     ++\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);\n      +\tif (ret >= 0) {\n      +\t\tret = server_thread_data->server_data->application_cb(\n      +\t\t\tserver_thread_data->server_data->application_data,\n     @@ simple-ipc.h (new)\n      +\n      +struct ipc_client_connection {\n      +\tint fd;\n     -+\tstruct packet_scratch_space scratch_write_buffer;\n      +};\n      +\n      +/*\n 10:  f5d5445cf42e !  7:  b368318e6a23 unix-socket: elimiate static unix_stream_socket() helper function\n     @@ Metadata\n       ## Commit message ##\n          unix-socket: elimiate static unix_stream_socket() helper function\n      \n     -    The static helper function `unix_stream_socket()` calls `die()`.  This is not\n     -    appropriate for all callers.  Eliminate the wrapper function and move the\n     -    existing error handling to the callers in preparation for adapting specific\n     -    callers.\n     +    The static helper function `unix_stream_socket()` calls `die()`.  This\n     +    is not appropriate for all callers.  Eliminate the wrapper function\n     +    and make the callers propagate the error.\n      \n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n     @@ unix-socket.c\n       static int chdir_len(const char *orig, int len)\n       {\n       \tchar *path = xmemdupz(orig, len);\n     -@@ unix-socket.c: int unix_stream_connect(const char *path)\n     +@@ unix-socket.c: static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n     + \n     + int unix_stream_connect(const char *path)\n     + {\n     +-\tint fd, saved_errno;\n     ++\tint fd = -1, saved_errno;\n     + \tstruct sockaddr_un sa;\n     + \tstruct unix_sockaddr_context ctx;\n       \n       \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n       \t\treturn -1;\n      -\tfd = unix_stream_socket();\n      +\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n      +\tif (fd < 0)\n     -+\t\tdie_errno(\"unable to create socket\");\n     ++\t\tgoto fail;\n      +\n       \tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n       \t\tgoto fail;\n       \tunix_sockaddr_cleanup(&ctx);\n     +@@ unix-socket.c: int unix_stream_connect(const char *path)\n     + \n     + fail:\n     + \tsaved_errno = errno;\n     ++\tif (fd != -1)\n     ++\t\tclose(fd);\n     + \tunix_sockaddr_cleanup(&ctx);\n     +-\tclose(fd);\n     + \terrno = saved_errno;\n     + \treturn -1;\n     + }\n     + \n     + int unix_stream_listen(const char *path)\n     + {\n     +-\tint fd, saved_errno;\n     ++\tint fd = -1, saved_errno;\n     + \tstruct sockaddr_un sa;\n     + \tstruct unix_sockaddr_context ctx;\n     + \n      @@ unix-socket.c: int unix_stream_listen(const char *path)\n       \n       \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n     @@ unix-socket.c: int unix_stream_listen(const char *path)\n      -\tfd = unix_stream_socket();\n      +\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n      +\tif (fd < 0)\n     -+\t\tdie_errno(\"unable to create socket\");\n     ++\t\tgoto fail;\n       \n       \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n       \t\tgoto fail;\n     +@@ unix-socket.c: int unix_stream_listen(const char *path)\n     + \n     + fail:\n     + \tsaved_errno = errno;\n     ++\tif (fd != -1)\n     ++\t\tclose(fd);\n     + \tunix_sockaddr_cleanup(&ctx);\n     +-\tclose(fd);\n     + \terrno = saved_errno;\n     + \treturn -1;\n     + }\n 11:  7a6a69dfc20c !  8:  985b2e02b2df unix-socket: add options to unix_stream_listen()\n     @@ Metadata\n      Author: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## Commit message ##\n     -    unix-socket: add options to unix_stream_listen()\n     +    unix-socket: add backlog size option to unix_stream_listen()\n      \n          Update `unix_stream_listen()` to take an options structure to override\n     -    default behaviors.  This includes the size of the `listen()` backlog\n     -    and whether it should always unlink the socket file before trying to\n     -    create a new one.  Also eliminate calls to `die()` if it cannot create\n     -    a socket.\n     -\n     -    Normally, `unix_stream_listen()` always tries to `unlink()` the\n     -    socket-path before calling `bind()`.  If there is an existing\n     -    server/daemon already bound and listening on that socket-path, our\n     -    `unlink()` would have the effect of disassociating the existing\n     -    server's bound-socket-fd from the socket-path without notifying the\n     -    existing server.  The existing server could continue to service\n     -    existing connections (accepted-socket-fd's), but would not receive any\n     -    futher new connections (since clients rendezvous via the socket-path).\n     -    The existing server would effectively be offline but yet appear to be\n     -    active.\n     -\n     -    Furthermore, `unix_stream_listen()` creates an opportunity for a brief\n     -    race condition for connecting clients if they try to connect in the\n     -    interval between the forced `unlink()` and the subsequent `bind()` (which\n     -    recreates the socket-path that is bound to a new socket-fd in the current\n     -    process).\n     +    default behaviors.  This commit includes the size of the `listen()` backlog.\n      \n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n     @@ unix-socket.c: int unix_stream_connect(const char *path)\n      +int unix_stream_listen(const char *path,\n      +\t\t       const struct unix_stream_listen_opts *opts)\n       {\n     --\tint fd, saved_errno;\n     -+\tint fd = -1;\n     -+\tint saved_errno;\n     -+\tint bind_successful = 0;\n     + \tint fd = -1, saved_errno;\n      +\tint backlog;\n       \tstruct sockaddr_un sa;\n       \tstruct unix_sockaddr_context ctx;\n       \n     --\tunlink(path);\n     --\n     - \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n     - \t\treturn -1;\n     -+\n     - \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n     - \tif (fd < 0)\n     --\t\tdie_errno(\"unable to create socket\");\n     -+\t\tgoto fail;\n     -+\n     -+\tif (opts->force_unlink_before_bind)\n     -+\t\tunlink(path);\n     - \n     +@@ unix-socket.c: int unix_stream_listen(const char *path)\n       \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n       \t\tgoto fail;\n     -+\tbind_successful = 1;\n       \n      -\tif (listen(fd, 5) < 0)\n     -+\tif (opts->listen_backlog_size > 0)\n     -+\t\tbacklog = opts->listen_backlog_size;\n     -+\telse\n     -+\t\tbacklog = 5;\n     ++\tbacklog = opts->listen_backlog_size;\n     ++\tif (backlog <= 0)\n     ++\t\tbacklog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;\n      +\tif (listen(fd, backlog) < 0)\n       \t\tgoto fail;\n       \n       \tunix_sockaddr_cleanup(&ctx);\n     -@@ unix-socket.c: int unix_stream_listen(const char *path)\n     - fail:\n     - \tsaved_errno = errno;\n     - \tunix_sockaddr_cleanup(&ctx);\n     --\tclose(fd);\n     -+\tif (fd != -1)\n     -+\t\tclose(fd);\n     -+\tif (bind_successful)\n     -+\t\tunlink(path);\n     - \terrno = saved_errno;\n     - \treturn -1;\n     - }\n      \n       ## unix-socket.h ##\n      @@\n     @@ unix-socket.h\n       \n      +struct unix_stream_listen_opts {\n      +\tint listen_backlog_size;\n     -+\tunsigned int force_unlink_before_bind:1;\n      +};\n      +\n     ++#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n     ++\n      +#define UNIX_STREAM_LISTEN_OPTS_INIT \\\n      +{ \\\n     -+\t.listen_backlog_size = 5, \\\n     -+\t.force_unlink_before_bind = 1, \\\n     ++\t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n      +}\n      +\n       int unix_stream_connect(const char *path);\n 12:  745b6d5fb746 !  9:  1bfa36409d07 unix-socket: add no-chdir option to unix_stream_listen()\n     @@ Metadata\n      Author: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## Commit message ##\n     -    unix-socket: add no-chdir option to unix_stream_listen()\n     +    unix-socket: disallow chdir() when creating unix domain sockets\n      \n          Calls to `chdir()` are dangerous in a multi-threaded context.  If\n     -    `unix_stream_listen()` is given a socket pathname that is too big to\n     -    fit in a `sockaddr_un` structure, it will `chdir()` to the parent\n     -    directory of the requested socket pathname, create the socket using a\n     -    relative pathname, and then `chdir()` back.  This is not thread-safe.\n     +    `unix_stream_listen()` or `unix_stream_connect()` is given a socket\n     +    pathname that is too long to fit in a `sockaddr_un` structure, it will\n     +    `chdir()` to the parent directory of the requested socket pathname,\n     +    create the socket using a relative pathname, and then `chdir()` back.\n     +    This is not thread-safe.\n      \n     -    Add `disallow_chdir` flag to `struct unix_sockaddr_context` and change\n     -    all callers to pass an initialized context structure.\n     -\n     -    Teach `unix_sockaddr_init()` to not allow calls to `chdir()` when flag\n     -    is set.\n     +    Teach `unix_sockaddr_init()` to not allow calls to `chdir()` when this\n     +    flag is set.\n      \n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n     - ## unix-socket.c ##\n     -@@ unix-socket.c: static int chdir_len(const char *orig, int len)\n     + ## builtin/credential-cache.c ##\n     +@@\n     + static int send_request(const char *socket, const struct strbuf *out)\n     + {\n     + \tint got_data = 0;\n     +-\tint fd = unix_stream_connect(socket);\n     ++\tint fd = unix_stream_connect(socket, 0);\n       \n     - struct unix_sockaddr_context {\n     - \tchar *orig_dir;\n     -+\tunsigned int disallow_chdir:1;\n     - };\n     + \tif (fd < 0)\n     + \t\treturn -1;\n     +\n     + ## unix-socket.c ##\n     +@@ unix-socket.c: static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n     + }\n       \n     -+#define UNIX_SOCKADDR_CONTEXT_INIT \\\n     -+{ \\\n     -+\t.orig_dir=NULL, \\\n     -+\t.disallow_chdir=0, \\\n     -+}\n     -+\n     - static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n     - {\n     - \tif (!ctx->orig_dir)\n     -@@ unix-socket.c: static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n     + static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n     +-\t\t\t      struct unix_sockaddr_context *ctx)\n     ++\t\t\t      struct unix_sockaddr_context *ctx,\n     ++\t\t\t      int disallow_chdir)\n       {\n       \tint size = strlen(path) + 1;\n       \n     --\tctx->orig_dir = NULL;\n     -+\tif (ctx->disallow_chdir && size > sizeof(sa->sun_path)) {\n     -+\t\terrno = ENAMETOOLONG;\n     -+\t\treturn -1;\n     -+\t}\n     -+\n     + \tctx->orig_dir = NULL;\n       \tif (size > sizeof(sa->sun_path)) {\n     - \t\tconst char *slash = find_last_dir_sep(path);\n     +-\t\tconst char *slash = find_last_dir_sep(path);\n     ++\t\tconst char *slash;\n       \t\tconst char *dir;\n     -@@ unix-socket.c: int unix_stream_connect(const char *path)\n     + \t\tstruct strbuf cwd = STRBUF_INIT;\n     + \n     ++\t\tif (disallow_chdir) {\n     ++\t\t\terrno = ENAMETOOLONG;\n     ++\t\t\treturn -1;\n     ++\t\t}\n     ++\n     ++\t\tslash = find_last_dir_sep(path);\n     + \t\tif (!slash) {\n     + \t\t\terrno = ENAMETOOLONG;\n     + \t\t\treturn -1;\n     +@@ unix-socket.c: static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n     + \treturn 0;\n     + }\n     + \n     +-int unix_stream_connect(const char *path)\n     ++int unix_stream_connect(const char *path, int disallow_chdir)\n       {\n     - \tint fd, saved_errno;\n     + \tint fd = -1, saved_errno;\n       \tstruct sockaddr_un sa;\n     --\tstruct unix_sockaddr_context ctx;\n     -+\tstruct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;\n     + \tstruct unix_sockaddr_context ctx;\n       \n     - \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n     +-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n     ++\tif (unix_sockaddr_init(&sa, path, &ctx, disallow_chdir) < 0)\n       \t\treturn -1;\n     + \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n     + \tif (fd < 0)\n      @@ unix-socket.c: int unix_stream_listen(const char *path,\n     - \tint bind_successful = 0;\n     - \tint backlog;\n     - \tstruct sockaddr_un sa;\n     --\tstruct unix_sockaddr_context ctx;\n     -+\tstruct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;\n     -+\n     -+\tctx.disallow_chdir = opts->disallow_chdir;\n       \n     - \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n     + \tunlink(path);\n     + \n     +-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n     ++\tif (unix_sockaddr_init(&sa, path, &ctx, opts->disallow_chdir) < 0)\n       \t\treturn -1;\n     + \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n     + \tif (fd < 0)\n      \n       ## unix-socket.h ##\n      @@\n     + \n       struct unix_stream_listen_opts {\n       \tint listen_backlog_size;\n     - \tunsigned int force_unlink_before_bind:1;\n      +\tunsigned int disallow_chdir:1;\n       };\n       \n     + #define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n     +@@ unix-socket.h: struct unix_stream_listen_opts {\n       #define UNIX_STREAM_LISTEN_OPTS_INIT \\\n       { \\\n     - \t.listen_backlog_size = 5, \\\n     - \t.force_unlink_before_bind = 1, \\\n     + \t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n      +\t.disallow_chdir = 0, \\\n       }\n       \n     - int unix_stream_connect(const char *path);\n     +-int unix_stream_connect(const char *path);\n     ++int unix_stream_connect(const char *path, int disallow_chdir);\n     + int unix_stream_listen(const char *path,\n     + \t\t       const struct unix_stream_listen_opts *opts);\n     + \n  -:  ------------ > 10:  b443e11ac32f unix-socket: create `unix_stream_server__listen_with_lock()`\n 14:  72c1c209c380 ! 11:  43c8db9a4468 simple-ipc: add Unix domain socket implementation\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t*pfd = -1;\n      +\n      +\tfor (k = 0; k < timeout_ms; k += wait_ms) {\n     -+\t\tint fd = unix_stream_connect(path);\n     ++\t\tint fd = unix_stream_connect(path, options->uds_disallow_chdir);\n      +\n      +\t\tif (fd != -1) {\n      +\t\t\t*pfd = fd;\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\n      +\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n      +\n     -+\tif (write_packetized_from_buf2(message, strlen(message),\n     -+\t\t\t\t       connection->fd, 1,\n     -+\t\t\t\t       &connection->scratch_write_buffer) < 0) {\n     ++\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n     ++\t\t\t\t\t       connection->fd) < 0 ||\n     ++\t    packet_flush_gently(connection->fd) < 0) {\n      +\t\tret = error(_(\"could not send IPC command\"));\n      +\t\tgoto done;\n      +\t}\n      +\n     -+\tif (read_packetized_to_strbuf(connection->fd, answer,\n     -+\t\t\t\t      PACKET_READ_NEVER_DIE) < 0) {\n     ++\tif (read_packetized_to_strbuf(\n     ++\t\t    connection->fd, answer,\n     ++\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {\n      +\t\tret = error(_(\"could not read IPC response\"));\n      +\t\tgoto done;\n      +\t}\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\tstruct ipc_worker_thread_data *next_thread;\n      +\tstruct ipc_server_data *server_data;\n      +\tpthread_t pthread_id;\n     -+\tstruct packet_scratch_space scratch_write_buffer;\n      +};\n      +\n      +struct ipc_accept_thread_data {\n      +\tenum magic magic;\n      +\tstruct ipc_server_data *server_data;\n      +\n     -+\tint fd_listen;\n     -+\tstruct stat st_listen;\n     ++\tstruct unix_stream_server_socket *server_socket;\n      +\n      +\tint fd_send_shutdown;\n      +\tint fd_wait_shutdown;\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n      +\t\t       const char *response, size_t response_len)\n      +{\n     -+\tstruct packet_scratch_space *scratch =\n     -+\t\t&reply_data->worker_thread_data->scratch_write_buffer;\n     -+\n      +\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n      +\t\tBUG(\"reply_cb called with wrong instance data\");\n      +\n     -+\treturn write_packetized_from_buf2(response, response_len,\n     -+\t\t\t\t\t  reply_data->fd, 0, scratch);\n     ++\treturn write_packetized_from_buf_no_flush(response, response_len,\n     ++\t\t\t\t\t\t  reply_data->fd);\n      +}\n      +\n      +/* A randomly chosen value. */\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\n      +\treply_data.fd = fd;\n      +\n     -+\tret = read_packetized_to_strbuf(reply_data.fd, &buf,\n     -+\t\t\t\t\tPACKET_READ_NEVER_DIE);\n     ++\tret = read_packetized_to_strbuf(\n     ++\t\treply_data.fd, &buf,\n     ++\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);\n      +\tif (ret >= 0) {\n      +\t\tret = worker_thread_data->server_data->application_cb(\n      +\t\t\tworker_thread_data->server_data->application_data,\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\tif (ret == SIMPLE_IPC_QUIT) {\n      +\t\t\ttrace2_data_string(\"ipc-worker\", NULL, \"queue_stop_async\",\n      +\t\t\t\t\t   \"application_quit\");\n     -+\t\t\t/* The application told us to shutdown. */\n     ++\t\t\t/*\n     ++\t\t\t * The application layer is telling the ipc-server\n     ++\t\t\t * layer to shutdown.\n     ++\t\t\t *\n     ++\t\t\t * We DO NOT have a response to send to the client.\n     ++\t\t\t *\n     ++\t\t\t * Queue an async stop (to stop the other threads) and\n     ++\t\t\t * allow this worker thread to exit now (no sense waiting\n     ++\t\t\t * for the thread-pool shutdown signal).\n     ++\t\t\t *\n     ++\t\t\t * Other non-idle worker threads are allowed to finish\n     ++\t\t\t * responding to their current clients.\n     ++\t\t\t */\n      +\t\t\tipc_server_stop_async(server_data);\n      +\t\t\tbreak;\n      +\t\t}\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\treturn NULL;\n      +}\n      +\n     -+/*\n     -+ * Return 1 if someone deleted or stole the on-disk socket from us.\n     -+ */\n     -+static int socket_was_stolen(struct ipc_accept_thread_data *accept_thread_data)\n     -+{\n     -+\tstruct stat st;\n     -+\tstruct stat *ref_st = &accept_thread_data->st_listen;\n     -+\n     -+\tif (lstat(accept_thread_data->server_data->buf_path.buf, &st) == -1)\n     -+\t\treturn 1;\n     -+\n     -+\tif (st.st_ino != ref_st->st_ino)\n     -+\t\treturn 1;\n     -+\n     -+\t/* We might also consider the creation time on some platforms. */\n     -+\n     -+\treturn 0;\n     -+}\n     -+\n      +/* A randomly chosen value. */\n      +#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)\n      +\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\tpollfd[0].fd = accept_thread_data->fd_wait_shutdown;\n      +\t\tpollfd[0].events = POLLIN;\n      +\n     -+\t\tpollfd[1].fd = accept_thread_data->fd_listen;\n     ++\t\tpollfd[1].fd = accept_thread_data->server_socket->fd_socket;\n      +\t\tpollfd[1].events = POLLIN;\n      +\n      +\t\tresult = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\n      +\t\t\t/*\n      +\t\t\t * If someone deletes or force-creates a new unix\n     -+\t\t\t * domain socket at out path, all future clients\n     ++\t\t\t * domain socket at our path, all future clients\n      +\t\t\t * will be routed elsewhere and we silently starve.\n      +\t\t\t * If that happens, just queue a shutdown.\n      +\t\t\t */\n     -+\t\t\tif (socket_was_stolen(\n     -+\t\t\t\t    accept_thread_data)) {\n     ++\t\t\tif (unix_stream_server__was_stolen(\n     ++\t\t\t\t    accept_thread_data->server_socket)) {\n      +\t\t\t\ttrace2_data_string(\"ipc-accept\", NULL,\n      +\t\t\t\t\t\t   \"queue_stop_async\",\n      +\t\t\t\t\t\t   \"socket_stolen\");\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\t}\n      +\n      +\t\tif (pollfd[1].revents & POLLIN) {\n     -+\t\t\t/* a connection is available on fd_listen */\n     ++\t\t\t/* a connection is available on server_socket */\n      +\n     -+\t\t\tint client_fd = accept(accept_thread_data->fd_listen,\n     -+\t\t\t\t\t       NULL, NULL);\n     ++\t\t\tint client_fd =\n     ++\t\t\t\taccept(accept_thread_data->server_socket->fd_socket,\n     ++\t\t\t\t       NULL, NULL);\n      +\t\t\tif (client_fd >= 0)\n      +\t\t\t\treturn client_fd;\n      +\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      + */\n      +#define LISTEN_BACKLOG (50)\n      +\n     -+/*\n     -+ * Create a unix domain socket at the given path to listen for\n     -+ * client connections.  The resulting socket will then appear\n     -+ * in the filesystem as an inode with S_IFSOCK.  The inode is\n     -+ * itself created as part of the `bind(2)` operation.\n     -+ *\n     -+ * The term \"socket\" is ambiguous in this context.  We want to open a\n     -+ * \"socket-fd\" that is bound to a \"socket-inode\" (path) on disk.  We\n     -+ * listen on \"socket-fd\" for new connections and clients try to\n     -+ * open/connect using the \"socket-inode\" pathname.\n     -+ *\n     -+ * Unix domain sockets have a fundamental design flaw because the\n     -+ * \"socket-inode\" persists until the pathname is deleted; closing the\n     -+ * listening \"socket-fd\" only closes the socket handle/descriptor, it\n     -+ * does not delete the inode/pathname.\n     -+ *\n     -+ * Well-behaving service daemons are expected to also delete the inode\n     -+ * before shutdown.  If a service crashes (or forgets) it can leave\n     -+ * the (now stale) inode in the filesystem.  This behaves like a stale\n     -+ * \".lock\" file and may prevent future service instances from starting\n     -+ * up correctly.  (Because they won't be able to bind.)\n     -+ *\n     -+ * When future service instances try to create the listener socket,\n     -+ * `bind(2)` will fail with EADDRINUSE -- because the inode already\n     -+ * exists.  However, the new instance cannot tell if it is a stale\n     -+ * inode *or* another service instance is already running.\n     -+ *\n     -+ * One possible solution is to blindly unlink the inode before\n     -+ * attempting to bind a new socket-fd and thus create a new\n     -+ * socket-inode.  Then `bind(2)` should always succeed.  However, if\n     -+ * there is an existing service instance, it would be orphaned -- it\n     -+ * would still be listening on a socket-fd that is still bound to an\n     -+ * (unlinked) socket-inode, but that socket-inode is no longer\n     -+ * associated with the pathname.  New client connections will arrive\n     -+ * at OUR new socket-inode -- rather than the existing server's\n     -+ * socket.  (I suppose it is up to the existing server to detect that\n     -+ * its socket-inode has been stolen and shutdown.)\n     -+ *\n     -+ * Another possible solution is to try to use the \".lock\" trick, but\n     -+ * bind() does not have a exclusive-create use bit like open() does,\n     -+ * so we cannot have multiple servers fighting/racing to create the\n     -+ * same file name without having losers lose without knowing that they\n     -+ * lost.\n     -+ *\n     -+ * We try to avoid such stealing and would rather fail to run than\n     -+ * steal an existing socket-inode (because we assume that the\n     -+ * existing server has more context and value to the clients than a\n     -+ * freshly started server).  However, if multiple servers are racing\n     -+ * to start, we don't care which one wins -- none of them have any\n     -+ * state information yet worth fighting for.\n     -+ *\n     -+ * Create a \"unique\" socket-inode (with our PID in it (and assume that\n     -+ * we can force-delete an existing socket with that name)).  Stat it\n     -+ * to get the inode number and ctime -- so that we can identify it as\n     -+ * the one we created.  Then use the atomic-rename trick to install it\n     -+ * in the real location.  (This will unlink an existing socket with\n     -+ * that pathname -- and thereby steal the real socket-inode from an\n     -+ * existing server.)\n     -+ *\n     -+ * Elsewhere, our thread will periodically poll the socket-inode to\n     -+ * see if someone else steals ours.\n     -+ */\n     -+static int create_listener_socket(const char *path,\n     -+\t\t\t\t  const struct ipc_server_opts *ipc_opts,\n     -+\t\t\t\t  struct stat *st_socket)\n     ++static struct unix_stream_server_socket *create_listener_socket(\n     ++\tconst char *path,\n     ++\tconst struct ipc_server_opts *ipc_opts)\n      +{\n     -+\tstruct stat st;\n     -+\tstruct strbuf buf_uniq = STRBUF_INIT;\n     -+\tint fd_listen;\n     ++\tstruct unix_stream_server_socket *server_socket = NULL;\n      +\tstruct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n      +\n     -+\tif (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {\n     -+\t\tint fd_client;\n     -+\t\t/*\n     -+\t\t * A socket-inode at `path` exists on disk, but we\n     -+\t\t * don't know whether it belongs to an active server\n     -+\t\t * or if the last server died without cleaning up.\n     -+\t\t *\n     -+\t\t * Poke it with a trivial connection to try to find out.\n     -+\t\t */\n     -+\t\ttrace2_data_string(\"ipc-server\", NULL, \"try-detect-server\",\n     -+\t\t\t\t   path);\n     -+\t\tfd_client = unix_stream_connect(path);\n     -+\t\tif (fd_client >= 0) {\n     -+\t\t\tclose(fd_client);\n     -+\t\t\terrno = EADDRINUSE;\n     -+\t\t\treturn error_errno(_(\"socket already in use '%s'\"),\n     -+\t\t\t\t\t   path);\n     -+\t\t}\n     -+\t}\n     -+\n     -+\t/*\n     -+\t * Create pathname to our \"unique\" socket and set it up for\n     -+\t * business.\n     -+\t */\n     -+\tstrbuf_addf(&buf_uniq, \"%s.%d\", path, getpid());\n     -+\n      +\tuslg_opts.listen_backlog_size = LISTEN_BACKLOG;\n     -+\tuslg_opts.force_unlink_before_bind = 1;\n      +\tuslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;\n     -+\tfd_listen = unix_stream_listen(buf_uniq.buf, &uslg_opts);\n     -+\tif (fd_listen < 0) {\n     -+\t\tint saved_errno = errno;\n     -+\t\terror_errno(_(\"could not create listener socket '%s'\"),\n     -+\t\t\t    buf_uniq.buf);\n     -+\t\tstrbuf_release(&buf_uniq);\n     -+\t\terrno = saved_errno;\n     -+\t\treturn -1;\n     -+\t}\n      +\n     -+\tif (lstat(buf_uniq.buf, st_socket)) {\n     -+\t\tint saved_errno = errno;\n     -+\t\terror_errno(_(\"could not stat listener socket '%s'\"),\n     -+\t\t\t    buf_uniq.buf);\n     -+\t\tclose(fd_listen);\n     -+\t\tunlink(buf_uniq.buf);\n     -+\t\tstrbuf_release(&buf_uniq);\n     -+\t\terrno = saved_errno;\n     -+\t\treturn -1;\n     -+\t}\n     ++\tserver_socket = unix_stream_server__listen_with_lock(path, &uslg_opts);\n     ++\tif (!server_socket)\n     ++\t\treturn NULL;\n      +\n     -+\tif (set_socket_blocking_flag(fd_listen, 1)) {\n     ++\tif (set_socket_blocking_flag(server_socket->fd_socket, 1)) {\n      +\t\tint saved_errno = errno;\n      +\t\terror_errno(_(\"could not set listener socket nonblocking '%s'\"),\n     -+\t\t\t    buf_uniq.buf);\n     -+\t\tclose(fd_listen);\n     -+\t\tunlink(buf_uniq.buf);\n     -+\t\tstrbuf_release(&buf_uniq);\n     -+\t\terrno = saved_errno;\n     -+\t\treturn -1;\n     -+\t}\n     -+\n     -+\t/*\n     -+\t * Install it as the \"real\" socket so that clients will starting\n     -+\t * connecting to our socket.\n     -+\t */\n     -+\tif (rename(buf_uniq.buf, path)) {\n     -+\t\tint saved_errno = errno;\n     -+\t\terror_errno(_(\"could not create listener socket '%s'\"), path);\n     -+\t\tclose(fd_listen);\n     -+\t\tunlink(buf_uniq.buf);\n     -+\t\tstrbuf_release(&buf_uniq);\n     ++\t\t\t    path);\n     ++\t\tunix_stream_server__free(server_socket);\n      +\t\terrno = saved_errno;\n     -+\t\treturn -1;\n     ++\t\treturn NULL;\n      +\t}\n      +\n     -+\tstrbuf_release(&buf_uniq);\n     -+\ttrace2_data_string(\"ipc-server\", NULL, \"try-listen\", path);\n     -+\treturn fd_listen;\n     ++\ttrace2_data_string(\"ipc-server\", NULL, \"listen-with-lock\", path);\n     ++\treturn server_socket;\n      +}\n      +\n     -+static int setup_listener_socket(const char *path, struct stat *st_socket,\n     -+\t\t\t\t const struct ipc_server_opts *ipc_opts)\n     ++static struct unix_stream_server_socket *setup_listener_socket(\n     ++\tconst char *path,\n     ++\tconst struct ipc_server_opts *ipc_opts)\n      +{\n     -+\tint fd_listen;\n     ++\tstruct unix_stream_server_socket *server_socket;\n      +\n      +\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n     -+\tfd_listen = create_listener_socket(path, ipc_opts, st_socket);\n     ++\tserver_socket = create_listener_socket(path, ipc_opts);\n      +\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n      +\n     -+\treturn fd_listen;\n     ++\treturn server_socket;\n      +}\n      +\n      +/*\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\t\t ipc_server_application_cb *application_cb,\n      +\t\t\t void *application_data)\n      +{\n     ++\tstruct unix_stream_server_socket *server_socket = NULL;\n      +\tstruct ipc_server_data *server_data;\n     -+\tint fd_listen;\n     -+\tstruct stat st_listen;\n      +\tint sv[2];\n      +\tint k;\n      +\tint nr_threads = opts->nr_threads;\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\t\t\t   path);\n      +\t}\n      +\n     -+\tfd_listen = setup_listener_socket(path, &st_listen, opts);\n     -+\tif (fd_listen < 0) {\n     ++\tserver_socket = setup_listener_socket(path, opts);\n     ++\tif (!server_socket) {\n      +\t\tint saved_errno = errno;\n      +\t\tclose(sv[0]);\n      +\t\tclose(sv[1]);\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\txcalloc(1, sizeof(*server_data->accept_thread));\n      +\tserver_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;\n      +\tserver_data->accept_thread->server_data = server_data;\n     -+\tserver_data->accept_thread->fd_listen = fd_listen;\n     -+\tserver_data->accept_thread->st_listen = st_listen;\n     ++\tserver_data->accept_thread->server_socket = server_socket;\n      +\tserver_data->accept_thread->fd_send_shutdown = sv[0];\n      +\tserver_data->accept_thread->fd_wait_shutdown = sv[1];\n      +\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\n      +\taccept_thread_data = server_data->accept_thread;\n      +\tif (accept_thread_data) {\n     -+\t\tif (accept_thread_data->fd_listen != -1) {\n     -+\t\t\t/*\n     -+\t\t\t * Only unlink the unix domain socket if we\n     -+\t\t\t * created it.  That is, if another daemon\n     -+\t\t\t * process force-created a new socket at this\n     -+\t\t\t * path, and effectively steals our path\n     -+\t\t\t * (which prevents us from receiving any\n     -+\t\t\t * future clients), we don't want to do the\n     -+\t\t\t * same thing to them.\n     -+\t\t\t */\n     -+\t\t\tif (!socket_was_stolen(\n     -+\t\t\t\t    accept_thread_data))\n     -+\t\t\t\tunlink(server_data->buf_path.buf);\n     ++\t\tunix_stream_server__free(accept_thread_data->server_socket);\n      +\n     -+\t\t\tclose(accept_thread_data->fd_listen);\n     -+\t\t}\n      +\t\tif (accept_thread_data->fd_send_shutdown != -1)\n      +\t\t\tclose(accept_thread_data->fd_send_shutdown);\n      +\t\tif (accept_thread_data->fd_wait_shutdown != -1)\n     @@ simple-ipc.h\n       #define SUPPORTS_SIMPLE_IPC\n       #endif\n       \n     +@@ simple-ipc.h: struct ipc_client_connect_options {\n     + \t * the service and need to wait for it to become ready.\n     + \t */\n     + \tunsigned int wait_if_not_found:1;\n     ++\n     ++\t/*\n     ++\t * Disallow chdir() when creating a Unix domain socket.\n     ++\t */\n     ++\tunsigned int uds_disallow_chdir:1;\n     + };\n     + \n     + #define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n     + \t.wait_if_busy = 0, \\\n     + \t.wait_if_not_found = 0, \\\n     ++\t.uds_disallow_chdir = 0, \\\n     + }\n     + \n     + /*\n      @@ simple-ipc.h: struct ipc_server_data;\n       struct ipc_server_opts\n       {\n  9:  f0bebf1cdb31 ! 12:  1e5c856ade85 simple-ipc: add t/helper/test-simple-ipc and t0052\n     @@ Metadata\n      Author: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## Commit message ##\n     -    simple-ipc: add t/helper/test-simple-ipc and t0052\n     +    t0052: add simple-ipc tests and t/helper/test-simple-ipc tool\n      \n     -    Create unit tests for \"simple-ipc\".  These are currently only enabled\n     -    on Windows.\n     +    Create t0052-simple-ipc.sh with unit tests for the \"simple-ipc\" mechanism.\n     +\n     +    Create t/helper/test-simple-ipc test tool to exercise the \"simple-ipc\"\n     +    functions.\n     +\n     +    When the tool is invoked with \"run-daemon\", it runs a server to listen\n     +    for \"simple-ipc\" connections on a test socket or named pipe and\n     +    responds to a set of commands to exercise/stress the communication\n     +    setup.\n     +\n     +    When the tool is invoked with \"start-daemon\", it spawns a \"run-daemon\"\n     +    command in the background and waits for the server to become ready\n     +    before exiting.  (This helps make unit tests in t0052 more predictable\n     +    and avoids the need for arbitrary sleeps in the test script.)\n     +\n     +    The tool also has a series of client \"send\" commands to send commands\n     +    and data to a server instance.\n      \n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n     @@ t/helper/test-simple-ipc.c (new)\n      +#include \"simple-ipc.h\"\n      +#include \"parse-options.h\"\n      +#include \"thread-utils.h\"\n     ++#include \"strvec.h\"\n      +\n      +#ifndef SUPPORTS_SIMPLE_IPC\n      +int cmd__simple_ipc(int argc, const char **argv)\n     @@ t/helper/test-simple-ipc.c (new)\n      +\n      +\tif (!strcmp(command, \"quit\")) {\n      +\t\t/*\n     -+\t\t * Tell ipc-server to hangup with an empty reply.\n     ++\t\t * The client sent a \"quit\" command.  This is an async\n     ++\t\t * request for the server to shutdown.\n     ++\t\t *\n     ++\t\t * We DO NOT send the client a response message\n     ++\t\t * (because we have nothing to say and the other\n     ++\t\t * server threads have not yet stopped).\n     ++\t\t *\n     ++\t\t * Tell the ipc-server layer to start shutting down.\n     ++\t\t * This includes: stop listening for new connections\n     ++\t\t * on the socket/pipe and telling all worker threads\n     ++\t\t * to finish/drain their outgoing responses to other\n     ++\t\t * clients.\n     ++\t\t *\n     ++\t\t * This DOES NOT force an immediate sync shutdown.\n      +\t\t */\n      +\t\treturn SIMPLE_IPC_QUIT;\n      +\t}\n     @@ t/helper/test-simple-ipc.c (new)\n      +\t};\n      +\n      +\tconst char * const daemon_usage[] = {\n     -+\t\tN_(\"test-helper simple-ipc daemon [<options>\"),\n     ++\t\tN_(\"test-helper simple-ipc run-daemon [<options>\"),\n      +\t\tNULL\n      +\t};\n      +\tstruct option daemon_options[] = {\n     @@ t/helper/test-simple-ipc.c (new)\n      +\treturn ipc_server_run(path, &opts, test_app_cb, (void*)&my_app_data);\n      +}\n      +\n     ++#ifndef GIT_WINDOWS_NATIVE\n     ++/*\n     ++ * This is adapted from `daemonize()`.  Use `fork()` to directly create and\n     ++ * run the daemon in a child process.\n     ++ */\n     ++static int spawn_server(const char *path,\n     ++\t\t\tconst struct ipc_server_opts *opts,\n     ++\t\t\tpid_t *pid)\n     ++{\n     ++\t*pid = fork();\n     ++\n     ++\tswitch (*pid) {\n     ++\tcase 0:\n     ++\t\tif (setsid() == -1)\n     ++\t\t\terror_errno(_(\"setsid failed\"));\n     ++\t\tclose(0);\n     ++\t\tclose(1);\n     ++\t\tclose(2);\n     ++\t\tsanitize_stdfds();\n     ++\n     ++\t\treturn ipc_server_run(path, opts, test_app_cb, (void*)&my_app_data);\n     ++\n     ++\tcase -1:\n     ++\t\treturn error_errno(_(\"could not spawn daemon in the background\"));\n     ++\n     ++\tdefault:\n     ++\t\treturn 0;\n     ++\t}\n     ++}\n     ++#else\n     ++/*\n     ++ * Conceptually like `daemonize()` but different because Windows does not\n     ++ * have `fork(2)`.  Spawn a normal Windows child process but without the\n     ++ * limitations of `start_command()` and `finish_command()`.\n     ++ */\n     ++static int spawn_server(const char *path,\n     ++\t\t\tconst struct ipc_server_opts *opts,\n     ++\t\t\tpid_t *pid)\n     ++{\n     ++\tchar test_tool_exe[MAX_PATH];\n     ++\tstruct strvec args = STRVEC_INIT;\n     ++\tint in, out;\n     ++\n     ++\tGetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);\n     ++\n     ++\tin = open(\"/dev/null\", O_RDONLY);\n     ++\tout = open(\"/dev/null\", O_WRONLY);\n     ++\n     ++\tstrvec_push(&args, test_tool_exe);\n     ++\tstrvec_push(&args, \"simple-ipc\");\n     ++\tstrvec_push(&args, \"run-daemon\");\n     ++\tstrvec_pushf(&args, \"--threads=%d\", opts->nr_threads);\n     ++\n     ++\t*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);\n     ++\tclose(in);\n     ++\tclose(out);\n     ++\n     ++\tstrvec_clear(&args);\n     ++\n     ++\tif (*pid < 0)\n     ++\t\treturn error(_(\"could not spawn daemon in the background\"));\n     ++\n     ++\treturn 0;\n     ++}\n     ++#endif\n     ++\n     ++/*\n     ++ * This is adapted from `wait_or_whine()`.  Watch the child process and\n     ++ * let it get started and begin listening for requests on the socket\n     ++ * before reporting our success.\n     ++ */\n     ++static int wait_for_server_startup(const char * path, pid_t pid_child,\n     ++\t\t\t\t   int max_wait_sec)\n     ++{\n     ++\tint status;\n     ++\tpid_t pid_seen;\n     ++\tenum ipc_active_state s;\n     ++\ttime_t time_limit, now;\n     ++\n     ++\ttime(&time_limit);\n     ++\ttime_limit += max_wait_sec;\n     ++\n     ++\tfor (;;) {\n     ++\t\tpid_seen = waitpid(pid_child, &status, WNOHANG);\n     ++\n     ++\t\tif (pid_seen == -1)\n     ++\t\t\treturn error_errno(_(\"waitpid failed\"));\n     ++\n     ++\t\telse if (pid_seen == 0) {\n     ++\t\t\t/*\n     ++\t\t\t * The child is still running (this should be\n     ++\t\t\t * the normal case).  Try to connect to it on\n     ++\t\t\t * the socket and see if it is ready for\n     ++\t\t\t * business.\n     ++\t\t\t *\n     ++\t\t\t * If there is another daemon already running,\n     ++\t\t\t * our child will fail to start (possibly\n     ++\t\t\t * after a timeout on the lock), but we don't\n     ++\t\t\t * care (who responds) if the socket is live.\n     ++\t\t\t */\n     ++\t\t\ts = ipc_get_active_state(path);\n     ++\t\t\tif (s == IPC_STATE__LISTENING)\n     ++\t\t\t\treturn 0;\n     ++\n     ++\t\t\ttime(&now);\n     ++\t\t\tif (now > time_limit)\n     ++\t\t\t\treturn error(_(\"daemon not online yet\"));\n     ++\n     ++\t\t\tcontinue;\n     ++\t\t}\n     ++\n     ++\t\telse if (pid_seen == pid_child) {\n     ++\t\t\t/*\n     ++\t\t\t * The new child daemon process shutdown while\n     ++\t\t\t * it was starting up, so it is not listening\n     ++\t\t\t * on the socket.\n     ++\t\t\t *\n     ++\t\t\t * Try to ping the socket in the odd chance\n     ++\t\t\t * that another daemon started (or was already\n     ++\t\t\t * running) while our child was starting.\n     ++\t\t\t *\n     ++\t\t\t * Again, we don't care who services the socket.\n     ++\t\t\t */\n     ++\t\t\ts = ipc_get_active_state(path);\n     ++\t\t\tif (s == IPC_STATE__LISTENING)\n     ++\t\t\t\treturn 0;\n     ++\n     ++\t\t\t/*\n     ++\t\t\t * We don't care about the WEXITSTATUS() nor\n     ++\t\t\t * any of the WIF*(status) values because\n     ++\t\t\t * `cmd__simple_ipc()` does the `!!result`\n     ++\t\t\t * trick on all function return values.\n     ++\t\t\t *\n     ++\t\t\t * So it is sufficient to just report the\n     ++\t\t\t * early shutdown as an error.\n     ++\t\t\t */\n     ++\t\t\treturn error(_(\"daemon failed to start\"));\n     ++\t\t}\n     ++\n     ++\t\telse\n     ++\t\t\treturn error(_(\"waitpid is confused\"));\n     ++\t}\n     ++}\n     ++\n     ++/*\n     ++ * This process will start a simple-ipc server in a background process and\n     ++ * wait for it to become ready.  This is like `daemonize()` but gives us\n     ++ * more control and better error reporting (and makes it easier to write\n     ++ * unit tests).\n     ++ */\n     ++static int daemon__start_server(const char *path, int argc, const char **argv)\n     ++{\n     ++\tpid_t pid_child;\n     ++\tint ret;\n     ++\tint max_wait_sec = 60;\n     ++\tstruct ipc_server_opts opts = {\n     ++\t\t.nr_threads = 5\n     ++\t};\n     ++\n     ++\tconst char * const daemon_usage[] = {\n     ++\t\tN_(\"test-helper simple-ipc start-daemon [<options>\"),\n     ++\t\tNULL\n     ++\t};\n     ++\n     ++\tstruct option daemon_options[] = {\n     ++\t\tOPT_INTEGER(0, \"max-wait\", &max_wait_sec,\n     ++\t\t\t    N_(\"seconds to wait for daemon to startup\")),\n     ++\t\tOPT_INTEGER(0, \"threads\", &opts.nr_threads,\n     ++\t\t\t    N_(\"number of threads in server thread pool\")),\n     ++\t\tOPT_END()\n     ++\t};\n     ++\n     ++\targc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);\n     ++\n     ++\tif (max_wait_sec < 0)\n     ++\t\tmax_wait_sec = 0;\n     ++\tif (opts.nr_threads < 1)\n     ++\t\topts.nr_threads = 1;\n     ++\n     ++\t/*\n     ++\t * Run the actual daemon in a background process.\n     ++\t */\n     ++\tret = spawn_server(path, &opts, &pid_child);\n     ++\tif (pid_child <= 0)\n     ++\t\treturn ret;\n     ++\n     ++\t/*\n     ++\t * Let the parent wait for the child process to get started\n     ++\t * and begin listening for requests on the socket.\n     ++\t */\n     ++\tret = wait_for_server_startup(path, pid_child, max_wait_sec);\n     ++\n     ++\treturn ret;\n     ++}\n     ++\n      +/*\n      + * This process will run a quick probe to see if a simple-ipc server\n      + * is active on this path.\n     @@ t/helper/test-simple-ipc.c (new)\n      +\toptions.wait_if_not_found = 0;\n      +\n      +\tif (!ipc_client_send_command(path, &options, command, &buf)) {\n     -+\t\tprintf(\"%s\\n\", buf.buf);\n     -+\t\tfflush(stdout);\n     ++\t\tif (buf.len) {\n     ++\t\t\tprintf(\"%s\\n\", buf.buf);\n     ++\t\t\tfflush(stdout);\n     ++\t\t}\n      +\t\tstrbuf_release(&buf);\n      +\n      +\t\treturn 0;\n     @@ t/helper/test-simple-ipc.c (new)\n      + * message can be sent and that the kernel or pkt-line layers will\n      + * properly chunk it and that the daemon receives the entire message.\n      + */\n     -+static int do_sendbytes(int bytecount, char byte, const char *path)\n     ++static int do_sendbytes(int bytecount, char byte, const char *path,\n     ++\t\t\tconst struct ipc_client_connect_options *options)\n      +{\n      +\tstruct strbuf buf_send = STRBUF_INIT;\n      +\tstruct strbuf buf_resp = STRBUF_INIT;\n     -+\tstruct ipc_client_connect_options options\n     -+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n     -+\n     -+\toptions.wait_if_busy = 1;\n     -+\toptions.wait_if_not_found = 0;\n      +\n      +\tstrbuf_addstr(&buf_send, \"sendbytes \");\n      +\tstrbuf_addchars(&buf_send, byte, bytecount);\n      +\n     -+\tif (!ipc_client_send_command(path, &options, buf_send.buf, &buf_resp)) {\n     ++\tif (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {\n      +\t\tstrbuf_rtrim(&buf_resp);\n      +\t\tprintf(\"sent:%c%08d %s\\n\", byte, bytecount, buf_resp.buf);\n      +\t\tfflush(stdout);\n     @@ t/helper/test-simple-ipc.c (new)\n      +\t\tOPT_STRING(0, \"byte\", &string, N_(\"byte\"), N_(\"ballast\")),\n      +\t\tOPT_END()\n      +\t};\n     ++\tstruct ipc_client_connect_options options\n     ++\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n     ++\n     ++\toptions.wait_if_busy = 1;\n     ++\toptions.wait_if_not_found = 0;\n     ++\toptions.uds_disallow_chdir = 0;\n      +\n      +\targc = parse_options(argc, argv, NULL, sendbytes_options, sendbytes_usage, 0);\n      +\n     -+\treturn do_sendbytes(bytecount, string[0], path);\n     ++\treturn do_sendbytes(bytecount, string[0], path, &options);\n      +}\n      +\n      +struct multiple_thread_data {\n     @@ t/helper/test-simple-ipc.c (new)\n      +{\n      +\tstruct multiple_thread_data *d = _multiple_thread_data;\n      +\tint k;\n     ++\tstruct ipc_client_connect_options options\n     ++\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n     ++\n     ++\toptions.wait_if_busy = 1;\n     ++\toptions.wait_if_not_found = 0;\n     ++\t/*\n     ++\t * A multi-threaded client should not be randomly calling chdir().\n     ++\t * The test will pass without this restriction because the test is\n     ++\t * not otherwise accessing the filesystem, but it makes us honest.\n     ++\t */\n     ++\toptions.uds_disallow_chdir = 1;\n      +\n      +\ttrace2_thread_start(\"multiple\");\n      +\n      +\tfor (k = 0; k < d->batchsize; k++) {\n     -+\t\tif (do_sendbytes(d->bytecount + k, d->letter, d->path))\n     ++\t\tif (do_sendbytes(d->bytecount + k, d->letter, d->path, &options))\n      +\t\t\td->sum_errors++;\n      +\t\telse\n      +\t\t\td->sum_good++;\n     @@ t/helper/test-simple-ipc.c (new)\n      +\tif (argc == 2 && !strcmp(argv[1], \"SUPPORTS_SIMPLE_IPC\"))\n      +\t\treturn 0;\n      +\n     -+\t/* Use '!!' on all dispatch functions to map from `error()` style\n     -+\t * (returns -1) style to `test_must_fail` style (expects 1) and\n     -+\t * get less confusing shell error messages.\n     ++\t/*\n     ++\t * Use '!!' on all dispatch functions to map from `error()` style\n     ++\t * (returns -1) style to `test_must_fail` style (expects 1).  This\n     ++\t * makes shell error messages less confusing.\n      +\t */\n      +\n      +\tif (argc == 2 && !strcmp(argv[1], \"is-active\"))\n      +\t\treturn !!client__probe_server(path);\n      +\n     -+\tif (argc >= 2 && !strcmp(argv[1], \"daemon\"))\n     ++\tif (argc >= 2 && !strcmp(argv[1], \"run-daemon\"))\n      +\t\treturn !!daemon__run_server(path, argc, argv);\n      +\n     ++\tif (argc >= 2 && !strcmp(argv[1], \"start-daemon\"))\n     ++\t\treturn !!daemon__start_server(path, argc, argv);\n     ++\n      +\t/*\n      +\t * Client commands follow.  Ensure a server is running before\n      +\t * going any further.\n     @@ t/t0052-simple-ipc.sh (new)\n      +}\n      +\n      +stop_simple_IPC_server () {\n     -+\ttest -n \"$SIMPLE_IPC_PID\" || return 0\n     -+\n     -+\tkill \"$SIMPLE_IPC_PID\" &&\n     -+\tSIMPLE_IPC_PID=\n     ++\ttest-tool simple-ipc send quit\n      +}\n      +\n      +test_expect_success 'start simple command server' '\n     -+\t{ test-tool simple-ipc daemon --threads=8 & } &&\n     -+\tSIMPLE_IPC_PID=$! &&\n      +\ttest_atexit stop_simple_IPC_server &&\n     -+\n     -+\tsleep 1 &&\n     -+\n     ++\ttest-tool simple-ipc start-daemon --threads=8 &&\n      +\ttest-tool simple-ipc is-active\n      +'\n      +\n     @@ t/t0052-simple-ipc.sh (new)\n      +'\n      +\n      +test_expect_success 'servers cannot share the same path' '\n     -+\ttest_must_fail test-tool simple-ipc daemon &&\n     ++\ttest_must_fail test-tool simple-ipc run-daemon &&\n      +\ttest-tool simple-ipc is-active\n      +'\n      +\n     @@ t/t0052-simple-ipc.sh (new)\n      +\ttest_cmp expect_a actual_a\n      +'\n      +\n     ++# Sending a \"quit\" message to the server causes it to start an \"async\n     ++# shutdown\" -- queuing shutdown events to all socket/pipe thread-pool\n     ++# threads.  Each thread will process that event after finishing\n     ++# (draining) any in-progress IO with other clients.  So when the \"send\n     ++# quit\" client command exits, the ipc-server may still be running (but\n     ++# it should be cleaning up).\n     ++#\n     ++# So, insert a generous sleep here to give the server time to shutdown.\n     ++#\n      +test_expect_success '`quit` works' '\n      +\ttest-tool simple-ipc send quit &&\n     ++\n     ++\tsleep 5 &&\n     ++\n      +\ttest_must_fail test-tool simple-ipc is-active &&\n      +\ttest_must_fail test-tool simple-ipc send ping\n      +'\n 13:  2cca15a10ece <  -:  ------------ unix-socket: do not call die in unix_stream_connect()\n\n-- \ngitgitgadget\n"},{"id":"416814","messageId":"b368318e6a23f8c4e60f77a8b81b558c523d5b03.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 07/12] unix-socket: elimiate static unix_stream_socket() helper function","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:08Z","receivedAt":"2021-02-13T00:10:53Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nThe static helper function `unix_stream_socket()` calls `die()`.  This\nis not appropriate for all callers.  Eliminate the wrapper function\nand make the callers propagate the error.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 27 +++++++++++++--------------\n 1 file changed, 13 insertions(+), 14 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 19ed48be9902..69f81d64e9d5 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,14 +1,6 @@\n #include \"cache.h\"\n #include \"unix-socket.h\"\n \n-static int unix_stream_socket(void)\n-{\n-\tint fd = socket(AF_UNIX, SOCK_STREAM, 0);\n-\tif (fd < 0)\n-\t\tdie_errno(\"unable to create socket\");\n-\treturn fd;\n-}\n-\n static int chdir_len(const char *orig, int len)\n {\n \tchar *path = xmemdupz(orig, len);\n@@ -73,13 +65,16 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \n int unix_stream_connect(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n+\n \tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \tunix_sockaddr_cleanup(&ctx);\n@@ -87,15 +82,16 @@ int unix_stream_connect(const char *path)\n \n fail:\n \tsaved_errno = errno;\n+\tif (fd != -1)\n+\t\tclose(fd);\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n \n int unix_stream_listen(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -103,7 +99,9 @@ int unix_stream_listen(const char *path)\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n \n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n@@ -116,8 +114,9 @@ int unix_stream_listen(const char *path)\n \n fail:\n \tsaved_errno = errno;\n+\tif (fd != -1)\n+\t\tclose(fd);\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n-- \ngitgitgadget\n\n"},{"id":"416815","messageId":"985b2e02b2df7725d70f1365f7cd2e525c9f3ade.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 08/12] unix-socket: add backlog size option to unix_stream_listen()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:09Z","receivedAt":"2021-02-13T00:11:06Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nUpdate `unix_stream_listen()` to take an options structure to override\ndefault behaviors.  This commit includes the size of the `listen()` backlog.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache--daemon.c |  3 ++-\n unix-socket.c                      |  9 +++++++--\n unix-socket.h                      | 14 +++++++++++++-\n 3 files changed, 22 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c\nindex c61f123a3b81..4c6c89ab0de2 100644\n--- a/builtin/credential-cache--daemon.c\n+++ b/builtin/credential-cache--daemon.c\n@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)\n \n static void serve_cache(const char *socket_path, int debug)\n {\n+\tstruct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n \tint fd;\n \n-\tfd = unix_stream_listen(socket_path);\n+\tfd = unix_stream_listen(socket_path, &opts);\n \tif (fd < 0)\n \t\tdie_errno(\"unable to bind to '%s'\", socket_path);\n \ndiff --git a/unix-socket.c b/unix-socket.c\nindex 69f81d64e9d5..5ac7dafe9828 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -89,9 +89,11 @@ int unix_stream_connect(const char *path)\n \treturn -1;\n }\n \n-int unix_stream_listen(const char *path)\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts)\n {\n \tint fd = -1, saved_errno;\n+\tint backlog;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -106,7 +108,10 @@ int unix_stream_listen(const char *path)\n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \n-\tif (listen(fd, 5) < 0)\n+\tbacklog = opts->listen_backlog_size;\n+\tif (backlog <= 0)\n+\t\tbacklog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;\n+\tif (listen(fd, backlog) < 0)\n \t\tgoto fail;\n \n \tunix_sockaddr_cleanup(&ctx);\ndiff --git a/unix-socket.h b/unix-socket.h\nindex e271aeec5a07..06a5a05b03fe 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -1,7 +1,19 @@\n #ifndef UNIX_SOCKET_H\n #define UNIX_SOCKET_H\n \n+struct unix_stream_listen_opts {\n+\tint listen_backlog_size;\n+};\n+\n+#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n+\n+#define UNIX_STREAM_LISTEN_OPTS_INIT \\\n+{ \\\n+\t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n+}\n+\n int unix_stream_connect(const char *path);\n-int unix_stream_listen(const char *path);\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts);\n \n #endif /* UNIX_SOCKET_H */\n-- \ngitgitgadget\n\n"},{"id":"416816","messageId":"b443e11ac32fd3082a59ada42ada8c8973fa0b8a.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 10/12] unix-socket: create `unix_stream_server__listen_with_lock()`","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:11Z","receivedAt":"2021-02-13T00:11:27Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a version of `unix_stream_listen()` that uses a \".lock\" lockfile\nto create the unix domain socket in a race-free manner.\n\nUnix domain sockets have a fundamental problem on Unix systems because\nthey persist in the filesystem until they are deleted.  This is\nindependent of whether a server is actually listening for connections.\nWell-behaved servers are expected to delete the socket when they\nshutdown.  A new server cannot easily tell if a found socket is\nattached to an active server or is leftover cruft from a dead server.\nThe traditional solution used by `unix_stream_listen()` is to force\ndelete the socket pathname and then create a new socket.  This solves\nthe latter (cruft) problem, but in the case of the former, it orphans\nthe existing server (by stealing the pathname associated with the\nsocket it is listening on).\n\nWe cannot directly use a .lock lockfile to create the socket because\nthe socket is created by `bind(2)` rather than the `open(2)` mechanism\nused by `tempfile.c`.\n\nAs an alternative, we hold a plain lockfile (\"<path>.lock\") as a\nmutual exclusion device.  Under the lock, we test if an existing\nsocket (\"<path>\") is has an active server.  If not, create a new\nsocket and begin listening.  Then we rollback the lockfile in all\ncases.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++\n unix-socket.h |  29 +++++++++++++\n 2 files changed, 144 insertions(+)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 1eaa8cf759c0..647bbde37f97 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,4 +1,5 @@\n #include \"cache.h\"\n+#include \"lockfile.h\"\n #include \"unix-socket.h\"\n \n static int chdir_len(const char *orig, int len)\n@@ -132,3 +133,117 @@ int unix_stream_listen(const char *path,\n \terrno = saved_errno;\n \treturn -1;\n }\n+\n+static int is_another_server_alive(const char *path,\n+\t\t\t\t   const struct unix_stream_listen_opts *opts)\n+{\n+\tstruct stat st;\n+\tint fd;\n+\n+\tif (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {\n+\t\t/*\n+\t\t * A socket-inode exists on disk at `path`, but we\n+\t\t * don't know whether it belongs to an active server\n+\t\t * or whether the last server died without cleaning\n+\t\t * up.\n+\t\t *\n+\t\t * Poke it with a trivial connection to try to find\n+\t\t * out.\n+\t\t */\n+\t\tfd = unix_stream_connect(path, opts->disallow_chdir);\n+\t\tif (fd >= 0) {\n+\t\t\tclose(fd);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+struct unix_stream_server_socket *unix_stream_server__listen_with_lock(\n+\tconst char *path,\n+\tconst struct unix_stream_listen_opts *opts)\n+{\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd_socket;\n+\tstruct unix_stream_server_socket *server_socket;\n+\n+\t/*\n+\t * Create a lock at \"<path>.lock\" if we can.\n+\t */\n+\tif (hold_lock_file_for_update_timeout(&lock, path, 0,\n+\t\t\t\t\t      opts->timeout_ms) < 0) {\n+\t\terror_errno(_(\"could not lock listener socket '%s'\"), path);\n+\t\treturn NULL;\n+\t}\n+\n+\t/*\n+\t * If another server is listening on \"<path>\" give up.  We do not\n+\t * want to create a socket and steal future connections from them.\n+\t */\n+\tif (is_another_server_alive(path, opts)) {\n+\t\terrno = EADDRINUSE;\n+\t\terror_errno(_(\"listener socket already in use '%s'\"), path);\n+\t\trollback_lock_file(&lock);\n+\t\treturn NULL;\n+\t}\n+\n+\t/*\n+\t * Create and bind to a Unix domain socket at \"<path>\".\n+\t */\n+\tfd_socket = unix_stream_listen(path, opts);\n+\tif (fd_socket < 0) {\n+\t\terror_errno(_(\"could not create listener socket '%s'\"), path);\n+\t\trollback_lock_file(&lock);\n+\t\treturn NULL;\n+\t}\n+\n+\tserver_socket = xcalloc(1, sizeof(*server_socket));\n+\tserver_socket->path_socket = strdup(path);\n+\tserver_socket->fd_socket = fd_socket;\n+\tlstat(path, &server_socket->st_socket);\n+\n+\t/*\n+\t * Always rollback (just delete) \"<path>.lock\" because we already created\n+\t * \"<path>\" as a socket and do not want to commit_lock to do the atomic\n+\t * rename trick.\n+\t */\n+\trollback_lock_file(&lock);\n+\n+\treturn server_socket;\n+}\n+\n+void unix_stream_server__free(\n+\tstruct unix_stream_server_socket *server_socket)\n+{\n+\tif (!server_socket)\n+\t\treturn;\n+\n+\tif (server_socket->fd_socket >= 0) {\n+\t\tif (!unix_stream_server__was_stolen(server_socket))\n+\t\t\tunlink(server_socket->path_socket);\n+\t\tclose(server_socket->fd_socket);\n+\t}\n+\n+\tfree(server_socket->path_socket);\n+\tfree(server_socket);\n+}\n+\n+int unix_stream_server__was_stolen(\n+\tstruct unix_stream_server_socket *server_socket)\n+{\n+\tstruct stat st_now;\n+\n+\tif (!server_socket)\n+\t\treturn 0;\n+\n+\tif (lstat(server_socket->path_socket, &st_now) == -1)\n+\t\treturn 1;\n+\n+\tif (st_now.st_ino != server_socket->st_socket.st_ino)\n+\t\treturn 1;\n+\n+\t/* We might also consider the ctime on some platforms. */\n+\n+\treturn 0;\n+}\ndiff --git a/unix-socket.h b/unix-socket.h\nindex 2c0b2e79d7b3..8faf5b692f90 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -2,14 +2,17 @@\n #define UNIX_SOCKET_H\n \n struct unix_stream_listen_opts {\n+\tlong timeout_ms;\n \tint listen_backlog_size;\n \tunsigned int disallow_chdir:1;\n };\n \n+#define DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT (100)\n #define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n \n #define UNIX_STREAM_LISTEN_OPTS_INIT \\\n { \\\n+\t.timeout_ms = DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT, \\\n \t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n \t.disallow_chdir = 0, \\\n }\n@@ -18,4 +21,30 @@ int unix_stream_connect(const char *path, int disallow_chdir);\n int unix_stream_listen(const char *path,\n \t\t       const struct unix_stream_listen_opts *opts);\n \n+struct unix_stream_server_socket {\n+\tchar *path_socket;\n+\tstruct stat st_socket;\n+\tint fd_socket;\n+};\n+\n+/*\n+ * Create a Unix Domain Socket at the given path under the protection\n+ * of a '.lock' lockfile.\n+ */\n+struct unix_stream_server_socket *unix_stream_server__listen_with_lock(\n+\tconst char *path,\n+\tconst struct unix_stream_listen_opts *opts);\n+\n+/*\n+ * Close and delete the socket.\n+ */\n+void unix_stream_server__free(\n+\tstruct unix_stream_server_socket *server_socket);\n+\n+/*\n+ * Return 1 if the inode of the pathname to our socket changes.\n+ */\n+int unix_stream_server__was_stolen(\n+\tstruct unix_stream_server_socket *server_socket);\n+\n #endif /* UNIX_SOCKET_H */\n-- \ngitgitgadget\n\n"},{"id":"416817","messageId":"1bfa36409d0706d5e22703f80bf95dfa1a313a83.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:10Z","receivedAt":"2021-02-13T00:11:34Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCalls to `chdir()` are dangerous in a multi-threaded context.  If\n`unix_stream_listen()` or `unix_stream_connect()` is given a socket\npathname that is too long to fit in a `sockaddr_un` structure, it will\n`chdir()` to the parent directory of the requested socket pathname,\ncreate the socket using a relative pathname, and then `chdir()` back.\nThis is not thread-safe.\n\nTeach `unix_sockaddr_init()` to not allow calls to `chdir()` when this\nflag is set.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache.c |  2 +-\n unix-socket.c              | 17 ++++++++++++-----\n unix-socket.h              |  4 +++-\n 3 files changed, 16 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/credential-cache.c b/builtin/credential-cache.c\nindex 9b3f70990597..76a6ba37223f 100644\n--- a/builtin/credential-cache.c\n+++ b/builtin/credential-cache.c\n@@ -14,7 +14,7 @@\n static int send_request(const char *socket, const struct strbuf *out)\n {\n \tint got_data = 0;\n-\tint fd = unix_stream_connect(socket);\n+\tint fd = unix_stream_connect(socket, 0);\n \n \tif (fd < 0)\n \t\treturn -1;\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 5ac7dafe9828..1eaa8cf759c0 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -28,16 +28,23 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n }\n \n static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n-\t\t\t      struct unix_sockaddr_context *ctx)\n+\t\t\t      struct unix_sockaddr_context *ctx,\n+\t\t\t      int disallow_chdir)\n {\n \tint size = strlen(path) + 1;\n \n \tctx->orig_dir = NULL;\n \tif (size > sizeof(sa->sun_path)) {\n-\t\tconst char *slash = find_last_dir_sep(path);\n+\t\tconst char *slash;\n \t\tconst char *dir;\n \t\tstruct strbuf cwd = STRBUF_INIT;\n \n+\t\tif (disallow_chdir) {\n+\t\t\terrno = ENAMETOOLONG;\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tslash = find_last_dir_sep(path);\n \t\tif (!slash) {\n \t\t\terrno = ENAMETOOLONG;\n \t\t\treturn -1;\n@@ -63,13 +70,13 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \treturn 0;\n }\n \n-int unix_stream_connect(const char *path)\n+int unix_stream_connect(const char *path, int disallow_chdir)\n {\n \tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\tif (unix_sockaddr_init(&sa, path, &ctx, disallow_chdir) < 0)\n \t\treturn -1;\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\n@@ -99,7 +106,7 @@ int unix_stream_listen(const char *path,\n \n \tunlink(path);\n \n-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\tif (unix_sockaddr_init(&sa, path, &ctx, opts->disallow_chdir) < 0)\n \t\treturn -1;\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\ndiff --git a/unix-socket.h b/unix-socket.h\nindex 06a5a05b03fe..2c0b2e79d7b3 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -3,6 +3,7 @@\n \n struct unix_stream_listen_opts {\n \tint listen_backlog_size;\n+\tunsigned int disallow_chdir:1;\n };\n \n #define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n@@ -10,9 +11,10 @@ struct unix_stream_listen_opts {\n #define UNIX_STREAM_LISTEN_OPTS_INIT \\\n { \\\n \t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n+\t.disallow_chdir = 0, \\\n }\n \n-int unix_stream_connect(const char *path);\n+int unix_stream_connect(const char *path, int disallow_chdir);\n int unix_stream_listen(const char *path,\n \t\t       const struct unix_stream_listen_opts *opts);\n \n-- \ngitgitgadget\n\n"},{"id":"416818","messageId":"1e5c856ade8557d7514d9bee1c58bf978aba062c.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:13Z","receivedAt":"2021-02-13T00:11:44Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate t0052-simple-ipc.sh with unit tests for the \"simple-ipc\" mechanism.\n\nCreate t/helper/test-simple-ipc test tool to exercise the \"simple-ipc\"\nfunctions.\n\nWhen the tool is invoked with \"run-daemon\", it runs a server to listen\nfor \"simple-ipc\" connections on a test socket or named pipe and\nresponds to a set of commands to exercise/stress the communication\nsetup.\n\nWhen the tool is invoked with \"start-daemon\", it spawns a \"run-daemon\"\ncommand in the background and waits for the server to become ready\nbefore exiting.  (This helps make unit tests in t0052 more predictable\nand avoids the need for arbitrary sleeps in the test script.)\n\nThe tool also has a series of client \"send\" commands to send commands\nand data to a server instance.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                   |   1 +\n t/helper/test-simple-ipc.c | 713 +++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0052-simple-ipc.sh      | 134 +++++++\n 5 files changed, 850 insertions(+)\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\ndiff --git a/Makefile b/Makefile\nindex 08a4c88b92f5..93f2e7ca9e1f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -740,6 +740,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o\n TEST_BUILTINS_OBJS += test-sha1.o\n TEST_BUILTINS_OBJS += test-sha256.o\n TEST_BUILTINS_OBJS += test-sigchain.o\n+TEST_BUILTINS_OBJS += test-simple-ipc.o\n TEST_BUILTINS_OBJS += test-strcmp-offset.o\n TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\ndiff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c\nnew file mode 100644\nindex 000000000000..92aa7f843cfa\n--- /dev/null\n+++ b/t/helper/test-simple-ipc.c\n@@ -0,0 +1,713 @@\n+/*\n+ * test-simple-ipc.c: verify that the Inter-Process Communication works.\n+ */\n+\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"simple-ipc.h\"\n+#include \"parse-options.h\"\n+#include \"thread-utils.h\"\n+#include \"strvec.h\"\n+\n+#ifndef SUPPORTS_SIMPLE_IPC\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tdie(\"simple IPC not available on this platform\");\n+}\n+#else\n+\n+/*\n+ * The test daemon defines an \"application callback\" that supports a\n+ * series of commands (see `test_app_cb()`).\n+ *\n+ * Unknown commands are caught here and we send an error message back\n+ * to the client process.\n+ */\n+static int app__unhandled_command(const char *command,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n+\n+\tstrbuf_addf(&buf, \"unhandled command: %s\", command);\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a single very large buffer.  This is to ensure that\n+ * long response are properly handled -- whether the chunking occurs\n+ * in the kernel or in the (probably pkt-line) layer.\n+ */\n+#define BIG_ROWS (10000)\n+static int app__big_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t    struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < BIG_ROWS; row++)\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a series of lines.  This is to ensure that we can incrementally\n+ * compute the response and chunk it to the client.\n+ */\n+#define CHUNK_ROWS (10000)\n+static int app__chunk_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t      struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < CHUNK_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Slowly reply with a series of lines.  This is to model an expensive to\n+ * compute chunked response (which might happen if this callback is running\n+ * in a thread and is fighting for a lock with other threads).\n+ */\n+#define SLOW_ROWS     (1000)\n+#define SLOW_DELAY_MS (10)\n+static int app__slow_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t     struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < SLOW_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t\tsleep_millisec(SLOW_DELAY_MS);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * The client sent a command followed by a (possibly very) large buffer.\n+ */\n+static int app__sendbytes_command(const char *received,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\tconst char *p = \"?\";\n+\tint len_ballast = 0;\n+\tint k;\n+\tint errs = 0;\n+\tint ret;\n+\n+\tif (skip_prefix(received, \"sendbytes \", &p))\n+\t\tlen_ballast = strlen(p);\n+\n+\t/*\n+\t * Verify that the ballast is n copies of a single letter.\n+\t * And that the multi-threaded IO layer didn't cross the streams.\n+\t */\n+\tfor (k = 1; k < len_ballast; k++)\n+\t\tif (p[k] != p[0])\n+\t\t\terrs++;\n+\n+\tif (errs)\n+\t\tstrbuf_addf(&buf_resp, \"errs:%d\\n\", errs);\n+\telse\n+\t\tstrbuf_addf(&buf_resp, \"rcvd:%c%08d\\n\", p[0], len_ballast);\n+\n+\tret = reply_cb(reply_data, buf_resp.buf, buf_resp.len);\n+\n+\tstrbuf_release(&buf_resp);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * An arbitrary fixed address to verify that the application instance\n+ * data is handled properly.\n+ */\n+static int my_app_data = 42;\n+\n+static ipc_server_application_cb test_app_cb;\n+\n+/*\n+ * This is \"application callback\" that sits on top of the \"ipc-server\".\n+ * It completely defines the set of command verbs supported by this\n+ * application.\n+ */\n+static int test_app_cb(void *application_data,\n+\t\t       const char *command,\n+\t\t       ipc_server_reply_cb *reply_cb,\n+\t\t       struct ipc_server_reply_data *reply_data)\n+{\n+\t/*\n+\t * Verify that we received the application-data that we passed\n+\t * when we started the ipc-server.  (We have several layers of\n+\t * callbacks calling callbacks and it's easy to get things mixed\n+\t * up (especially when some are \"void*\").)\n+\t */\n+\tif (application_data != (void*)&my_app_data)\n+\t\tBUG(\"application_cb: application_data pointer wrong\");\n+\n+\tif (!strcmp(command, \"quit\")) {\n+\t\t/*\n+\t\t * The client sent a \"quit\" command.  This is an async\n+\t\t * request for the server to shutdown.\n+\t\t *\n+\t\t * We DO NOT send the client a response message\n+\t\t * (because we have nothing to say and the other\n+\t\t * server threads have not yet stopped).\n+\t\t *\n+\t\t * Tell the ipc-server layer to start shutting down.\n+\t\t * This includes: stop listening for new connections\n+\t\t * on the socket/pipe and telling all worker threads\n+\t\t * to finish/drain their outgoing responses to other\n+\t\t * clients.\n+\t\t *\n+\t\t * This DOES NOT force an immediate sync shutdown.\n+\t\t */\n+\t\treturn SIMPLE_IPC_QUIT;\n+\t}\n+\n+\tif (!strcmp(command, \"ping\")) {\n+\t\tconst char *answer = \"pong\";\n+\t\treturn reply_cb(reply_data, answer, strlen(answer));\n+\t}\n+\n+\tif (!strcmp(command, \"big\"))\n+\t\treturn app__big_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"chunk\"))\n+\t\treturn app__chunk_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"slow\"))\n+\t\treturn app__slow_command(reply_cb, reply_data);\n+\n+\tif (starts_with(command, \"sendbytes \"))\n+\t\treturn app__sendbytes_command(command, reply_cb, reply_data);\n+\n+\treturn app__unhandled_command(command, reply_cb, reply_data);\n+}\n+\n+/*\n+ * This process will run as a simple-ipc server and listen for IPC commands\n+ * from client processes.\n+ */\n+static int daemon__run_server(const char *path, int argc, const char **argv)\n+{\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = 5\n+\t};\n+\n+\tconst char * const daemon_usage[] = {\n+\t\tN_(\"test-helper simple-ipc run-daemon [<options>\"),\n+\t\tNULL\n+\t};\n+\tstruct option daemon_options[] = {\n+\t\tOPT_INTEGER(0, \"threads\", &opts.nr_threads,\n+\t\t\t    N_(\"number of threads in server thread pool\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);\n+\n+\tif (opts.nr_threads < 1)\n+\t\topts.nr_threads = 1;\n+\n+\t/*\n+\t * Synchronously run the ipc-server.  We don't need any application\n+\t * instance data, so pass an arbitrary pointer (that we'll later\n+\t * verify made the round trip).\n+\t */\n+\treturn ipc_server_run(path, &opts, test_app_cb, (void*)&my_app_data);\n+}\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+/*\n+ * This is adapted from `daemonize()`.  Use `fork()` to directly create and\n+ * run the daemon in a child process.\n+ */\n+static int spawn_server(const char *path,\n+\t\t\tconst struct ipc_server_opts *opts,\n+\t\t\tpid_t *pid)\n+{\n+\t*pid = fork();\n+\n+\tswitch (*pid) {\n+\tcase 0:\n+\t\tif (setsid() == -1)\n+\t\t\terror_errno(_(\"setsid failed\"));\n+\t\tclose(0);\n+\t\tclose(1);\n+\t\tclose(2);\n+\t\tsanitize_stdfds();\n+\n+\t\treturn ipc_server_run(path, opts, test_app_cb, (void*)&my_app_data);\n+\n+\tcase -1:\n+\t\treturn error_errno(_(\"could not spawn daemon in the background\"));\n+\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+}\n+#else\n+/*\n+ * Conceptually like `daemonize()` but different because Windows does not\n+ * have `fork(2)`.  Spawn a normal Windows child process but without the\n+ * limitations of `start_command()` and `finish_command()`.\n+ */\n+static int spawn_server(const char *path,\n+\t\t\tconst struct ipc_server_opts *opts,\n+\t\t\tpid_t *pid)\n+{\n+\tchar test_tool_exe[MAX_PATH];\n+\tstruct strvec args = STRVEC_INIT;\n+\tint in, out;\n+\n+\tGetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);\n+\n+\tin = open(\"/dev/null\", O_RDONLY);\n+\tout = open(\"/dev/null\", O_WRONLY);\n+\n+\tstrvec_push(&args, test_tool_exe);\n+\tstrvec_push(&args, \"simple-ipc\");\n+\tstrvec_push(&args, \"run-daemon\");\n+\tstrvec_pushf(&args, \"--threads=%d\", opts->nr_threads);\n+\n+\t*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);\n+\tclose(in);\n+\tclose(out);\n+\n+\tstrvec_clear(&args);\n+\n+\tif (*pid < 0)\n+\t\treturn error(_(\"could not spawn daemon in the background\"));\n+\n+\treturn 0;\n+}\n+#endif\n+\n+/*\n+ * This is adapted from `wait_or_whine()`.  Watch the child process and\n+ * let it get started and begin listening for requests on the socket\n+ * before reporting our success.\n+ */\n+static int wait_for_server_startup(const char * path, pid_t pid_child,\n+\t\t\t\t   int max_wait_sec)\n+{\n+\tint status;\n+\tpid_t pid_seen;\n+\tenum ipc_active_state s;\n+\ttime_t time_limit, now;\n+\n+\ttime(&time_limit);\n+\ttime_limit += max_wait_sec;\n+\n+\tfor (;;) {\n+\t\tpid_seen = waitpid(pid_child, &status, WNOHANG);\n+\n+\t\tif (pid_seen == -1)\n+\t\t\treturn error_errno(_(\"waitpid failed\"));\n+\n+\t\telse if (pid_seen == 0) {\n+\t\t\t/*\n+\t\t\t * The child is still running (this should be\n+\t\t\t * the normal case).  Try to connect to it on\n+\t\t\t * the socket and see if it is ready for\n+\t\t\t * business.\n+\t\t\t *\n+\t\t\t * If there is another daemon already running,\n+\t\t\t * our child will fail to start (possibly\n+\t\t\t * after a timeout on the lock), but we don't\n+\t\t\t * care (who responds) if the socket is live.\n+\t\t\t */\n+\t\t\ts = ipc_get_active_state(path);\n+\t\t\tif (s == IPC_STATE__LISTENING)\n+\t\t\t\treturn 0;\n+\n+\t\t\ttime(&now);\n+\t\t\tif (now > time_limit)\n+\t\t\t\treturn error(_(\"daemon not online yet\"));\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\telse if (pid_seen == pid_child) {\n+\t\t\t/*\n+\t\t\t * The new child daemon process shutdown while\n+\t\t\t * it was starting up, so it is not listening\n+\t\t\t * on the socket.\n+\t\t\t *\n+\t\t\t * Try to ping the socket in the odd chance\n+\t\t\t * that another daemon started (or was already\n+\t\t\t * running) while our child was starting.\n+\t\t\t *\n+\t\t\t * Again, we don't care who services the socket.\n+\t\t\t */\n+\t\t\ts = ipc_get_active_state(path);\n+\t\t\tif (s == IPC_STATE__LISTENING)\n+\t\t\t\treturn 0;\n+\n+\t\t\t/*\n+\t\t\t * We don't care about the WEXITSTATUS() nor\n+\t\t\t * any of the WIF*(status) values because\n+\t\t\t * `cmd__simple_ipc()` does the `!!result`\n+\t\t\t * trick on all function return values.\n+\t\t\t *\n+\t\t\t * So it is sufficient to just report the\n+\t\t\t * early shutdown as an error.\n+\t\t\t */\n+\t\t\treturn error(_(\"daemon failed to start\"));\n+\t\t}\n+\n+\t\telse\n+\t\t\treturn error(_(\"waitpid is confused\"));\n+\t}\n+}\n+\n+/*\n+ * This process will start a simple-ipc server in a background process and\n+ * wait for it to become ready.  This is like `daemonize()` but gives us\n+ * more control and better error reporting (and makes it easier to write\n+ * unit tests).\n+ */\n+static int daemon__start_server(const char *path, int argc, const char **argv)\n+{\n+\tpid_t pid_child;\n+\tint ret;\n+\tint max_wait_sec = 60;\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = 5\n+\t};\n+\n+\tconst char * const daemon_usage[] = {\n+\t\tN_(\"test-helper simple-ipc start-daemon [<options>\"),\n+\t\tNULL\n+\t};\n+\n+\tstruct option daemon_options[] = {\n+\t\tOPT_INTEGER(0, \"max-wait\", &max_wait_sec,\n+\t\t\t    N_(\"seconds to wait for daemon to startup\")),\n+\t\tOPT_INTEGER(0, \"threads\", &opts.nr_threads,\n+\t\t\t    N_(\"number of threads in server thread pool\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);\n+\n+\tif (max_wait_sec < 0)\n+\t\tmax_wait_sec = 0;\n+\tif (opts.nr_threads < 1)\n+\t\topts.nr_threads = 1;\n+\n+\t/*\n+\t * Run the actual daemon in a background process.\n+\t */\n+\tret = spawn_server(path, &opts, &pid_child);\n+\tif (pid_child <= 0)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Let the parent wait for the child process to get started\n+\t * and begin listening for requests on the socket.\n+\t */\n+\tret = wait_for_server_startup(path, pid_child, max_wait_sec);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * This process will run a quick probe to see if a simple-ipc server\n+ * is active on this path.\n+ *\n+ * Returns 0 if the server is alive.\n+ */\n+static int client__probe_server(const char *path)\n+{\n+\tenum ipc_active_state s;\n+\n+\ts = ipc_get_active_state(path);\n+\tswitch (s) {\n+\tcase IPC_STATE__LISTENING:\n+\t\treturn 0;\n+\n+\tcase IPC_STATE__NOT_LISTENING:\n+\t\treturn error(\"no server listening at '%s'\", path);\n+\n+\tcase IPC_STATE__PATH_NOT_FOUND:\n+\t\treturn error(\"path not found '%s'\", path);\n+\n+\tcase IPC_STATE__INVALID_PATH:\n+\t\treturn error(\"invalid pipe/socket name '%s'\", path);\n+\n+\tcase IPC_STATE__OTHER_ERROR:\n+\tdefault:\n+\t\treturn error(\"other error for '%s'\", path);\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command to an already-running server daemon and print the\n+ * response.\n+ *\n+ * argv[2] contains a simple (1 word) command verb that `test_app_cb()`\n+ * (in the daemon process) will understand.\n+ */\n+static int client__send_ipc(int argc, const char **argv, const char *path)\n+{\n+\tconst char *command = argc > 2 ? argv[2] : \"(no command)\";\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (!ipc_client_send_command(path, &options, command, &buf)) {\n+\t\tif (buf.len) {\n+\t\t\tprintf(\"%s\\n\", buf.buf);\n+\t\t\tfflush(stdout);\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"failed to send '%s' to '%s'\", command, path);\n+}\n+\n+/*\n+ * Send an IPC command followed by ballast to confirm that a large\n+ * message can be sent and that the kernel or pkt-line layers will\n+ * properly chunk it and that the daemon receives the entire message.\n+ */\n+static int do_sendbytes(int bytecount, char byte, const char *path,\n+\t\t\tconst struct ipc_client_connect_options *options)\n+{\n+\tstruct strbuf buf_send = STRBUF_INIT;\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_send, \"sendbytes \");\n+\tstrbuf_addchars(&buf_send, byte, bytecount);\n+\n+\tif (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {\n+\t\tstrbuf_rtrim(&buf_resp);\n+\t\tprintf(\"sent:%c%08d %s\\n\", byte, bytecount, buf_resp.buf);\n+\t\tfflush(stdout);\n+\t\tstrbuf_release(&buf_send);\n+\t\tstrbuf_release(&buf_resp);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"client failed to sendbytes(%d, '%c') to '%s'\",\n+\t\t     bytecount, byte, path);\n+}\n+\n+/*\n+ * Send an IPC command with ballast to an already-running server daemon.\n+ */\n+static int client__sendbytes(int argc, const char **argv, const char *path)\n+{\n+\tint bytecount = 1024;\n+\tchar *string = \"x\";\n+\tconst char * const sendbytes_usage[] = {\n+\t\tN_(\"test-helper simple-ipc sendbytes [<options>]\"),\n+\t\tNULL\n+\t};\n+\tstruct option sendbytes_options[] = {\n+\t\tOPT_INTEGER(0, \"bytecount\", &bytecount, N_(\"number of bytes\")),\n+\t\tOPT_STRING(0, \"byte\", &string, N_(\"byte\"), N_(\"ballast\")),\n+\t\tOPT_END()\n+\t};\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\toptions.uds_disallow_chdir = 0;\n+\n+\targc = parse_options(argc, argv, NULL, sendbytes_options, sendbytes_usage, 0);\n+\n+\treturn do_sendbytes(bytecount, string[0], path, &options);\n+}\n+\n+struct multiple_thread_data {\n+\tpthread_t pthread_id;\n+\tstruct multiple_thread_data *next;\n+\tconst char *path;\n+\tint bytecount;\n+\tint batchsize;\n+\tint sum_errors;\n+\tint sum_good;\n+\tchar letter;\n+};\n+\n+static void *multiple_thread_proc(void *_multiple_thread_data)\n+{\n+\tstruct multiple_thread_data *d = _multiple_thread_data;\n+\tint k;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\t/*\n+\t * A multi-threaded client should not be randomly calling chdir().\n+\t * The test will pass without this restriction because the test is\n+\t * not otherwise accessing the filesystem, but it makes us honest.\n+\t */\n+\toptions.uds_disallow_chdir = 1;\n+\n+\ttrace2_thread_start(\"multiple\");\n+\n+\tfor (k = 0; k < d->batchsize; k++) {\n+\t\tif (do_sendbytes(d->bytecount + k, d->letter, d->path, &options))\n+\t\t\td->sum_errors++;\n+\t\telse\n+\t\t\td->sum_good++;\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * Start a client-side thread pool.  Each thread sends a series of\n+ * IPC requests.  Each request is on a new connection to the server.\n+ */\n+static int client__multiple(int argc, const char **argv, const char *path)\n+{\n+\tstruct multiple_thread_data *list = NULL;\n+\tint k;\n+\tint nr_threads = 5;\n+\tint bytecount = 1;\n+\tint batchsize = 10;\n+\tint sum_join_errors = 0;\n+\tint sum_thread_errors = 0;\n+\tint sum_good = 0;\n+\n+\tconst char * const multiple_usage[] = {\n+\t\tN_(\"test-helper simple-ipc multiple [<options>]\"),\n+\t\tNULL\n+\t};\n+\tstruct option multiple_options[] = {\n+\t\tOPT_INTEGER(0, \"bytecount\", &bytecount, N_(\"number of bytes\")),\n+\t\tOPT_INTEGER(0, \"threads\", &nr_threads, N_(\"number of threads\")),\n+\t\tOPT_INTEGER(0, \"batchsize\", &batchsize, N_(\"number of requests per thread\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, multiple_options, multiple_usage, 0);\n+\n+\tif (bytecount < 1)\n+\t\tbytecount = 1;\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\tif (batchsize < 1)\n+\t\tbatchsize = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct multiple_thread_data *d = xcalloc(1, sizeof(*d));\n+\t\td->next = list;\n+\t\td->path = path;\n+\t\td->bytecount = bytecount + batchsize*(k/26);\n+\t\td->batchsize = batchsize;\n+\t\td->sum_errors = 0;\n+\t\td->sum_good = 0;\n+\t\td->letter = 'A' + (k % 26);\n+\n+\t\tif (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) {\n+\t\t\twarning(\"failed to create thread[%d] skipping remainder\", k);\n+\t\t\tfree(d);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tlist = d;\n+\t}\n+\n+\twhile (list) {\n+\t\tstruct multiple_thread_data *d = list;\n+\n+\t\tif (pthread_join(d->pthread_id, NULL))\n+\t\t\tsum_join_errors++;\n+\n+\t\tsum_thread_errors += d->sum_errors;\n+\t\tsum_good += d->sum_good;\n+\n+\t\tlist = d->next;\n+\t\tfree(d);\n+\t}\n+\n+\tprintf(\"client (good %d) (join %d), (errors %d)\\n\",\n+\t       sum_good, sum_join_errors, sum_thread_errors);\n+\n+\treturn (sum_join_errors + sum_thread_errors) ? 1 : 0;\n+}\n+\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tconst char *path = \"ipc-test\";\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"SUPPORTS_SIMPLE_IPC\"))\n+\t\treturn 0;\n+\n+\t/*\n+\t * Use '!!' on all dispatch functions to map from `error()` style\n+\t * (returns -1) style to `test_must_fail` style (expects 1).  This\n+\t * makes shell error messages less confusing.\n+\t */\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"is-active\"))\n+\t\treturn !!client__probe_server(path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"run-daemon\"))\n+\t\treturn !!daemon__run_server(path, argc, argv);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"start-daemon\"))\n+\t\treturn !!daemon__start_server(path, argc, argv);\n+\n+\t/*\n+\t * Client commands follow.  Ensure a server is running before\n+\t * going any further.\n+\t */\n+\tif (client__probe_server(path))\n+\t\treturn 1;\n+\n+\tif ((argc == 2 || argc == 3) && !strcmp(argv[1], \"send\"))\n+\t\treturn !!client__send_ipc(argc, argv, path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"sendbytes\"))\n+\t\treturn !!client__sendbytes(argc, argv, path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"multiple\"))\n+\t\treturn !!client__multiple(argc, argv, path);\n+\n+\tdie(\"Unhandled argv[1]: '%s'\", argv[1]);\n+}\n+#endif\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 9d6d14d92937..a409655f03b5 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -64,6 +64,7 @@ static struct test_cmd cmds[] = {\n \t{ \"sha1\", cmd__sha1 },\n \t{ \"sha256\", cmd__sha256 },\n \t{ \"sigchain\", cmd__sigchain },\n+\t{ \"simple-ipc\", cmd__simple_ipc },\n \t{ \"strcmp-offset\", cmd__strcmp_offset },\n \t{ \"string-list\", cmd__string_list },\n \t{ \"submodule-config\", cmd__submodule_config },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex a6470ff62c42..564eb3c8e911 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -54,6 +54,7 @@ int cmd__sha1(int argc, const char **argv);\n int cmd__oid_array(int argc, const char **argv);\n int cmd__sha256(int argc, const char **argv);\n int cmd__sigchain(int argc, const char **argv);\n+int cmd__simple_ipc(int argc, const char **argv);\n int cmd__strcmp_offset(int argc, const char **argv);\n int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\ndiff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\nnew file mode 100755\nindex 000000000000..e36b786709ec\n--- /dev/null\n+++ b/t/t0052-simple-ipc.sh\n@@ -0,0 +1,134 @@\n+#!/bin/sh\n+\n+test_description='simple command server'\n+\n+. ./test-lib.sh\n+\n+test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {\n+\tskip_all='simple IPC not supported on this platform'\n+\ttest_done\n+}\n+\n+stop_simple_IPC_server () {\n+\ttest-tool simple-ipc send quit\n+}\n+\n+test_expect_success 'start simple command server' '\n+\ttest_atexit stop_simple_IPC_server &&\n+\ttest-tool simple-ipc start-daemon --threads=8 &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'simple command server' '\n+\ttest-tool simple-ipc send ping >actual &&\n+\techo pong >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'servers cannot share the same path' '\n+\ttest_must_fail test-tool simple-ipc run-daemon &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'big response' '\n+\ttest-tool simple-ipc send big >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'chunk response' '\n+\ttest-tool simple-ipc send chunk >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'slow response' '\n+\ttest-tool simple-ipc send slow >actual &&\n+\ttest_line_count -ge 100 actual &&\n+\tgrep -q \"big: [0]*99\\$\" actual\n+'\n+\n+# Send an IPC with n=100,000 bytes of ballast.  This should be large enough\n+# to force both the kernel and the pkt-line layer to chunk the message to the\n+# daemon and for the daemon to receive it in chunks.\n+#\n+test_expect_success 'sendbytes' '\n+\ttest-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual &&\n+\tgrep \"sent:A00100000 rcvd:A00100000\" actual\n+'\n+\n+# Start a series of <threads> client threads that each make <batchsize>\n+# IPC requests to the server.  Each (<threads> * <batchsize>) request\n+# will open a new connection to the server and randomly bind to a server\n+# thread.  Each client thread exits after completing its batch.  So the\n+# total number of live client threads will be smaller than the total.\n+# Each request will send a message containing at least <bytecount> bytes\n+# of ballast.  (Responses are small.)\n+#\n+# The purpose here is to test threading in the server and responding to\n+# many concurrent client requests (regardless of whether they come from\n+# 1 client process or many).  And to test that the server side of the\n+# named pipe/socket is stable.  (On Windows this means that the server\n+# pipe is properly recycled.)\n+#\n+# On Windows it also lets us adjust the connection timeout in the\n+# `ipc_client_send_command()`.\n+#\n+# Note it is easy to drive the system into failure by requesting an\n+# insane number of threads on client or server and/or increasing the\n+# per-thread batchsize or the per-request bytecount (ballast).\n+# On Windows these failures look like \"pipe is busy\" errors.\n+# So I've chosen fairly conservative values for now.\n+#\n+# We expect output of the form \"sent:<letter><length> ...\"\n+# With terms (7, 19, 13) we expect:\n+#   <letter> in [A-G]\n+#   <length> in [19+0 .. 19+(13-1)]\n+# and (7 * 13) successful responses.\n+#\n+test_expect_success 'stress test threads' '\n+\ttest-tool simple-ipc multiple \\\n+\t\t--threads=7 \\\n+\t\t--bytecount=19 \\\n+\t\t--batchsize=13 \\\n+\t\t>actual &&\n+\ttest_line_count = 92 actual &&\n+\tgrep \"good 91\" actual &&\n+\tgrep \"sent:A\" <actual >actual_a &&\n+\tcat >expect_a <<-EOF &&\n+\t\tsent:A00000019 rcvd:A00000019\n+\t\tsent:A00000020 rcvd:A00000020\n+\t\tsent:A00000021 rcvd:A00000021\n+\t\tsent:A00000022 rcvd:A00000022\n+\t\tsent:A00000023 rcvd:A00000023\n+\t\tsent:A00000024 rcvd:A00000024\n+\t\tsent:A00000025 rcvd:A00000025\n+\t\tsent:A00000026 rcvd:A00000026\n+\t\tsent:A00000027 rcvd:A00000027\n+\t\tsent:A00000028 rcvd:A00000028\n+\t\tsent:A00000029 rcvd:A00000029\n+\t\tsent:A00000030 rcvd:A00000030\n+\t\tsent:A00000031 rcvd:A00000031\n+\tEOF\n+\ttest_cmp expect_a actual_a\n+'\n+\n+# Sending a \"quit\" message to the server causes it to start an \"async\n+# shutdown\" -- queuing shutdown events to all socket/pipe thread-pool\n+# threads.  Each thread will process that event after finishing\n+# (draining) any in-progress IO with other clients.  So when the \"send\n+# quit\" client command exits, the ipc-server may still be running (but\n+# it should be cleaning up).\n+#\n+# So, insert a generous sleep here to give the server time to shutdown.\n+#\n+test_expect_success '`quit` works' '\n+\ttest-tool simple-ipc send quit &&\n+\n+\tsleep 5 &&\n+\n+\ttest_must_fail test-tool simple-ipc is-active &&\n+\ttest_must_fail test-tool simple-ipc send ping\n+'\n+\n+test_done\n-- \ngitgitgadget\n"},{"id":"416819","messageId":"43c8db9a4468c0ca50e8f4efa55ab01a77cafcf6.1613174954.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v3 11/12] simple-ipc: add Unix domain socket implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-13T00:09:12Z","receivedAt":"2021-02-13T00:11:46Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Unix domain socket based implementation of \"simple-ipc\".\n\nA set of `ipc_client` routines implement a client library to connect\nto an `ipc_server` over a Unix domain socket, send a simple request,\nand receive a single response.  Clients use blocking IO on the socket.\n\nA set of `ipc_server` routines implement a thread pool to listen for\nand concurrently service client connections.\n\nThe server creates a new Unix domain socket at a known location.  If a\nsocket already exists with that name, the server tries to determine if\nanother server is already listening on the socket or if the socket is\ndead.  If socket is busy, the server exits with an error rather than\nstealing the socket.  If the socket is dead, the server creates a new\none and starts up.\n\nIf while running, the server detects that its socket has been stolen\nby another server, it automatically exits.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   2 +\n compat/simple-ipc/ipc-unix-socket.c | 979 ++++++++++++++++++++++++++++\n contrib/buildsystems/CMakeLists.txt |   2 +\n simple-ipc.h                        |  13 +-\n 4 files changed, 995 insertions(+), 1 deletion(-)\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n\ndiff --git a/Makefile b/Makefile\nindex 40d5cab78d3f..08a4c88b92f5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1677,6 +1677,8 @@ ifdef NO_UNIX_SOCKETS\n \tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n else\n \tLIB_OBJS += unix-socket.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-unix-socket.o\n endif\n \n ifdef USE_WIN32_IPC\ndiff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c\nnew file mode 100644\nindex 000000000000..b7fd0b34329e\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-unix-socket.c\n@@ -0,0 +1,979 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+#include \"unix-socket.h\"\n+\n+#ifdef NO_UNIX_SOCKETS\n+#error compat/simple-ipc/ipc-unix-socket.c requires Unix sockets\n+#endif\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\tstruct stat st;\n+\tstruct ipc_client_connection *connection_test = NULL;\n+\n+\toptions.wait_if_busy = 0;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (lstat(path, &st) == -1) {\n+\t\tswitch (errno) {\n+\t\tcase ENOENT:\n+\t\tcase ENOTDIR:\n+\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__INVALID_PATH;\n+\t\t}\n+\t}\n+\n+\t/* also complain if a plain file is in the way */\n+\tif ((st.st_mode & S_IFMT) != S_IFSOCK)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\t/*\n+\t * Just because the filesystem has a S_IFSOCK type inode\n+\t * at `path`, doesn't mean it that there is a server listening.\n+\t * Ping it to be sure.\n+\t */\n+\tstate = ipc_client_try_connect(path, &options, &connection_test);\n+\tipc_client_close_connection(connection_test);\n+\n+\treturn state;\n+}\n+\n+/*\n+ * This value was chosen at random.\n+ */\n+#define WAIT_STEP_MS (50)\n+\n+/*\n+ * Try to connect to the server.  If the server is just starting up or\n+ * is very busy, we may not get a connection the first time.\n+ */\n+static enum ipc_active_state connect_to_server(\n+\tconst char *path,\n+\tint timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tint wait_ms = 50;\n+\tint k;\n+\n+\t*pfd = -1;\n+\n+\tfor (k = 0; k < timeout_ms; k += wait_ms) {\n+\t\tint fd = unix_stream_connect(path, options->uds_disallow_chdir);\n+\n+\t\tif (fd != -1) {\n+\t\t\t*pfd = fd;\n+\t\t\treturn IPC_STATE__LISTENING;\n+\t\t}\n+\n+\t\tif (errno == ENOENT) {\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ETIMEDOUT) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ECONNREFUSED) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\n+\tsleep_and_try_again:\n+\t\tsleep_millisec(wait_ms);\n+\t}\n+\n+\treturn IPC_STATE__NOT_LISTENING;\n+}\n+\n+/*\n+ * A randomly chosen timeout value.\n+ */\n+#define MY_CONNECTION_TIMEOUT_MS (1000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tstate = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n+\t\t\t\t\t       connection->fd) < 0 ||\n+\t    packet_flush_gently(connection->fd) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tif (read_packetized_to_strbuf(\n+\t\t    connection->fd, answer,\n+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, answer);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+static int set_socket_blocking_flag(int fd, int make_nonblocking)\n+{\n+\tint flags;\n+\n+\tflags = fcntl(fd, F_GETFL, NULL);\n+\n+\tif (flags < 0)\n+\t\treturn -1;\n+\n+\tif (make_nonblocking)\n+\t\tflags |= O_NONBLOCK;\n+\telse\n+\t\tflags &= ~O_NONBLOCK;\n+\n+\treturn fcntl(fd, F_SETFL, flags);\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_WORKER_THREAD_DATA,\n+\tMAGIC_ACCEPT_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_worker_thread_data *worker_thread_data;\n+};\n+\n+struct ipc_worker_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_worker_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+};\n+\n+struct ipc_accept_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_data *server_data;\n+\n+\tstruct unix_stream_server_socket *server_socket;\n+\n+\tint fd_send_shutdown;\n+\tint fd_wait_shutdown;\n+\tpthread_t pthread_id;\n+};\n+\n+/*\n+ * With unix-sockets, the conceptual \"ipc-server\" is implemented as a single\n+ * controller \"accept-thread\" thread and a pool of \"worker-thread\" threads.\n+ * The former does the usual `accept()` loop and dispatches connections\n+ * to an idle worker thread.  The worker threads wait in an idle loop for\n+ * a new connection, communicate with the client and relay data to/from\n+ * the `application_cb` and then wait for another connection from the\n+ * server thread.  This avoids the overhead of constantly creating and\n+ * destroying threads.\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\n+\tstruct ipc_accept_thread_data *accept_thread;\n+\tstruct ipc_worker_thread_data *worker_thread_list;\n+\n+\tpthread_mutex_t work_available_mutex;\n+\tpthread_cond_t work_available_cond;\n+\n+\t/*\n+\t * Accepted but not yet processed client connections are kept\n+\t * in a circular buffer FIFO.  The queue is empty when the\n+\t * positions are equal.\n+\t */\n+\tint *fifo_fds;\n+\tint queue_size;\n+\tint back_pos;\n+\tint front_pos;\n+\n+\tint shutdown_requested;\n+\tint is_stopped;\n+};\n+\n+/*\n+ * Remove and return the oldest queued connection.\n+ *\n+ * Returns -1 if empty.\n+ */\n+static int fifo_dequeue(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint fd;\n+\n+\tif (server_data->back_pos == server_data->front_pos)\n+\t\treturn -1;\n+\n+\tfd = server_data->fifo_fds[server_data->front_pos];\n+\tserver_data->fifo_fds[server_data->front_pos] = -1;\n+\n+\tserver_data->front_pos++;\n+\tif (server_data->front_pos == server_data->queue_size)\n+\t\tserver_data->front_pos = 0;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Push a new fd onto the back of the queue.\n+ *\n+ * Drop it and return -1 if queue is already full.\n+ */\n+static int fifo_enqueue(struct ipc_server_data *server_data, int fd)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint next_back_pos;\n+\n+\tnext_back_pos = server_data->back_pos + 1;\n+\tif (next_back_pos == server_data->queue_size)\n+\t\tnext_back_pos = 0;\n+\n+\tif (next_back_pos == server_data->front_pos) {\n+\t\t/* Queue is full. Just drop it. */\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data->fifo_fds[server_data->back_pos] = fd;\n+\tserver_data->back_pos = next_back_pos;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Wait for a connection to be queued to the FIFO and return it.\n+ *\n+ * Returns -1 if someone has already requested a shutdown.\n+ */\n+static int worker_thread__wait_for_connection(\n+\tstruct ipc_worker_thread_data *worker_thread_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tint fd = -1;\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\tfor (;;) {\n+\t\tif (server_data->shutdown_requested)\n+\t\t\tbreak;\n+\n+\t\tfd = fifo_dequeue(server_data);\n+\t\tif (fd >= 0)\n+\t\t\tbreak;\n+\n+\t\tpthread_cond_wait(&server_data->work_available_cond,\n+\t\t\t\t  &server_data->work_available_mutex);\n+\t}\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf_no_flush(response, response_len,\n+\t\t\t\t\t\t  reply_data->fd);\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_WAIT_POLL_TIMEOUT_MS (10)\n+\n+/*\n+ * If the client hangs up without sending any data on the wire, just\n+ * quietly close the socket and ignore this client.\n+ *\n+ * This worker thread is committed to reading the IPC request data\n+ * from the client at the other end of this fd.  Wait here for the\n+ * client to actually put something on the wire -- because if the\n+ * client just does a ping (connect and hangup without sending any\n+ * data), our use of the pkt-line read routines will spew an error\n+ * message.\n+ *\n+ * Return -1 if the client hung up.\n+ * Return 0 if data (possibly incomplete) is ready.\n+ */\n+static int worker_thread__wait_for_io_start(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tstruct pollfd pollfd[1];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = fd;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\tint in_shutdown;\n+\n+\t\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\t\tin_shutdown = server_data->shutdown_requested;\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\t\t\t/*\n+\t\t\t * If a shutdown is already in progress and this\n+\t\t\t * client has not started talking yet, just drop it.\n+\t\t\t */\n+\t\t\tif (in_shutdown)\n+\t\t\t\tgoto cleanup;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLHUP)\n+\t\t\tgoto cleanup;\n+\n+\t\tif (pollfd[0].revents & POLLIN)\n+\t\t\treturn 0;\n+\n+\t\tgoto cleanup;\n+\t}\n+\n+cleanup:\n+\tclose(fd);\n+\treturn -1;\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ */\n+static int worker_thread__do_io(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\t/* ASSERT NOT holding lock */\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.worker_thread_data = worker_thread_data;\n+\n+\treply_data.fd = fd;\n+\n+\tret = read_packetized_to_strbuf(\n+\t\treply_data.fd, &buf,\n+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);\n+\tif (ret >= 0) {\n+\t\tret = worker_thread_data->server_data->application_cb(\n+\t\t\tworker_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Block SIGPIPE on the current thread (so that we get EPIPE from\n+ * write() rather than an actual signal).\n+ *\n+ * Note that using sigchain_push() and _pop() to control SIGPIPE\n+ * around our IO calls is not thread safe:\n+ * [] It uses a global stack of handler frames.\n+ * [] It uses ALLOC_GROW() to resize it.\n+ * [] Finally, according to the `signal(2)` man-page:\n+ *    \"The effects of `signal()` in a multithreaded process are unspecified.\"\n+ */\n+static void thread_block_sigpipe(sigset_t *old_set)\n+{\n+\tsigset_t new_set;\n+\n+\tsigemptyset(&new_set);\n+\tsigaddset(&new_set, SIGPIPE);\n+\n+\tsigemptyset(old_set);\n+\tpthread_sigmask(SIG_BLOCK, &new_set, old_set);\n+}\n+\n+/*\n+ * Thread proc for an IPC worker thread.  It handles a series of\n+ * connections from clients.  It pulls the next fd from the queue\n+ * processes it, and then waits for the next client.\n+ *\n+ * Block SIGPIPE in this worker thread for the life of the thread.\n+ * This avoids stray (and sometimes delayed) SIGPIPE signals caused\n+ * by client errors and/or when we are under extremely heavy IO load.\n+ *\n+ * This means that the application callback will have SIGPIPE blocked.\n+ * The callback should not change it.\n+ */\n+static void *worker_thread_proc(void *_worker_thread_data)\n+{\n+\tstruct ipc_worker_thread_data *worker_thread_data = _worker_thread_data;\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tsigset_t old_set;\n+\tint fd, io;\n+\tint ret;\n+\n+\ttrace2_thread_start(\"ipc-worker\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tfd = worker_thread__wait_for_connection(worker_thread_data);\n+\t\tif (fd == -1)\n+\t\t\tbreak; /* in shutdown */\n+\n+\t\tio = worker_thread__wait_for_io_start(worker_thread_data, fd);\n+\t\tif (io == -1)\n+\t\t\tcontinue; /* client hung up without sending anything */\n+\n+\t\tret = worker_thread__do_io(worker_thread_data, fd);\n+\n+\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\ttrace2_data_string(\"ipc-worker\", NULL, \"queue_stop_async\",\n+\t\t\t\t\t   \"application_quit\");\n+\t\t\t/*\n+\t\t\t * The application layer is telling the ipc-server\n+\t\t\t * layer to shutdown.\n+\t\t\t *\n+\t\t\t * We DO NOT have a response to send to the client.\n+\t\t\t *\n+\t\t\t * Queue an async stop (to stop the other threads) and\n+\t\t\t * allow this worker thread to exit now (no sense waiting\n+\t\t\t * for the thread-pool shutdown signal).\n+\t\t\t *\n+\t\t\t * Other non-idle worker threads are allowed to finish\n+\t\t\t * responding to their current clients.\n+\t\t\t */\n+\t\t\tipc_server_stop_async(server_data);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)\n+\n+/*\n+ * Accept a new client connection on our socket.  This uses non-blocking\n+ * IO so that we can also wait for shutdown requests on our socket-pair\n+ * without actually spinning on a fast timeout.\n+ */\n+static int accept_thread__wait_for_connection(\n+\tstruct ipc_accept_thread_data *accept_thread_data)\n+{\n+\tstruct pollfd pollfd[2];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = accept_thread_data->fd_wait_shutdown;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tpollfd[1].fd = accept_thread_data->server_socket->fd_socket;\n+\t\tpollfd[1].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\treturn result;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\t/*\n+\t\t\t * If someone deletes or force-creates a new unix\n+\t\t\t * domain socket at our path, all future clients\n+\t\t\t * will be routed elsewhere and we silently starve.\n+\t\t\t * If that happens, just queue a shutdown.\n+\t\t\t */\n+\t\t\tif (unix_stream_server__was_stolen(\n+\t\t\t\t    accept_thread_data->server_socket)) {\n+\t\t\t\ttrace2_data_string(\"ipc-accept\", NULL,\n+\t\t\t\t\t\t   \"queue_stop_async\",\n+\t\t\t\t\t\t   \"socket_stolen\");\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\taccept_thread_data->server_data);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLIN) {\n+\t\t\t/* shutdown message queued to socketpair */\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (pollfd[1].revents & POLLIN) {\n+\t\t\t/* a connection is available on server_socket */\n+\n+\t\t\tint client_fd =\n+\t\t\t\taccept(accept_thread_data->server_socket->fd_socket,\n+\t\t\t\t       NULL, NULL);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\treturn client_fd;\n+\n+\t\t\t/*\n+\t\t\t * An error here is unlikely -- it probably\n+\t\t\t * indicates that the connecting process has\n+\t\t\t * already dropped the connection.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tBUG(\"unandled poll result errno=%d r[0]=%d r[1]=%d\",\n+\t\t    errno, pollfd[0].revents, pollfd[1].revents);\n+\t}\n+}\n+\n+/*\n+ * Thread proc for the IPC server \"accept thread\".  This waits for\n+ * an incoming socket connection, appends it to the queue of available\n+ * connections, and notifies a worker thread to process it.\n+ *\n+ * Block SIGPIPE in this thread for the life of the thread.  This\n+ * avoids any stray SIGPIPE signals when closing pipe fds under\n+ * extremely heavy loads (such as when the fifo queue is full and we\n+ * drop incomming connections).\n+ */\n+static void *accept_thread_proc(void *_accept_thread_data)\n+{\n+\tstruct ipc_accept_thread_data *accept_thread_data = _accept_thread_data;\n+\tstruct ipc_server_data *server_data = accept_thread_data->server_data;\n+\tsigset_t old_set;\n+\n+\ttrace2_thread_start(\"ipc-accept\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tint client_fd = accept_thread__wait_for_connection(\n+\t\t\taccept_thread_data);\n+\n+\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\tif (server_data->shutdown_requested) {\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\tclose(client_fd);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (client_fd < 0) {\n+\t\t\t/* ignore transient accept() errors */\n+\t\t}\n+\t\telse {\n+\t\t\tfifo_enqueue(server_data, client_fd);\n+\t\t\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\t\t}\n+\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * We can't predict the connection arrival rate relative to the worker\n+ * processing rate, therefore we allow the \"accept-thread\" to queue up\n+ * a generous number of connections, since we'd rather have the client\n+ * not unnecessarily timeout if we can avoid it.  (The assumption is\n+ * that this will be used for FSMonitor and a few second wait on a\n+ * connection is better than having the client timeout and do the full\n+ * computation itself.)\n+ *\n+ * The FIFO queue size is set to a multiple of the worker pool size.\n+ * This value chosen at random.\n+ */\n+#define FIFO_SCALE (100)\n+\n+/*\n+ * The backlog value for `listen(2)`.  This doesn't need to huge,\n+ * rather just large enough for our \"accept-thread\" to wake up and\n+ * queue incoming connections onto the FIFO without the kernel\n+ * dropping any.\n+ *\n+ * This value chosen at random.\n+ */\n+#define LISTEN_BACKLOG (50)\n+\n+static struct unix_stream_server_socket *create_listener_socket(\n+\tconst char *path,\n+\tconst struct ipc_server_opts *ipc_opts)\n+{\n+\tstruct unix_stream_server_socket *server_socket = NULL;\n+\tstruct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n+\n+\tuslg_opts.listen_backlog_size = LISTEN_BACKLOG;\n+\tuslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;\n+\n+\tserver_socket = unix_stream_server__listen_with_lock(path, &uslg_opts);\n+\tif (!server_socket)\n+\t\treturn NULL;\n+\n+\tif (set_socket_blocking_flag(server_socket->fd_socket, 1)) {\n+\t\tint saved_errno = errno;\n+\t\terror_errno(_(\"could not set listener socket nonblocking '%s'\"),\n+\t\t\t    path);\n+\t\tunix_stream_server__free(server_socket);\n+\t\terrno = saved_errno;\n+\t\treturn NULL;\n+\t}\n+\n+\ttrace2_data_string(\"ipc-server\", NULL, \"listen-with-lock\", path);\n+\treturn server_socket;\n+}\n+\n+static struct unix_stream_server_socket *setup_listener_socket(\n+\tconst char *path,\n+\tconst struct ipc_server_opts *ipc_opts)\n+{\n+\tstruct unix_stream_server_socket *server_socket;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n+\tserver_socket = create_listener_socket(path, ipc_opts);\n+\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n+\n+\treturn server_socket;\n+}\n+\n+/*\n+ * Start IPC server in a pool of background threads.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct unix_stream_server_socket *server_socket = NULL;\n+\tstruct ipc_server_data *server_data;\n+\tint sv[2];\n+\tint k;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\t/*\n+\t * Create a socketpair and set sv[1] to non-blocking.  This\n+\t * will used to send a shutdown message to the accept-thread\n+\t * and allows the accept-thread to wait on EITHER a client\n+\t * connection or a shutdown request without spinning.\n+\t */\n+\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)\n+\t\treturn error_errno(_(\"could not create socketpair for '%s'\"),\n+\t\t\t\t   path);\n+\n+\tif (set_socket_blocking_flag(sv[1], 1)) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn error_errno(_(\"making socketpair nonblocking '%s'\"),\n+\t\t\t\t   path);\n+\t}\n+\n+\tserver_socket = setup_listener_socket(path, opts);\n+\tif (!server_socket) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tpthread_mutex_init(&server_data->work_available_mutex, NULL);\n+\tpthread_cond_init(&server_data->work_available_cond, NULL);\n+\n+\tserver_data->queue_size = nr_threads * FIFO_SCALE;\n+\tserver_data->fifo_fds = xcalloc(server_data->queue_size,\n+\t\t\t\t\tsizeof(*server_data->fifo_fds));\n+\n+\tserver_data->accept_thread =\n+\t\txcalloc(1, sizeof(*server_data->accept_thread));\n+\tserver_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;\n+\tserver_data->accept_thread->server_data = server_data;\n+\tserver_data->accept_thread->server_socket = server_socket;\n+\tserver_data->accept_thread->fd_send_shutdown = sv[0];\n+\tserver_data->accept_thread->fd_wait_shutdown = sv[1];\n+\n+\tif (pthread_create(&server_data->accept_thread->pthread_id, NULL,\n+\t\t\t   accept_thread_proc, server_data->accept_thread))\n+\t\tdie_errno(_(\"could not start accept_thread '%s'\"), path);\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_worker_thread_data *wtd;\n+\n+\t\twtd = xcalloc(1, sizeof(*wtd));\n+\t\twtd->magic = MAGIC_WORKER_THREAD_DATA;\n+\t\twtd->server_data = server_data;\n+\n+\t\tif (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc,\n+\t\t\t\t   wtd)) {\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start worker[0] for '%s'\"),\n+\t\t\t\t    path);\n+\t\t\t/*\n+\t\t\t * Limp along with the thread pool that we have.\n+\t\t\t */\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\twtd->next_thread = server_data->worker_thread_list;\n+\t\tserver_data->worker_thread_list = wtd;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+/*\n+ * Gently tell the IPC server treads to shutdown.\n+ * Can be run on any thread.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tint fd;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\n+\tserver_data->shutdown_requested = 1;\n+\n+\t/*\n+\t * Write a byte to the shutdown socket pair to wake up the\n+\t * accept-thread.\n+\t */\n+\tif (write(server_data->accept_thread->fd_send_shutdown, \"Q\", 1) < 0)\n+\t\terror_errno(\"could not write to fd_send_shutdown\");\n+\n+\t/*\n+\t * Drain the queue of existing connections.\n+\t */\n+\twhile ((fd = fifo_dequeue(server_data)) != -1)\n+\t\tclose(fd);\n+\n+\t/*\n+\t * Gently tell worker threads to stop processing new connections\n+\t * and exit.  (This does not abort in-process conversations.)\n+\t */\n+\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\ttrace2_region_leave(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Wait for all IPC server threads to stop.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tpthread_join(server_data->accept_thread->pthread_id, NULL);\n+\n+\tif (!server_data->shutdown_requested)\n+\t\tBUG(\"ipc-server: accept-thread stopped for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tpthread_join(wtd->pthread_id, NULL);\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tstruct ipc_accept_thread_data * accept_thread_data;\n+\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\taccept_thread_data = server_data->accept_thread;\n+\tif (accept_thread_data) {\n+\t\tunix_stream_server__free(accept_thread_data->server_socket);\n+\n+\t\tif (accept_thread_data->fd_send_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_send_shutdown);\n+\t\tif (accept_thread_data->fd_wait_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_wait_shutdown);\n+\n+\t\tfree(server_data->accept_thread);\n+\t}\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tpthread_cond_destroy(&server_data->work_available_cond);\n+\tpthread_mutex_destroy(&server_data->work_available_mutex);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tfree(server_data->fifo_fds);\n+\tfree(server_data);\n+}\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 4bd41054ee70..4c27a373414a 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -248,6 +248,8 @@ endif()\n \n if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+else()\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c)\n endif()\n \n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\ndiff --git a/simple-ipc.h b/simple-ipc.h\nindex a3f96b42cca2..f7e72e966f9a 100644\n--- a/simple-ipc.h\n+++ b/simple-ipc.h\n@@ -5,7 +5,7 @@\n  * See Documentation/technical/api-simple-ipc.txt\n  */\n \n-#if defined(GIT_WINDOWS_NATIVE)\n+#if defined(GIT_WINDOWS_NATIVE) || !defined(NO_UNIX_SOCKETS)\n #define SUPPORTS_SIMPLE_IPC\n #endif\n \n@@ -62,11 +62,17 @@ struct ipc_client_connect_options {\n \t * the service and need to wait for it to become ready.\n \t */\n \tunsigned int wait_if_not_found:1;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n #define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n \t.wait_if_busy = 0, \\\n \t.wait_if_not_found = 0, \\\n+\t.uds_disallow_chdir = 0, \\\n }\n \n /*\n@@ -159,6 +165,11 @@ struct ipc_server_data;\n struct ipc_server_opts\n {\n \tint nr_threads;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"416836","messageId":"20210213093052.GJ1015009@szeder.dev","threadId":"54978","inReplyTo":"1e5c856ade8557d7514d9bee1c58bf978aba062c.1613174954.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2021-02-13T09:30:52Z","receivedAt":"2021-02-13T09:55:54Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Sat, Feb 13, 2021 at 12:09:13AM +0000, Jeff Hostetler via GitGitGadget wrote:\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n> Create t0052-simple-ipc.sh with unit tests for the \"simple-ipc\" mechanism.\n> \n> Create t/helper/test-simple-ipc test tool to exercise the \"simple-ipc\"\n> functions.\n> \n> When the tool is invoked with \"run-daemon\", it runs a server to listen\n> for \"simple-ipc\" connections on a test socket or named pipe and\n> responds to a set of commands to exercise/stress the communication\n> setup.\n> \n> When the tool is invoked with \"start-daemon\", it spawns a \"run-daemon\"\n> command in the background and waits for the server to become ready\n> before exiting.  (This helps make unit tests in t0052 more predictable\n> and avoids the need for arbitrary sleeps in the test script.)\n> \n> The tool also has a series of client \"send\" commands to send commands\n> and data to a server instance.\n> \n> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> ---\n\n> diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c\n> new file mode 100644\n> index 000000000000..92aa7f843cfa\n> --- /dev/null\n> +++ b/t/helper/test-simple-ipc.c\n\n[...]\n\n> +/*\n> + * This is \"application callback\" that sits on top of the \"ipc-server\".\n> + * It completely defines the set of command verbs supported by this\n\nPlease avoid the noiseword \"verbs\" and just call them commands; a few\nof these commands are not even verbs.\n\n> + * application.\n> + */\n> +static int test_app_cb(void *application_data,\n> +\t\t       const char *command,\n> +\t\t       ipc_server_reply_cb *reply_cb,\n> +\t\t       struct ipc_server_reply_data *reply_data)\n> +{\n> +\t/*\n> +\t * Verify that we received the application-data that we passed\n> +\t * when we started the ipc-server.  (We have several layers of\n> +\t * callbacks calling callbacks and it's easy to get things mixed\n> +\t * up (especially when some are \"void*\").)\n> +\t */\n> +\tif (application_data != (void*)&my_app_data)\n> +\t\tBUG(\"application_cb: application_data pointer wrong\");\n> +\n> +\tif (!strcmp(command, \"quit\")) {\n> +\t\t/*\n> +\t\t * The client sent a \"quit\" command.  This is an async\n> +\t\t * request for the server to shutdown.\n> +\t\t *\n> +\t\t * We DO NOT send the client a response message\n> +\t\t * (because we have nothing to say and the other\n> +\t\t * server threads have not yet stopped).\n> +\t\t *\n> +\t\t * Tell the ipc-server layer to start shutting down.\n> +\t\t * This includes: stop listening for new connections\n> +\t\t * on the socket/pipe and telling all worker threads\n> +\t\t * to finish/drain their outgoing responses to other\n> +\t\t * clients.\n> +\t\t *\n> +\t\t * This DOES NOT force an immediate sync shutdown.\n> +\t\t */\n> +\t\treturn SIMPLE_IPC_QUIT;\n> +\t}\n> +\n> +\tif (!strcmp(command, \"ping\")) {\n> +\t\tconst char *answer = \"pong\";\n> +\t\treturn reply_cb(reply_data, answer, strlen(answer));\n> +\t}\n> +\n> +\tif (!strcmp(command, \"big\"))\n> +\t\treturn app__big_command(reply_cb, reply_data);\n> +\n> +\tif (!strcmp(command, \"chunk\"))\n> +\t\treturn app__chunk_command(reply_cb, reply_data);\n> +\n> +\tif (!strcmp(command, \"slow\"))\n> +\t\treturn app__slow_command(reply_cb, reply_data);\n> +\n> +\tif (starts_with(command, \"sendbytes \"))\n> +\t\treturn app__sendbytes_command(command, reply_cb, reply_data);\n> +\n> +\treturn app__unhandled_command(command, reply_cb, reply_data);\n> +}\n\n[...]\n\n> +int cmd__simple_ipc(int argc, const char **argv)\n> +{\n> +\tconst char *path = \"ipc-test\";\n\nSince the path of the socket used in the tests is hardcoded, we could\nuse it in the tests as well to check its presence/absence.\n\n[...]\n\n> diff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\n> new file mode 100755\n> index 000000000000..e36b786709ec\n> --- /dev/null\n> +++ b/t/t0052-simple-ipc.sh\n> @@ -0,0 +1,134 @@\n\n[...]\n\n> +# Sending a \"quit\" message to the server causes it to start an \"async\n> +# shutdown\" -- queuing shutdown events to all socket/pipe thread-pool\n> +# threads.  Each thread will process that event after finishing\n> +# (draining) any in-progress IO with other clients.  So when the \"send\n> +# quit\" client command exits, the ipc-server may still be running (but\n> +# it should be cleaning up).\n> +#\n> +# So, insert a generous sleep here to give the server time to shutdown.\n> +#\n> +test_expect_success '`quit` works' '\n> +\ttest-tool simple-ipc send quit &&\n> +\n> +\tsleep 5 &&\n\nThe server process is responsible for removing the socket, so instead\nof a hard-coded 5 seconds delay the test could (semi-)busy wait in a\nloop until the socket disappears like this:\n\ndiff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\nindex 6958835454..609d8d4283 100755\n--- a/t/t0052-simple-ipc.sh\n+++ b/t/t0052-simple-ipc.sh\n@@ -122,6 +122,13 @@ test_expect_success 'stress test threads' '\n \n test_expect_success '`quit` works' '\n \ttest-tool simple-ipc send quit &&\n+\tnr_tries_left=10 &&\n+\twhile test -S ipc-test &&\n+\t      test $nr_tries_left -gt 0\n+\tdo\n+\t\tsleep 1\n+\t\tnr_tries_left=$(($nr_tries_left - 1))\n+\tdone &&\n \ttest_must_fail test-tool simple-ipc is-active &&\n \ttest_must_fail test-tool simple-ipc send ping\n '\n\nThis way we might get away without any delay or with only a single\none-second sleep in most cases, while we could bump the timeout a bit\nhigher for the sake of a CI system in a particularly bad mood.\n\nWould this work on Windows, or at least could it be tweaked to work\nthere?\n\nI think this is conceptually the same as what you did at startup,\nexcept in this example the test script waits instead of the test-tool\nsubcommand.  Perhaps it would be worth incorporating this wait into\nthe test-tool as well; or perhaps it would be simpler to do the\nwaiting in the test script at startup as well.\n\n> +\ttest_must_fail test-tool simple-ipc is-active &&\n> +\ttest_must_fail test-tool simple-ipc send ping\n> +'\n> +\n> +test_done\n> -- \n> gitgitgadget\n"},{"id":"417066","messageId":"01e7805a-a245-7640-42d4-5ecc01195598@jeffhostetler.com","threadId":"54978","inReplyTo":"20210213093052.GJ1015009@szeder.dev","subject":"Re: [PATCH v3 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-02-16T15:53:48Z","receivedAt":"2021-02-16T15:54:54Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/13/21 4:30 AM, SZEDER Gábor wrote:\n\n[...]\n\n> [...]\n> \n>> +int cmd__simple_ipc(int argc, const char **argv)\n>> +{\n>> +\tconst char *path = \"ipc-test\";\n> \n> Since the path of the socket used in the tests is hardcoded, we could\n> use it in the tests as well to check its presence/absence.\n> \n> [...]\n> \n>> diff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\n>> new file mode 100755\n>> index 000000000000..e36b786709ec\n>> --- /dev/null\n>> +++ b/t/t0052-simple-ipc.sh\n>> @@ -0,0 +1,134 @@\n> \n> [...]\n> \n>> +# Sending a \"quit\" message to the server causes it to start an \"async\n>> +# shutdown\" -- queuing shutdown events to all socket/pipe thread-pool\n>> +# threads.  Each thread will process that event after finishing\n>> +# (draining) any in-progress IO with other clients.  So when the \"send\n>> +# quit\" client command exits, the ipc-server may still be running (but\n>> +# it should be cleaning up).\n>> +#\n>> +# So, insert a generous sleep here to give the server time to shutdown.\n>> +#\n>> +test_expect_success '`quit` works' '\n>> +\ttest-tool simple-ipc send quit &&\n>> +\n>> +\tsleep 5 &&\n> \n> The server process is responsible for removing the socket, so instead\n> of a hard-coded 5 seconds delay the test could (semi-)busy wait in a\n> loop until the socket disappears like this:\n> \n> diff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\n> index 6958835454..609d8d4283 100755\n> --- a/t/t0052-simple-ipc.sh\n> +++ b/t/t0052-simple-ipc.sh\n> @@ -122,6 +122,13 @@ test_expect_success 'stress test threads' '\n>   \n>   test_expect_success '`quit` works' '\n>   \ttest-tool simple-ipc send quit &&\n> +\tnr_tries_left=10 &&\n> +\twhile test -S ipc-test &&\n> +\t      test $nr_tries_left -gt 0\n> +\tdo\n> +\t\tsleep 1\n> +\t\tnr_tries_left=$(($nr_tries_left - 1))\n> +\tdone &&\n>   \ttest_must_fail test-tool simple-ipc is-active &&\n>   \ttest_must_fail test-tool simple-ipc send ping\n>   '\n> \n> This way we might get away without any delay or with only a single\n> one-second sleep in most cases, while we could bump the timeout a bit\n> higher for the sake of a CI system in a particularly bad mood.\n> \n> Would this work on Windows, or at least could it be tweaked to work\n> there?\n> \n> I think this is conceptually the same as what you did at startup,\n> except in this example the test script waits instead of the test-tool\n> subcommand.  Perhaps it would be worth incorporating this wait into\n> the test-tool as well; or perhaps it would be simpler to do the\n> waiting in the test script at startup as well.\n\nThanks for the suggestions.  Let me take another pass at\nit.  I think making the \"send quit\" command try to wait until\nthe server shutdown would make it easier for all concerned.\n\n> \n>> +\ttest_must_fail test-tool simple-ipc is-active &&\n>> +\ttest_must_fail test-tool simple-ipc send ping\n>> +'\n>> +\n>> +test_done\n>> -- \n>> gitgitgadget\n"},{"id":"417226","messageId":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v3.git.1613174954.gitgitgadget@gmail.com","subject":"[PATCH v4 00/12] Simple IPC Mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:36Z","receivedAt":"2021-02-17T21:49:36Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"Here is V4 of my \"Simple IPC\" series. It addresses Gábor's comment WRT\nshutting down the server to make unit tests more predictable on CI servers.\n(https://lore.kernel.org/git/20210213093052.GJ1015009@szeder.dev)\n\nJeff\n\ncc: Ævar Arnfjörð Bjarmason avarab@gmail.com cc: Jeff Hostetler\ngit@jeffhostetler.com cc: Jeff King peff@peff.net cc: Chris Torek\nchris.torek@gmail.com\n\nJeff Hostetler (9):\n  pkt-line: eliminate the need for static buffer in\n    packet_write_gently()\n  simple-ipc: design documentation for new IPC mechanism\n  simple-ipc: add win32 implementation\n  unix-socket: elimiate static unix_stream_socket() helper function\n  unix-socket: add backlog size option to unix_stream_listen()\n  unix-socket: disallow chdir() when creating unix domain sockets\n  unix-socket: create `unix_stream_server__listen_with_lock()`\n  simple-ipc: add Unix domain socket implementation\n  t0052: add simple-ipc tests and t/helper/test-simple-ipc tool\n\nJohannes Schindelin (3):\n  pkt-line: do not issue flush packets in write_packetized_*()\n  pkt-line: (optionally) libify the packet readers\n  pkt-line: add options argument to read_packetized_to_strbuf()\n\n Documentation/technical/api-simple-ipc.txt |  34 +\n Makefile                                   |   8 +\n builtin/credential-cache--daemon.c         |   3 +-\n builtin/credential-cache.c                 |   2 +-\n compat/simple-ipc/ipc-shared.c             |  28 +\n compat/simple-ipc/ipc-unix-socket.c        | 979 +++++++++++++++++++++\n compat/simple-ipc/ipc-win32.c              | 749 ++++++++++++++++\n config.mak.uname                           |   2 +\n contrib/buildsystems/CMakeLists.txt        |   6 +\n convert.c                                  |  16 +-\n pkt-line.c                                 |  57 +-\n pkt-line.h                                 |  20 +-\n simple-ipc.h                               | 235 +++++\n t/helper/test-simple-ipc.c                 | 773 ++++++++++++++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t0052-simple-ipc.sh                      | 122 +++\n unix-socket.c                              | 168 +++-\n unix-socket.h                              |  47 +-\n 19 files changed, 3198 insertions(+), 53 deletions(-)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\n\nbase-commit: 773e25afc41b1b6533fa9ae2cd825d0b4a697fad\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/766\n\nRange-diff vs v3:\n\n  1:  2d6858b1625a =  1:  2d6858b1625a pkt-line: eliminate the need for static buffer in packet_write_gently()\n  2:  91a9f63d6692 =  2:  91a9f63d6692 pkt-line: do not issue flush packets in write_packetized_*()\n  3:  e05467def4e1 =  3:  e05467def4e1 pkt-line: (optionally) libify the packet readers\n  4:  81e14bed955c =  4:  81e14bed955c pkt-line: add options argument to read_packetized_to_strbuf()\n  5:  22eec60761a8 =  5:  22eec60761a8 simple-ipc: design documentation for new IPC mechanism\n  6:  171ec43ecfa4 =  6:  171ec43ecfa4 simple-ipc: add win32 implementation\n  7:  b368318e6a23 =  7:  b368318e6a23 unix-socket: elimiate static unix_stream_socket() helper function\n  8:  985b2e02b2df =  8:  985b2e02b2df unix-socket: add backlog size option to unix_stream_listen()\n  9:  1bfa36409d07 =  9:  1bfa36409d07 unix-socket: disallow chdir() when creating unix domain sockets\n 10:  b443e11ac32f = 10:  b443e11ac32f unix-socket: create `unix_stream_server__listen_with_lock()`\n 11:  43c8db9a4468 = 11:  43c8db9a4468 simple-ipc: add Unix domain socket implementation\n 12:  1e5c856ade85 ! 12:  09568a6500dd t0052: add simple-ipc tests and t/helper/test-simple-ipc tool\n     @@ t/helper/test-simple-ipc.c (new)\n      +static ipc_server_application_cb test_app_cb;\n      +\n      +/*\n     -+ * This is \"application callback\" that sits on top of the \"ipc-server\".\n     -+ * It completely defines the set of command verbs supported by this\n     -+ * application.\n     ++ * This is the \"application callback\" that sits on top of the\n     ++ * \"ipc-server\".  It completely defines the set of commands supported\n     ++ * by this application.\n      + */\n      +static int test_app_cb(void *application_data,\n      +\t\t       const char *command,\n     @@ t/helper/test-simple-ipc.c (new)\n      + * Send an IPC command to an already-running server daemon and print the\n      + * response.\n      + *\n     -+ * argv[2] contains a simple (1 word) command verb that `test_app_cb()`\n     -+ * (in the daemon process) will understand.\n     ++ * argv[2] contains a simple (1 word) command that `test_app_cb()` (in\n     ++ * the daemon process) will understand.\n      + */\n      +static int client__send_ipc(int argc, const char **argv, const char *path)\n      +{\n     @@ t/helper/test-simple-ipc.c (new)\n      +}\n      +\n      +/*\n     ++ * Send an IPC command to an already-running server and ask it to\n     ++ * shutdown.  \"send quit\" is an async request and queues a shutdown\n     ++ * event in the server, so we spin and wait here for it to actually\n     ++ * shutdown to make the unit tests a little easier to write.\n     ++ */\n     ++static int client__stop_server(int argc, const char **argv, const char *path)\n     ++{\n     ++\tconst char *send_quit[] = { argv[0], \"send\", \"quit\", NULL };\n     ++\tint max_wait_sec = 60;\n     ++\tint ret;\n     ++\ttime_t time_limit, now;\n     ++\tenum ipc_active_state s;\n     ++\n     ++\tconst char * const stop_usage[] = {\n     ++\t\tN_(\"test-helper simple-ipc stop-daemon [<options>]\"),\n     ++\t\tNULL\n     ++\t};\n     ++\n     ++\tstruct option stop_options[] = {\n     ++\t\tOPT_INTEGER(0, \"max-wait\", &max_wait_sec,\n     ++\t\t\t    N_(\"seconds to wait for daemon to stop\")),\n     ++\t\tOPT_END()\n     ++\t};\n     ++\n     ++\targc = parse_options(argc, argv, NULL, stop_options, stop_usage, 0);\n     ++\n     ++\tif (max_wait_sec < 0)\n     ++\t\tmax_wait_sec = 0;\n     ++\n     ++\ttime(&time_limit);\n     ++\ttime_limit += max_wait_sec;\n     ++\n     ++\tret = client__send_ipc(3, send_quit, path);\n     ++\tif (ret)\n     ++\t\treturn ret;\n     ++\n     ++\tfor (;;) {\n     ++\t\tsleep_millisec(100);\n     ++\n     ++\t\ts = ipc_get_active_state(path);\n     ++\n     ++\t\tif (s != IPC_STATE__LISTENING) {\n     ++\t\t\t/*\n     ++\t\t\t * The socket/pipe is gone and/or has stopped\n     ++\t\t\t * responding.  Lets assume that the daemon\n     ++\t\t\t * process has exited too.\n     ++\t\t\t */\n     ++\t\t\treturn 0;\n     ++\t\t}\n     ++\n     ++\t\ttime(&now);\n     ++\t\tif (now > time_limit)\n     ++\t\t\treturn error(_(\"daemon has not shutdown yet\"));\n     ++\t}\n     ++}\n     ++\n     ++/*\n      + * Send an IPC command followed by ballast to confirm that a large\n      + * message can be sent and that the kernel or pkt-line layers will\n      + * properly chunk it and that the daemon receives the entire message.\n     @@ t/helper/test-simple-ipc.c (new)\n      +\tif (client__probe_server(path))\n      +\t\treturn 1;\n      +\n     ++\tif (argc >= 2 && !strcmp(argv[1], \"stop-daemon\"))\n     ++\t\treturn !!client__stop_server(argc, argv, path);\n     ++\n      +\tif ((argc == 2 || argc == 3) && !strcmp(argv[1], \"send\"))\n      +\t\treturn !!client__send_ipc(argc, argv, path);\n      +\n     @@ t/t0052-simple-ipc.sh (new)\n      +}\n      +\n      +stop_simple_IPC_server () {\n     -+\ttest-tool simple-ipc send quit\n     ++\ttest-tool simple-ipc stop-daemon\n      +}\n      +\n      +test_expect_success 'start simple command server' '\n     @@ t/t0052-simple-ipc.sh (new)\n      +\ttest_cmp expect_a actual_a\n      +'\n      +\n     -+# Sending a \"quit\" message to the server causes it to start an \"async\n     -+# shutdown\" -- queuing shutdown events to all socket/pipe thread-pool\n     -+# threads.  Each thread will process that event after finishing\n     -+# (draining) any in-progress IO with other clients.  So when the \"send\n     -+# quit\" client command exits, the ipc-server may still be running (but\n     -+# it should be cleaning up).\n     -+#\n     -+# So, insert a generous sleep here to give the server time to shutdown.\n     -+#\n     -+test_expect_success '`quit` works' '\n     -+\ttest-tool simple-ipc send quit &&\n     -+\n     -+\tsleep 5 &&\n     -+\n     ++test_expect_success 'stop-daemon works' '\n     ++\ttest-tool simple-ipc stop-daemon &&\n      +\ttest_must_fail test-tool simple-ipc is-active &&\n      +\ttest_must_fail test-tool simple-ipc send ping\n      +'\n\n-- \ngitgitgadget\n"},{"id":"417227","messageId":"2d6858b1625aa3c96688c6c6a9157c2d2b16f43e.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:37Z","receivedAt":"2021-02-17T21:49:46Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nTeach `packet_write_gently()` to write the pkt-line header and the actual\nbuffer in 2 separate calls to `write_in_full()` and avoid the need for a\nstatic buffer, thread-safe scratch space, or an excessively large stack\nbuffer.\n\nChange the API of `write_packetized_from_fd()` to accept a scratch space\nargument from its caller to avoid similar issues here.\n\nThese changes are intended to make it easier to use pkt-line routines in\na multi-threaded context with multiple concurrent writers writing to\ndifferent streams.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n convert.c  |  7 ++++---\n pkt-line.c | 28 +++++++++++++++++++---------\n pkt-line.h | 12 +++++++++---\n 3 files changed, 32 insertions(+), 15 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07ce..41012c2d301c 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -883,9 +883,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \tif (err)\n \t\tgoto done;\n \n-\tif (fd >= 0)\n-\t\terr = write_packetized_from_fd(fd, process->in);\n-\telse\n+\tif (fd >= 0) {\n+\t\tstruct packet_scratch_space scratch;\n+\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n+\t} else\n \t\terr = write_packetized_from_buf(src, len, process->in);\n \tif (err)\n \t\tgoto done;\ndiff --git a/pkt-line.c b/pkt-line.c\nindex d633005ef746..4cff2f7a68a5 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -196,17 +196,25 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)\n \n static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n {\n-\tstatic char packet_write_buffer[LARGE_PACKET_MAX];\n+\tchar header[4];\n \tsize_t packet_size;\n \n-\tif (size > sizeof(packet_write_buffer) - 4)\n+\tif (size > LARGE_PACKET_DATA_MAX)\n \t\treturn error(_(\"packet write failed - data exceeds max packet size\"));\n \n \tpacket_trace(buf, size, 1);\n \tpacket_size = size + 4;\n-\tset_packet_header(packet_write_buffer, packet_size);\n-\tmemcpy(packet_write_buffer + 4, buf, size);\n-\tif (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)\n+\n+\tset_packet_header(header, packet_size);\n+\n+\t/*\n+\t * Write the header and the buffer in 2 parts so that we do not need\n+\t * to allocate a buffer or rely on a static buffer.  This avoids perf\n+\t * and multi-threading issues.\n+\t */\n+\n+\tif (write_in_full(fd_out, header, 4) < 0 ||\n+\t    write_in_full(fd_out, buf, size) < 0)\n \t\treturn error(_(\"packet write failed\"));\n \treturn 0;\n }\n@@ -242,19 +250,21 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \tpacket_trace(data, len, 1);\n }\n \n-int write_packetized_from_fd(int fd_in, int fd_out)\n+int write_packetized_from_fd(int fd_in, int fd_out,\n+\t\t\t     struct packet_scratch_space *scratch)\n {\n-\tstatic char buf[LARGE_PACKET_DATA_MAX];\n \tint err = 0;\n \tssize_t bytes_to_write;\n \n \twhile (!err) {\n-\t\tbytes_to_write = xread(fd_in, buf, sizeof(buf));\n+\t\tbytes_to_write = xread(fd_in, scratch->buffer,\n+\t\t\t\t       sizeof(scratch->buffer));\n \t\tif (bytes_to_write < 0)\n \t\t\treturn COPY_READ_ERROR;\n \t\tif (bytes_to_write == 0)\n \t\t\tbreak;\n-\t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n+\t\terr = packet_write_gently(fd_out, scratch->buffer,\n+\t\t\t\t\t  bytes_to_write);\n \t}\n \tif (!err)\n \t\terr = packet_flush_gently(fd_out);\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 8c90daa59ef0..c0722aefe638 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -5,6 +5,13 @@\n #include \"strbuf.h\"\n #include \"sideband.h\"\n \n+#define LARGE_PACKET_MAX 65520\n+#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)\n+\n+struct packet_scratch_space {\n+\tchar buffer[LARGE_PACKET_DATA_MAX]; /* does not include header bytes */\n+};\n+\n /*\n  * Write a packetized stream, where each line is preceded by\n  * its length (including the header) as a 4-byte hex number.\n@@ -32,7 +39,7 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f\n void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n int packet_flush_gently(int fd);\n int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n-int write_packetized_from_fd(int fd_in, int fd_out);\n+int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n \n /*\n@@ -213,8 +220,7 @@ enum packet_read_status packet_reader_read(struct packet_reader *reader);\n enum packet_read_status packet_reader_peek(struct packet_reader *reader);\n \n #define DEFAULT_PACKET_MAX 1000\n-#define LARGE_PACKET_MAX 65520\n-#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)\n+\n extern char packet_buffer[LARGE_PACKET_MAX];\n \n struct packet_writer {\n-- \ngitgitgadget\n\n"},{"id":"417228","messageId":"91a9f63d66924d14a22feedf7b1d88fe298b90bc.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 02/12] pkt-line: do not issue flush packets in write_packetized_*()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:38Z","receivedAt":"2021-02-17T21:49:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nRemove the `packet_flush_gently()` call in `write_packetized_from_buf() and\n`write_packetized_from_fd()` and require the caller to call it if desired.\nRename both functions to `write_packetized_from_*_no_flush()` to prevent\nlater merge accidents.\n\n`write_packetized_from_buf()` currently only has one caller:\n`apply_multi_file_filter()` in `convert.c`.  It always wants a flush packet\nto be written after writing the payload.\n\nHowever, we are about to introduce a caller that wants to write many\npackets before a final flush packet, so let's make the caller responsible\nfor emitting the flush packet.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n convert.c  |  8 ++++++--\n pkt-line.c | 10 +++-------\n pkt-line.h |  4 ++--\n 3 files changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 41012c2d301c..bccf7afa8797 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -885,9 +885,13 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \n \tif (fd >= 0) {\n \t\tstruct packet_scratch_space scratch;\n-\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n+\t\terr = write_packetized_from_fd_no_flush(fd, process->in, &scratch);\n \t} else\n-\t\terr = write_packetized_from_buf(src, len, process->in);\n+\t\terr = write_packetized_from_buf_no_flush(src, len, process->in);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = packet_flush_gently(process->in);\n \tif (err)\n \t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 4cff2f7a68a5..3602b0d37092 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -250,8 +250,8 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \tpacket_trace(data, len, 1);\n }\n \n-int write_packetized_from_fd(int fd_in, int fd_out,\n-\t\t\t     struct packet_scratch_space *scratch)\n+int write_packetized_from_fd_no_flush(int fd_in, int fd_out,\n+\t\t\t\t      struct packet_scratch_space *scratch)\n {\n \tint err = 0;\n \tssize_t bytes_to_write;\n@@ -266,12 +266,10 @@ int write_packetized_from_fd(int fd_in, int fd_out,\n \t\terr = packet_write_gently(fd_out, scratch->buffer,\n \t\t\t\t\t  bytes_to_write);\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\n \n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out)\n {\n \tint err = 0;\n \tsize_t bytes_written = 0;\n@@ -287,8 +285,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n \t\tbytes_written += bytes_to_write;\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\n \ndiff --git a/pkt-line.h b/pkt-line.h\nindex c0722aefe638..a7149429ac35 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -39,8 +39,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f\n void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n int packet_flush_gently(int fd);\n int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n-int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n+int write_packetized_from_fd_no_flush(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);\n \n /*\n  * Read a packetized line into the buffer, which must be at least size bytes\n-- \ngitgitgadget\n\n"},{"id":"417229","messageId":"e05467def4e158a5f1cfa3aafffdb5c77097859a.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 03/12] pkt-line: (optionally) libify the packet readers","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:39Z","receivedAt":"2021-02-17T21:49:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nSo far, the (possibly indirect) callers of `get_packet_data()` can ask\nthat function to return an error instead of `die()`ing upon end-of-file.\nHowever, random read errors will still cause the process to die.\n\nSo let's introduce an explicit option to tell the packet reader\nmachinery to please be nice and only return an error.\n\nThis change prepares pkt-line for use by long-running daemon processes.\nSuch processes should be able to serve multiple concurrent clients and\nand survive random IO errors.  If there is an error on one connection,\na daemon should be able to drop that connection and continue serving\nexisting and future connections.\n\nThis ability will be used by a Git-aware \"Internal FSMonitor\" feature\nin a later patch series.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n pkt-line.c | 19 +++++++++++++++++--\n pkt-line.h |  4 ++++\n 2 files changed, 21 insertions(+), 2 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex 3602b0d37092..83c46e6b46ee 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -304,8 +304,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\t*src_size -= ret;\n \t} else {\n \t\tret = read_in_full(fd, dst, size);\n-\t\tif (ret < 0)\n+\t\tif (ret < 0) {\n+\t\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\t\treturn error_errno(_(\"read error\"));\n \t\t\tdie_errno(_(\"read error\"));\n+\t\t}\n \t}\n \n \t/* And complain if we didn't get enough bytes to satisfy the read. */\n@@ -313,6 +316,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n \t\t\treturn -1;\n \n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"the remote end hung up unexpectedly\"));\n \t\tdie(_(\"the remote end hung up unexpectedly\"));\n \t}\n \n@@ -341,6 +346,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \tlen = packet_length(linelen);\n \n \tif (len < 0) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length \"\n+\t\t\t\t       \"character: %.4s\"), linelen);\n \t\tdie(_(\"protocol error: bad line length character: %.4s\"), linelen);\n \t} else if (!len) {\n \t\tpacket_trace(\"0000\", 4, 0);\n@@ -355,12 +363,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \t\t*pktlen = 0;\n \t\treturn PACKET_READ_RESPONSE_END;\n \t} else if (len < 4) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n \t}\n \n \tlen -= 4;\n-\tif ((unsigned)len >= size)\n+\tif ((unsigned)len >= size) {\n+\t\tif (options & PACKET_READ_NEVER_DIE)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n+\t}\n \n \tif (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) {\n \t\t*pktlen = -1;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex a7149429ac35..2e472efaf2c5 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -75,10 +75,14 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou\n  *\n  * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n  * ERR packet.\n+ *\n+ * With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except\n+ * an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect).\n  */\n #define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n #define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n #define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n+#define PACKET_READ_NEVER_DIE         (1u<<3)\n int packet_read(int fd, char **src_buffer, size_t *src_len, char\n \t\t*buffer, unsigned size, int options);\n \n-- \ngitgitgadget\n\n"},{"id":"417230","messageId":"81e14bed955c6b50e155f6f73cb642d6c9f2fd73.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 04/12] pkt-line: add options argument to read_packetized_to_strbuf()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:40Z","receivedAt":"2021-02-17T21:49:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nUpdate the calling sequence of `read_packetized_to_strbuf()` to take\nan options argument and not assume a fixed set of options.  Update the\nonly existing caller accordingly to explicitly pass the\nformerly-assumed flags.\n\nThe `read_packetized_to_strbuf()` function calls `packet_read()` with\na fixed set of assumed options (`PACKET_READ_GENTLE_ON_EOF`).  This\nassumption has been fine for the single existing caller\n`apply_multi_file_filter()` in `convert.c`.\n\nIn a later commit we would like to add other callers to\n`read_packetized_to_strbuf()` that need a different set of options.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n convert.c  | 3 ++-\n pkt-line.c | 4 ++--\n pkt-line.h | 2 +-\n 3 files changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex bccf7afa8797..9f44f00d841f 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -908,7 +908,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \t\tif (err)\n \t\t\tgoto done;\n \n-\t\terr = read_packetized_to_strbuf(process->out, &nbuf) < 0;\n+\t\terr = read_packetized_to_strbuf(process->out, &nbuf,\n+\t\t\t\t\t\tPACKET_READ_GENTLE_ON_EOF) < 0;\n \t\tif (err)\n \t\t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 83c46e6b46ee..18ecad65e08c 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -442,7 +442,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)\n \treturn packet_read_line_generic(-1, src, src_len, dst_len);\n }\n \n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options)\n {\n \tint packet_len;\n \n@@ -458,7 +458,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n \t\t\t * that there is already room for the extra byte.\n \t\t\t */\n \t\t\tsb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,\n-\t\t\tPACKET_READ_GENTLE_ON_EOF);\n+\t\t\toptions);\n \t\tif (packet_len <= 0)\n \t\t\tbreak;\n \t\tsb_out->len += packet_len;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 2e472efaf2c5..e347fe46832a 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -142,7 +142,7 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);\n /*\n  * Reads a stream of variable sized packets until a flush packet is detected.\n  */\n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options);\n \n /*\n  * Receive multiplexed output stream over git native protocol.\n-- \ngitgitgadget\n\n"},{"id":"417231","messageId":"22eec60761a88107b2e337ce13eed1020352aa73.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 05/12] simple-ipc: design documentation for new IPC mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:41Z","receivedAt":"2021-02-17T21:50:28Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nBrief design documentation for new IPC mechanism allowing\nforeground Git client to talk with an existing daemon process\nat a known location using a named pipe or unix domain socket.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-simple-ipc.txt | 34 ++++++++++++++++++++++\n 1 file changed, 34 insertions(+)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n\ndiff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt\nnew file mode 100644\nindex 000000000000..670a5c163e39\n--- /dev/null\n+++ b/Documentation/technical/api-simple-ipc.txt\n@@ -0,0 +1,34 @@\n+simple-ipc API\n+==============\n+\n+The simple-ipc API is used to send an IPC message and response between\n+a (presumably) foreground Git client process to a background server or\n+daemon process.  The server process must already be running.  Multiple\n+client processes can simultaneously communicate with the server\n+process.\n+\n+Communication occurs over a named pipe on Windows and a Unix domain\n+socket on other platforms.  Clients and the server rendezvous at a\n+previously agreed-to application-specific pathname (which is outside\n+the scope of this design).\n+\n+This IPC mechanism differs from the existing `sub-process.c` model\n+(Documentation/technical/long-running-process-protocol.txt) and used\n+by applications like Git-LFS.  In the simple-ipc model the server is\n+assumed to be a very long-running system service.  In contrast, in the\n+LFS-style sub-process model the helper is started with the foreground\n+process and exits when the foreground process terminates.\n+\n+How the simple-ipc server is started is also outside the scope of the\n+IPC mechanism.  For example, the server might be started during\n+maintenance operations.\n+\n+The IPC protocol consists of a single request message from the client and\n+an optional request message from the server.  For simplicity, pkt-line\n+routines are used to hide chunking and buffering concerns.  Each side\n+terminates their message with a flush packet.\n+(Documentation/technical/protocol-common.txt)\n+\n+The actual format of the client and server messages is application\n+specific.  The IPC layer transmits and receives an opaque buffer without\n+any concern for the content within.\n-- \ngitgitgadget\n\n"},{"id":"417232","messageId":"171ec43ecfa45054afc378aca00c13282e438c47.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 06/12] simple-ipc: add win32 implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:42Z","receivedAt":"2021-02-17T21:50:30Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Windows implementation of \"simple-ipc\" using named pipes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   5 +\n compat/simple-ipc/ipc-shared.c      |  28 ++\n compat/simple-ipc/ipc-win32.c       | 749 ++++++++++++++++++++++++++++\n config.mak.uname                    |   2 +\n contrib/buildsystems/CMakeLists.txt |   4 +\n simple-ipc.h                        | 224 +++++++++\n 6 files changed, 1012 insertions(+)\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n\ndiff --git a/Makefile b/Makefile\nindex 4128b457e14b..40d5cab78d3f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1679,6 +1679,11 @@ else\n \tLIB_OBJS += unix-socket.o\n endif\n \n+ifdef USE_WIN32_IPC\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-win32.o\n+endif\n+\n ifdef NO_ICONV\n \tBASIC_CFLAGS += -DNO_ICONV\n endif\ndiff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c\nnew file mode 100644\nindex 000000000000..1edec8159532\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-shared.c\n@@ -0,0 +1,28 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data)\n+{\n+\tstruct ipc_server_data *server_data = NULL;\n+\tint ret;\n+\n+\tret = ipc_server_run_async(&server_data, path, opts,\n+\t\t\t\t   application_cb, application_data);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = ipc_server_await(server_data);\n+\n+\tipc_server_free(server_data);\n+\n+\treturn ret;\n+}\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\ndiff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c\nnew file mode 100644\nindex 000000000000..f0cfbf9d15c3\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-win32.c\n@@ -0,0 +1,749 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+#error This file can only be compiled on Windows\n+#endif\n+\n+static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc)\n+{\n+\tint off = 0;\n+\tstruct strbuf realpath = STRBUF_INIT;\n+\n+\tif (!strbuf_realpath(&realpath, path, 0))\n+\t\treturn -1;\n+\n+\toff = swprintf(wpath, alloc, L\"\\\\\\\\.\\\\pipe\\\\\");\n+\tif (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0)\n+\t\treturn -1;\n+\n+\t/* Handle drive prefix */\n+\tif (wpath[off] && wpath[off + 1] == L':') {\n+\t\twpath[off + 1] = L'_';\n+\t\toff += 2;\n+\t}\n+\n+\tfor (; wpath[off]; off++)\n+\t\tif (wpath[off] == L'/')\n+\t\t\twpath[off] = L'\\\\';\n+\n+\tstrbuf_release(&realpath);\n+\treturn 0;\n+}\n+\n+static enum ipc_active_state get_active_state(wchar_t *pipe_path)\n+{\n+\tif (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT))\n+\t\treturn IPC_STATE__LISTENING;\n+\n+\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\tif (GetLastError() == ERROR_FILE_NOT_FOUND)\n+\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\treturn IPC_STATE__OTHER_ERROR;\n+}\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\twchar_t pipe_path[MAX_PATH];\n+\n+\tif (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\treturn get_active_state(pipe_path);\n+}\n+\n+#define WAIT_STEP_MS (50)\n+\n+static enum ipc_active_state connect_to_server(\n+\tconst wchar_t *wpath,\n+\tDWORD timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tDWORD t_start_ms, t_waited_ms;\n+\tDWORD step_ms;\n+\tHANDLE hPipe = INVALID_HANDLE_VALUE;\n+\tDWORD mode = PIPE_READMODE_BYTE;\n+\tDWORD gle;\n+\n+\t*pfd = -1;\n+\n+\tfor (;;) {\n+\t\thPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE,\n+\t\t\t\t    0, NULL, OPEN_EXISTING, 0, NULL);\n+\t\tif (hPipe != INVALID_HANDLE_VALUE)\n+\t\t\tbreak;\n+\n+\t\tgle = GetLastError();\n+\n+\t\tswitch (gle) {\n+\t\tcase ERROR_FILE_NOT_FOUND:\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tstep_ms = (timeout_ms < WAIT_STEP_MS) ?\n+\t\t\t\ttimeout_ms : WAIT_STEP_MS;\n+\t\t\tsleep_millisec(step_ms);\n+\n+\t\t\ttimeout_ms -= step_ms;\n+\t\t\tbreak; /* try again */\n+\n+\t\tcase ERROR_PIPE_BUSY:\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tt_start_ms = (DWORD)(getnanotime() / 1000000);\n+\n+\t\t\tif (!WaitNamedPipeW(wpath, timeout_ms)) {\n+\t\t\t\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * A pipe server instance became available.\n+\t\t\t * Race other client processes to connect to\n+\t\t\t * it.\n+\t\t\t *\n+\t\t\t * But first decrement our overall timeout so\n+\t\t\t * that we don't starve if we keep losing the\n+\t\t\t * race.  But also guard against special\n+\t\t\t * NPMWAIT_ values (0 and -1).\n+\t\t\t */\n+\t\t\tt_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms;\n+\t\t\tif (t_waited_ms < timeout_ms)\n+\t\t\t\ttimeout_ms -= t_waited_ms;\n+\t\t\telse\n+\t\t\t\ttimeout_ms = 1;\n+\t\t\tbreak; /* try again */\n+\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t}\n+\t}\n+\n+\tif (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t*pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);\n+\tif (*pfd < 0) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t/* fd now owns hPipe */\n+\n+\treturn IPC_STATE__LISTENING;\n+}\n+\n+/*\n+ * The default connection timeout for Windows clients.\n+ *\n+ * This is not currently part of the ipc_ API (nor the config settings)\n+ * because of differences between Windows and other platforms.\n+ *\n+ * This value was chosen at random.\n+ */\n+#define WINDOWS_CONNECTION_TIMEOUT_MS (30000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\twchar_t wpath[MAX_PATH];\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tif (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0)\n+\t\tstate = IPC_STATE__INVALID_PATH;\n+\telse\n+\t\tstate = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n+\t\t\t\t\t       connection->fd) < 0 ||\n+\t    packet_flush_gently(connection->fd) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tFlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));\n+\n+\tif (read_packetized_to_strbuf(\n+\t\t    connection->fd, answer,\n+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *response)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, response);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Duplicate the given pipe handle and wrap it in a file descriptor so\n+ * that we can use pkt-line on it.\n+ */\n+static int dup_fd_from_pipe(const HANDLE pipe)\n+{\n+\tHANDLE process = GetCurrentProcess();\n+\tHANDLE handle;\n+\tint fd;\n+\n+\tif (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE,\n+\t\t\t     DUPLICATE_SAME_ACCESS)) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\treturn -1;\n+\t}\n+\n+\tfd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY);\n+\tif (fd < 0) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\tCloseHandle(handle);\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * `handle` is now owned by `fd` and will be automatically closed\n+\t * when the descriptor is closed.\n+\t */\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_SERVER_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_server_thread_data *server_thread_data;\n+};\n+\n+struct ipc_server_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+\tHANDLE hPipe;\n+};\n+\n+/*\n+ * On Windows, the conceptual \"ipc-server\" is implemented as a pool of\n+ * n idential/peer \"server-thread\" threads.  That is, there is no\n+ * hierarchy of threads; and therefore no controller thread managing\n+ * the pool.  Each thread has an independent handle to the named pipe,\n+ * receives incoming connections, processes the client, and re-uses\n+ * the pipe for the next client connection.\n+ *\n+ * Therefore, the \"ipc-server\" only needs to maintain a list of the\n+ * spawned threads for eventual \"join\" purposes.\n+ *\n+ * A single \"stop-event\" is visible to all of the server threads to\n+ * tell them to shutdown (when idle).\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\twchar_t wpath[MAX_PATH];\n+\n+\tHANDLE hEventStopRequested;\n+\tstruct ipc_server_thread_data *thread_list;\n+\tint is_stopped;\n+};\n+\n+enum connect_result {\n+\tCR_CONNECTED = 0,\n+\tCR_CONNECT_PENDING,\n+\tCR_CONNECT_ERROR,\n+\tCR_WAIT_ERROR,\n+\tCR_SHUTDOWN,\n+};\n+\n+static enum connect_result queue_overlapped_connect(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tif (ConnectNamedPipe(server_thread_data->hPipe, lpo))\n+\t\tgoto failed;\n+\n+\tswitch (GetLastError()) {\n+\tcase ERROR_IO_PENDING:\n+\t\treturn CR_CONNECT_PENDING;\n+\n+\tcase ERROR_PIPE_CONNECTED:\n+\t\tSetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+failed:\n+\terror(_(\"ConnectNamedPipe failed for '%s' (%lu)\"),\n+\t      server_thread_data->server_data->buf_path.buf,\n+\t      GetLastError());\n+\treturn CR_CONNECT_ERROR;\n+}\n+\n+/*\n+ * Use Windows Overlapped IO to wait for a connection or for our event\n+ * to be signalled.\n+ */\n+static enum connect_result wait_for_connection(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tenum connect_result r;\n+\tHANDLE waitHandles[2];\n+\tDWORD dwWaitResult;\n+\n+\tr = queue_overlapped_connect(server_thread_data, lpo);\n+\tif (r != CR_CONNECT_PENDING)\n+\t\treturn r;\n+\n+\twaitHandles[0] = server_thread_data->server_data->hEventStopRequested;\n+\twaitHandles[1] = lpo->hEvent;\n+\n+\tdwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);\n+\tswitch (dwWaitResult) {\n+\tcase WAIT_OBJECT_0 + 0:\n+\t\treturn CR_SHUTDOWN;\n+\n+\tcase WAIT_OBJECT_0 + 1:\n+\t\tResetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\treturn CR_WAIT_ERROR;\n+\t}\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf_no_flush(response, response_len,\n+\t\t\t\t\t\t  reply_data->fd);\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ *\n+ * Simple-IPC only contains one round trip, so we flush and close\n+ * here after the response.\n+ */\n+static int do_io(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.server_thread_data = server_thread_data;\n+\n+\treply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe);\n+\tif (reply_data.fd < 0)\n+\t\treturn error(_(\"could not create fd from pipe for '%s'\"),\n+\t\t\t     server_thread_data->server_data->buf_path.buf);\n+\n+\tret = read_packetized_to_strbuf(\n+\t\treply_data.fd, &buf,\n+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);\n+\tif (ret >= 0) {\n+\t\tret = server_thread_data->server_data->application_cb(\n+\t\t\tserver_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\n+\t\tFlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd)));\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Handle IPC request and response with this connected client.  And reset\n+ * the pipe to prepare for the next client.\n+ */\n+static int use_connection(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tint ret;\n+\n+\tret = do_io(server_thread_data);\n+\n+\tFlushFileBuffers(server_thread_data->hPipe);\n+\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Thread proc for an IPC server worker thread.  It handles a series of\n+ * connections from clients.  It cleans and reuses the hPipe between each\n+ * client.\n+ */\n+static void *server_thread_proc(void *_server_thread_data)\n+{\n+\tstruct ipc_server_thread_data *server_thread_data = _server_thread_data;\n+\tHANDLE hEventConnected = INVALID_HANDLE_VALUE;\n+\tOVERLAPPED oConnect;\n+\tenum connect_result cr;\n+\tint ret;\n+\n+\tassert(server_thread_data->hPipe != INVALID_HANDLE_VALUE);\n+\n+\ttrace2_thread_start(\"ipc-server\");\n+\ttrace2_data_string(\"ipc-server\", NULL, \"pipe\",\n+\t\t\t   server_thread_data->server_data->buf_path.buf);\n+\n+\thEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL);\n+\n+\tmemset(&oConnect, 0, sizeof(oConnect));\n+\toConnect.hEvent = hEventConnected;\n+\n+\tfor (;;) {\n+\t\tcr = wait_for_connection(server_thread_data, &oConnect);\n+\n+\t\tswitch (cr) {\n+\t\tcase CR_SHUTDOWN:\n+\t\t\tgoto finished;\n+\n+\t\tcase CR_CONNECTED:\n+\t\t\tret = use_connection(server_thread_data);\n+\t\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\tserver_thread_data->server_data);\n+\t\t\t\tgoto finished;\n+\t\t\t}\n+\t\t\tif (ret > 0) {\n+\t\t\t\t/*\n+\t\t\t\t * Ignore (transient) IO errors with this\n+\t\t\t\t * client and reset for the next client.\n+\t\t\t\t */\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase CR_CONNECT_PENDING:\n+\t\t\t/* By construction, this should not happen. */\n+\t\t\tBUG(\"ipc-server[%s]: unexpeced CR_CONNECT_PENDING\",\n+\t\t\t    server_thread_data->server_data->buf_path.buf);\n+\n+\t\tcase CR_CONNECT_ERROR:\n+\t\tcase CR_WAIT_ERROR:\n+\t\t\t/*\n+\t\t\t * Ignore these theoretical errors.\n+\t\t\t */\n+\t\t\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tBUG(\"unandled case after wait_for_connection\");\n+\t\t}\n+\t}\n+\n+finished:\n+\tCloseHandle(server_thread_data->hPipe);\n+\tCloseHandle(hEventConnected);\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+static HANDLE create_new_pipe(wchar_t *wpath, int is_first)\n+{\n+\tHANDLE hPipe;\n+\tDWORD dwOpenMode, dwPipeMode;\n+\tLPSECURITY_ATTRIBUTES lpsa = NULL;\n+\n+\tdwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |\n+\t\tFILE_FLAG_OVERLAPPED;\n+\n+\tdwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |\n+\t\tPIPE_REJECT_REMOTE_CLIENTS;\n+\n+\tif (is_first) {\n+\t\tdwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE;\n+\n+\t\t/*\n+\t\t * On Windows, the first server pipe instance gets to\n+\t\t * set the ACL / Security Attributes on the named\n+\t\t * pipe; subsequent instances inherit and cannot\n+\t\t * change them.\n+\t\t *\n+\t\t * TODO Should we allow the application layer to\n+\t\t * specify security attributes, such as `LocalService`\n+\t\t * or `LocalSystem`, when we create the named pipe?\n+\t\t * This question is probably not important when the\n+\t\t * daemon is started by a foreground user process and\n+\t\t * only needs to talk to the current user, but may be\n+\t\t * if the daemon is run via the Control Panel as a\n+\t\t * System Service.\n+\t\t */\n+\t}\n+\n+\thPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,\n+\t\t\t\t PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);\n+\n+\treturn hPipe;\n+}\n+\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct ipc_server_data *server_data;\n+\twchar_t wpath[MAX_PATH];\n+\tHANDLE hPipeFirst = INVALID_HANDLE_VALUE;\n+\tint k;\n+\tint ret = 0;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\tret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));\n+\tif (ret < 0)\n+\t\treturn error(\n+\t\t\t_(\"could not create normalized wchar_t path for '%s'\"),\n+\t\t\tpath);\n+\n+\thPipeFirst = create_new_pipe(wpath, 1);\n+\tif (hPipeFirst == INVALID_HANDLE_VALUE)\n+\t\treturn error(_(\"IPC server already running on '%s'\"), path);\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tserver_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL);\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\twcscpy(server_data->wpath, wpath);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_server_thread_data *std;\n+\n+\t\tstd = xcalloc(1, sizeof(*std));\n+\t\tstd->magic = MAGIC_SERVER_THREAD_DATA;\n+\t\tstd->server_data = server_data;\n+\t\tstd->hPipe = INVALID_HANDLE_VALUE;\n+\n+\t\tstd->hPipe = (k == 0)\n+\t\t\t? hPipeFirst\n+\t\t\t: create_new_pipe(server_data->wpath, 0);\n+\n+\t\tif (std->hPipe == INVALID_HANDLE_VALUE) {\n+\t\t\t/*\n+\t\t\t * If we've reached a pipe instance limit for\n+\t\t\t * this path, just use fewer threads.\n+\t\t\t */\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (pthread_create(&std->pthread_id, NULL,\n+\t\t\t\t   server_thread_proc, std)) {\n+\t\t\t/*\n+\t\t\t * Likewise, if we're out of threads, just use\n+\t\t\t * fewer threads than requested.\n+\t\t\t *\n+\t\t\t * However, we just give up if we can't even get\n+\t\t\t * one thread.  This should not happen.\n+\t\t\t */\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start thread[0] for '%s'\"),\n+\t\t\t\t    path);\n+\n+\t\t\tCloseHandle(std->hPipe);\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tstd->next_thread = server_data->thread_list;\n+\t\tserver_data->thread_list = std;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Gently tell all of the ipc_server threads to shutdown.\n+\t * This will be seen the next time they are idle (and waiting\n+\t * for a connection).\n+\t *\n+\t * We DO NOT attempt to force them to drop an active connection.\n+\t */\n+\tSetEvent(server_data->hEventStopRequested);\n+\treturn 0;\n+}\n+\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tDWORD dwWaitResult;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\tdwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE);\n+\tif (dwWaitResult != WAIT_OBJECT_0)\n+\t\treturn error(_(\"wait for hEvent failed for '%s'\"),\n+\t\t\t     server_data->buf_path.buf);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tpthread_join(std->pthread_id, NULL);\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tif (server_data->hEventStopRequested != INVALID_HANDLE_VALUE)\n+\t\tCloseHandle(server_data->hEventStopRequested);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tfree(server_data);\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 198ab1e58f83..76087cff6789 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows)\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \t# USE_NED_ALLOCATOR = YesPlease\n@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \tUSE_NED_ALLOCATOR = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex c151dd7257f3..4bd41054ee70 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n \tlist(APPEND compat_SOURCES unix-socket.c)\n endif()\n \n+if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+endif()\n+\n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\n \n #header checks\ndiff --git a/simple-ipc.h b/simple-ipc.h\nnew file mode 100644\nindex 000000000000..a3f96b42cca2\n--- /dev/null\n+++ b/simple-ipc.h\n@@ -0,0 +1,224 @@\n+#ifndef GIT_SIMPLE_IPC_H\n+#define GIT_SIMPLE_IPC_H\n+\n+/*\n+ * See Documentation/technical/api-simple-ipc.txt\n+ */\n+\n+#if defined(GIT_WINDOWS_NATIVE)\n+#define SUPPORTS_SIMPLE_IPC\n+#endif\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+#include \"pkt-line.h\"\n+\n+/*\n+ * Simple IPC Client Side API.\n+ */\n+\n+enum ipc_active_state {\n+\t/*\n+\t * The pipe/socket exists and the daemon is waiting for connections.\n+\t */\n+\tIPC_STATE__LISTENING = 0,\n+\n+\t/*\n+\t * The pipe/socket exists, but the daemon is not listening.\n+\t * Perhaps it is very busy.\n+\t * Perhaps the daemon died without deleting the path.\n+\t * Perhaps it is shutting down and draining existing clients.\n+\t * Perhaps it is dead, but other clients are lingering and\n+\t * still holding a reference to the pathname.\n+\t */\n+\tIPC_STATE__NOT_LISTENING,\n+\n+\t/*\n+\t * The requested pathname is bogus and no amount of retries\n+\t * will fix that.\n+\t */\n+\tIPC_STATE__INVALID_PATH,\n+\n+\t/*\n+\t * The requested pathname is not found.  This usually means\n+\t * that there is no daemon present.\n+\t */\n+\tIPC_STATE__PATH_NOT_FOUND,\n+\n+\tIPC_STATE__OTHER_ERROR,\n+};\n+\n+struct ipc_client_connect_options {\n+\t/*\n+\t * Spin under timeout if the server is running but can't\n+\t * accept our connection yet.  This should always be set\n+\t * unless you just want to poke the server and see if it\n+\t * is alive.\n+\t */\n+\tunsigned int wait_if_busy:1;\n+\n+\t/*\n+\t * Spin under timeout if the pipe/socket is not yet present\n+\t * on the file system.  This is useful if we just started\n+\t * the service and need to wait for it to become ready.\n+\t */\n+\tunsigned int wait_if_not_found:1;\n+};\n+\n+#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n+\t.wait_if_busy = 0, \\\n+\t.wait_if_not_found = 0, \\\n+}\n+\n+/*\n+ * Determine if a server is listening on this named pipe or socket using\n+ * platform-specific logic.  This might just probe the filesystem or it\n+ * might make a trivial connection to the server using this pathname.\n+ */\n+enum ipc_active_state ipc_get_active_state(const char *path);\n+\n+struct ipc_client_connection {\n+\tint fd;\n+};\n+\n+/*\n+ * Try to connect to the daemon on the named pipe or socket.\n+ *\n+ * Returns IPC_STATE__LISTENING and a connection handle.\n+ *\n+ * Otherwise, returns info to help decide whether to retry or to\n+ * spawn/respawn the server.\n+ */\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection);\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection);\n+\n+/*\n+ * Used by the client to synchronously send and receive a message with\n+ * the server on the provided client connection.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer);\n+\n+/*\n+ * Used by the client to synchronously connect and send and receive a\n+ * message to the server listening at the given path.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer);\n+\n+/*\n+ * Simple IPC Server Side API.\n+ */\n+\n+struct ipc_server_reply_data;\n+\n+typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *,\n+\t\t\t\t  const char *response,\n+\t\t\t\t  size_t response_len);\n+\n+/*\n+ * Prototype for an application-supplied callback to process incoming\n+ * client IPC messages and compose a reply.  The `application_cb` should\n+ * use the provided `reply_cb` and `reply_data` to send an IPC response\n+ * back to the client.  The `reply_cb` callback can be called multiple\n+ * times for chunking purposes.  A reply message is optional and may be\n+ * omitted if not necessary for the application.\n+ *\n+ * The return value from the application callback is ignored.\n+ * The value `SIMPLE_IPC_QUIT` can be used to shutdown the server.\n+ */\n+typedef int (ipc_server_application_cb)(void *application_data,\n+\t\t\t\t\tconst char *request,\n+\t\t\t\t\tipc_server_reply_cb *reply_cb,\n+\t\t\t\t\tstruct ipc_server_reply_data *reply_data);\n+\n+#define SIMPLE_IPC_QUIT -2\n+\n+/*\n+ * Opaque instance data to represent an IPC server instance.\n+ */\n+struct ipc_server_data;\n+\n+/*\n+ * Control parameters for the IPC server instance.\n+ * Use this to hide platform-specific settings.\n+ */\n+struct ipc_server_opts\n+{\n+\tint nr_threads;\n+};\n+\n+/*\n+ * Start an IPC server instance in one or more background threads\n+ * and return a handle to the pool.\n+ *\n+ * Returns 0 if the asynchronous server pool was started successfully.\n+ * Returns -1 if not.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data);\n+\n+/*\n+ * Gently signal the IPC server pool to shutdown.  No new client\n+ * connections will be accepted, but existing connections will be\n+ * allowed to complete.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data);\n+\n+/*\n+ * Block the calling thread until all threads in the IPC server pool\n+ * have completed and been joined.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data);\n+\n+/*\n+ * Close and free all resource handles associated with the IPC server\n+ * pool.\n+ */\n+void ipc_server_free(struct ipc_server_data *server_data);\n+\n+/*\n+ * Run an IPC server instance and block the calling thread of the\n+ * current process.  It does not return until the IPC server has\n+ * either shutdown or had an unrecoverable error.\n+ *\n+ * The IPC server handles incoming IPC messages from client processes\n+ * and may use one or more background threads as necessary.\n+ *\n+ * Returns 0 after the server has completed successfully.\n+ * Returns -1 if the server cannot be started.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ *\n+ * Note that `ipc_server_run()` is a synchronous wrapper around the\n+ * above asynchronous routines.  It effectively hides all of the\n+ * server state and thread details from the caller and presents a\n+ * simple synchronous interface.\n+ */\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data);\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\n+#endif /* GIT_SIMPLE_IPC_H */\n-- \ngitgitgadget\n\n"},{"id":"417233","messageId":"b368318e6a23f8c4e60f77a8b81b558c523d5b03.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 07/12] unix-socket: elimiate static unix_stream_socket() helper function","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:43Z","receivedAt":"2021-02-17T21:50:34Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nThe static helper function `unix_stream_socket()` calls `die()`.  This\nis not appropriate for all callers.  Eliminate the wrapper function\nand make the callers propagate the error.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 27 +++++++++++++--------------\n 1 file changed, 13 insertions(+), 14 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 19ed48be9902..69f81d64e9d5 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,14 +1,6 @@\n #include \"cache.h\"\n #include \"unix-socket.h\"\n \n-static int unix_stream_socket(void)\n-{\n-\tint fd = socket(AF_UNIX, SOCK_STREAM, 0);\n-\tif (fd < 0)\n-\t\tdie_errno(\"unable to create socket\");\n-\treturn fd;\n-}\n-\n static int chdir_len(const char *orig, int len)\n {\n \tchar *path = xmemdupz(orig, len);\n@@ -73,13 +65,16 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \n int unix_stream_connect(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n+\n \tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \tunix_sockaddr_cleanup(&ctx);\n@@ -87,15 +82,16 @@ int unix_stream_connect(const char *path)\n \n fail:\n \tsaved_errno = errno;\n+\tif (fd != -1)\n+\t\tclose(fd);\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n \n int unix_stream_listen(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -103,7 +99,9 @@ int unix_stream_listen(const char *path)\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n \n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n@@ -116,8 +114,9 @@ int unix_stream_listen(const char *path)\n \n fail:\n \tsaved_errno = errno;\n+\tif (fd != -1)\n+\t\tclose(fd);\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n-- \ngitgitgadget\n\n"},{"id":"417234","messageId":"985b2e02b2df7725d70f1365f7cd2e525c9f3ade.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 08/12] unix-socket: add backlog size option to unix_stream_listen()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:44Z","receivedAt":"2021-02-17T21:50:51Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nUpdate `unix_stream_listen()` to take an options structure to override\ndefault behaviors.  This commit includes the size of the `listen()` backlog.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache--daemon.c |  3 ++-\n unix-socket.c                      |  9 +++++++--\n unix-socket.h                      | 14 +++++++++++++-\n 3 files changed, 22 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c\nindex c61f123a3b81..4c6c89ab0de2 100644\n--- a/builtin/credential-cache--daemon.c\n+++ b/builtin/credential-cache--daemon.c\n@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)\n \n static void serve_cache(const char *socket_path, int debug)\n {\n+\tstruct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n \tint fd;\n \n-\tfd = unix_stream_listen(socket_path);\n+\tfd = unix_stream_listen(socket_path, &opts);\n \tif (fd < 0)\n \t\tdie_errno(\"unable to bind to '%s'\", socket_path);\n \ndiff --git a/unix-socket.c b/unix-socket.c\nindex 69f81d64e9d5..5ac7dafe9828 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -89,9 +89,11 @@ int unix_stream_connect(const char *path)\n \treturn -1;\n }\n \n-int unix_stream_listen(const char *path)\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts)\n {\n \tint fd = -1, saved_errno;\n+\tint backlog;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -106,7 +108,10 @@ int unix_stream_listen(const char *path)\n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \n-\tif (listen(fd, 5) < 0)\n+\tbacklog = opts->listen_backlog_size;\n+\tif (backlog <= 0)\n+\t\tbacklog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;\n+\tif (listen(fd, backlog) < 0)\n \t\tgoto fail;\n \n \tunix_sockaddr_cleanup(&ctx);\ndiff --git a/unix-socket.h b/unix-socket.h\nindex e271aeec5a07..06a5a05b03fe 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -1,7 +1,19 @@\n #ifndef UNIX_SOCKET_H\n #define UNIX_SOCKET_H\n \n+struct unix_stream_listen_opts {\n+\tint listen_backlog_size;\n+};\n+\n+#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n+\n+#define UNIX_STREAM_LISTEN_OPTS_INIT \\\n+{ \\\n+\t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n+}\n+\n int unix_stream_connect(const char *path);\n-int unix_stream_listen(const char *path);\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts);\n \n #endif /* UNIX_SOCKET_H */\n-- \ngitgitgadget\n\n"},{"id":"417235","messageId":"1bfa36409d0706d5e22703f80bf95dfa1a313a83.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:45Z","receivedAt":"2021-02-17T21:50:55Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCalls to `chdir()` are dangerous in a multi-threaded context.  If\n`unix_stream_listen()` or `unix_stream_connect()` is given a socket\npathname that is too long to fit in a `sockaddr_un` structure, it will\n`chdir()` to the parent directory of the requested socket pathname,\ncreate the socket using a relative pathname, and then `chdir()` back.\nThis is not thread-safe.\n\nTeach `unix_sockaddr_init()` to not allow calls to `chdir()` when this\nflag is set.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache.c |  2 +-\n unix-socket.c              | 17 ++++++++++++-----\n unix-socket.h              |  4 +++-\n 3 files changed, 16 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/credential-cache.c b/builtin/credential-cache.c\nindex 9b3f70990597..76a6ba37223f 100644\n--- a/builtin/credential-cache.c\n+++ b/builtin/credential-cache.c\n@@ -14,7 +14,7 @@\n static int send_request(const char *socket, const struct strbuf *out)\n {\n \tint got_data = 0;\n-\tint fd = unix_stream_connect(socket);\n+\tint fd = unix_stream_connect(socket, 0);\n \n \tif (fd < 0)\n \t\treturn -1;\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 5ac7dafe9828..1eaa8cf759c0 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -28,16 +28,23 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n }\n \n static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n-\t\t\t      struct unix_sockaddr_context *ctx)\n+\t\t\t      struct unix_sockaddr_context *ctx,\n+\t\t\t      int disallow_chdir)\n {\n \tint size = strlen(path) + 1;\n \n \tctx->orig_dir = NULL;\n \tif (size > sizeof(sa->sun_path)) {\n-\t\tconst char *slash = find_last_dir_sep(path);\n+\t\tconst char *slash;\n \t\tconst char *dir;\n \t\tstruct strbuf cwd = STRBUF_INIT;\n \n+\t\tif (disallow_chdir) {\n+\t\t\terrno = ENAMETOOLONG;\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tslash = find_last_dir_sep(path);\n \t\tif (!slash) {\n \t\t\terrno = ENAMETOOLONG;\n \t\t\treturn -1;\n@@ -63,13 +70,13 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \treturn 0;\n }\n \n-int unix_stream_connect(const char *path)\n+int unix_stream_connect(const char *path, int disallow_chdir)\n {\n \tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\tif (unix_sockaddr_init(&sa, path, &ctx, disallow_chdir) < 0)\n \t\treturn -1;\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\n@@ -99,7 +106,7 @@ int unix_stream_listen(const char *path,\n \n \tunlink(path);\n \n-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\tif (unix_sockaddr_init(&sa, path, &ctx, opts->disallow_chdir) < 0)\n \t\treturn -1;\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\ndiff --git a/unix-socket.h b/unix-socket.h\nindex 06a5a05b03fe..2c0b2e79d7b3 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -3,6 +3,7 @@\n \n struct unix_stream_listen_opts {\n \tint listen_backlog_size;\n+\tunsigned int disallow_chdir:1;\n };\n \n #define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n@@ -10,9 +11,10 @@ struct unix_stream_listen_opts {\n #define UNIX_STREAM_LISTEN_OPTS_INIT \\\n { \\\n \t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n+\t.disallow_chdir = 0, \\\n }\n \n-int unix_stream_connect(const char *path);\n+int unix_stream_connect(const char *path, int disallow_chdir);\n int unix_stream_listen(const char *path,\n \t\t       const struct unix_stream_listen_opts *opts);\n \n-- \ngitgitgadget\n\n"},{"id":"417236","messageId":"b443e11ac32fd3082a59ada42ada8c8973fa0b8a.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 10/12] unix-socket: create `unix_stream_server__listen_with_lock()`","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:46Z","receivedAt":"2021-02-17T21:51:02Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a version of `unix_stream_listen()` that uses a \".lock\" lockfile\nto create the unix domain socket in a race-free manner.\n\nUnix domain sockets have a fundamental problem on Unix systems because\nthey persist in the filesystem until they are deleted.  This is\nindependent of whether a server is actually listening for connections.\nWell-behaved servers are expected to delete the socket when they\nshutdown.  A new server cannot easily tell if a found socket is\nattached to an active server or is leftover cruft from a dead server.\nThe traditional solution used by `unix_stream_listen()` is to force\ndelete the socket pathname and then create a new socket.  This solves\nthe latter (cruft) problem, but in the case of the former, it orphans\nthe existing server (by stealing the pathname associated with the\nsocket it is listening on).\n\nWe cannot directly use a .lock lockfile to create the socket because\nthe socket is created by `bind(2)` rather than the `open(2)` mechanism\nused by `tempfile.c`.\n\nAs an alternative, we hold a plain lockfile (\"<path>.lock\") as a\nmutual exclusion device.  Under the lock, we test if an existing\nsocket (\"<path>\") is has an active server.  If not, create a new\nsocket and begin listening.  Then we rollback the lockfile in all\ncases.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++\n unix-socket.h |  29 +++++++++++++\n 2 files changed, 144 insertions(+)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 1eaa8cf759c0..647bbde37f97 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,4 +1,5 @@\n #include \"cache.h\"\n+#include \"lockfile.h\"\n #include \"unix-socket.h\"\n \n static int chdir_len(const char *orig, int len)\n@@ -132,3 +133,117 @@ int unix_stream_listen(const char *path,\n \terrno = saved_errno;\n \treturn -1;\n }\n+\n+static int is_another_server_alive(const char *path,\n+\t\t\t\t   const struct unix_stream_listen_opts *opts)\n+{\n+\tstruct stat st;\n+\tint fd;\n+\n+\tif (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {\n+\t\t/*\n+\t\t * A socket-inode exists on disk at `path`, but we\n+\t\t * don't know whether it belongs to an active server\n+\t\t * or whether the last server died without cleaning\n+\t\t * up.\n+\t\t *\n+\t\t * Poke it with a trivial connection to try to find\n+\t\t * out.\n+\t\t */\n+\t\tfd = unix_stream_connect(path, opts->disallow_chdir);\n+\t\tif (fd >= 0) {\n+\t\t\tclose(fd);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+struct unix_stream_server_socket *unix_stream_server__listen_with_lock(\n+\tconst char *path,\n+\tconst struct unix_stream_listen_opts *opts)\n+{\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd_socket;\n+\tstruct unix_stream_server_socket *server_socket;\n+\n+\t/*\n+\t * Create a lock at \"<path>.lock\" if we can.\n+\t */\n+\tif (hold_lock_file_for_update_timeout(&lock, path, 0,\n+\t\t\t\t\t      opts->timeout_ms) < 0) {\n+\t\terror_errno(_(\"could not lock listener socket '%s'\"), path);\n+\t\treturn NULL;\n+\t}\n+\n+\t/*\n+\t * If another server is listening on \"<path>\" give up.  We do not\n+\t * want to create a socket and steal future connections from them.\n+\t */\n+\tif (is_another_server_alive(path, opts)) {\n+\t\terrno = EADDRINUSE;\n+\t\terror_errno(_(\"listener socket already in use '%s'\"), path);\n+\t\trollback_lock_file(&lock);\n+\t\treturn NULL;\n+\t}\n+\n+\t/*\n+\t * Create and bind to a Unix domain socket at \"<path>\".\n+\t */\n+\tfd_socket = unix_stream_listen(path, opts);\n+\tif (fd_socket < 0) {\n+\t\terror_errno(_(\"could not create listener socket '%s'\"), path);\n+\t\trollback_lock_file(&lock);\n+\t\treturn NULL;\n+\t}\n+\n+\tserver_socket = xcalloc(1, sizeof(*server_socket));\n+\tserver_socket->path_socket = strdup(path);\n+\tserver_socket->fd_socket = fd_socket;\n+\tlstat(path, &server_socket->st_socket);\n+\n+\t/*\n+\t * Always rollback (just delete) \"<path>.lock\" because we already created\n+\t * \"<path>\" as a socket and do not want to commit_lock to do the atomic\n+\t * rename trick.\n+\t */\n+\trollback_lock_file(&lock);\n+\n+\treturn server_socket;\n+}\n+\n+void unix_stream_server__free(\n+\tstruct unix_stream_server_socket *server_socket)\n+{\n+\tif (!server_socket)\n+\t\treturn;\n+\n+\tif (server_socket->fd_socket >= 0) {\n+\t\tif (!unix_stream_server__was_stolen(server_socket))\n+\t\t\tunlink(server_socket->path_socket);\n+\t\tclose(server_socket->fd_socket);\n+\t}\n+\n+\tfree(server_socket->path_socket);\n+\tfree(server_socket);\n+}\n+\n+int unix_stream_server__was_stolen(\n+\tstruct unix_stream_server_socket *server_socket)\n+{\n+\tstruct stat st_now;\n+\n+\tif (!server_socket)\n+\t\treturn 0;\n+\n+\tif (lstat(server_socket->path_socket, &st_now) == -1)\n+\t\treturn 1;\n+\n+\tif (st_now.st_ino != server_socket->st_socket.st_ino)\n+\t\treturn 1;\n+\n+\t/* We might also consider the ctime on some platforms. */\n+\n+\treturn 0;\n+}\ndiff --git a/unix-socket.h b/unix-socket.h\nindex 2c0b2e79d7b3..8faf5b692f90 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -2,14 +2,17 @@\n #define UNIX_SOCKET_H\n \n struct unix_stream_listen_opts {\n+\tlong timeout_ms;\n \tint listen_backlog_size;\n \tunsigned int disallow_chdir:1;\n };\n \n+#define DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT (100)\n #define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n \n #define UNIX_STREAM_LISTEN_OPTS_INIT \\\n { \\\n+\t.timeout_ms = DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT, \\\n \t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n \t.disallow_chdir = 0, \\\n }\n@@ -18,4 +21,30 @@ int unix_stream_connect(const char *path, int disallow_chdir);\n int unix_stream_listen(const char *path,\n \t\t       const struct unix_stream_listen_opts *opts);\n \n+struct unix_stream_server_socket {\n+\tchar *path_socket;\n+\tstruct stat st_socket;\n+\tint fd_socket;\n+};\n+\n+/*\n+ * Create a Unix Domain Socket at the given path under the protection\n+ * of a '.lock' lockfile.\n+ */\n+struct unix_stream_server_socket *unix_stream_server__listen_with_lock(\n+\tconst char *path,\n+\tconst struct unix_stream_listen_opts *opts);\n+\n+/*\n+ * Close and delete the socket.\n+ */\n+void unix_stream_server__free(\n+\tstruct unix_stream_server_socket *server_socket);\n+\n+/*\n+ * Return 1 if the inode of the pathname to our socket changes.\n+ */\n+int unix_stream_server__was_stolen(\n+\tstruct unix_stream_server_socket *server_socket);\n+\n #endif /* UNIX_SOCKET_H */\n-- \ngitgitgadget\n\n"},{"id":"417237","messageId":"09568a6500dde4a592a994b661a7beec23af32b4.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:48Z","receivedAt":"2021-02-17T21:51:10Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate t0052-simple-ipc.sh with unit tests for the \"simple-ipc\" mechanism.\n\nCreate t/helper/test-simple-ipc test tool to exercise the \"simple-ipc\"\nfunctions.\n\nWhen the tool is invoked with \"run-daemon\", it runs a server to listen\nfor \"simple-ipc\" connections on a test socket or named pipe and\nresponds to a set of commands to exercise/stress the communication\nsetup.\n\nWhen the tool is invoked with \"start-daemon\", it spawns a \"run-daemon\"\ncommand in the background and waits for the server to become ready\nbefore exiting.  (This helps make unit tests in t0052 more predictable\nand avoids the need for arbitrary sleeps in the test script.)\n\nThe tool also has a series of client \"send\" commands to send commands\nand data to a server instance.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                   |   1 +\n t/helper/test-simple-ipc.c | 773 +++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0052-simple-ipc.sh      | 122 ++++++\n 5 files changed, 898 insertions(+)\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\ndiff --git a/Makefile b/Makefile\nindex 08a4c88b92f5..93f2e7ca9e1f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -740,6 +740,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o\n TEST_BUILTINS_OBJS += test-sha1.o\n TEST_BUILTINS_OBJS += test-sha256.o\n TEST_BUILTINS_OBJS += test-sigchain.o\n+TEST_BUILTINS_OBJS += test-simple-ipc.o\n TEST_BUILTINS_OBJS += test-strcmp-offset.o\n TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\ndiff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c\nnew file mode 100644\nindex 000000000000..d67eaa9a6ecc\n--- /dev/null\n+++ b/t/helper/test-simple-ipc.c\n@@ -0,0 +1,773 @@\n+/*\n+ * test-simple-ipc.c: verify that the Inter-Process Communication works.\n+ */\n+\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"simple-ipc.h\"\n+#include \"parse-options.h\"\n+#include \"thread-utils.h\"\n+#include \"strvec.h\"\n+\n+#ifndef SUPPORTS_SIMPLE_IPC\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tdie(\"simple IPC not available on this platform\");\n+}\n+#else\n+\n+/*\n+ * The test daemon defines an \"application callback\" that supports a\n+ * series of commands (see `test_app_cb()`).\n+ *\n+ * Unknown commands are caught here and we send an error message back\n+ * to the client process.\n+ */\n+static int app__unhandled_command(const char *command,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n+\n+\tstrbuf_addf(&buf, \"unhandled command: %s\", command);\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a single very large buffer.  This is to ensure that\n+ * long response are properly handled -- whether the chunking occurs\n+ * in the kernel or in the (probably pkt-line) layer.\n+ */\n+#define BIG_ROWS (10000)\n+static int app__big_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t    struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < BIG_ROWS; row++)\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a series of lines.  This is to ensure that we can incrementally\n+ * compute the response and chunk it to the client.\n+ */\n+#define CHUNK_ROWS (10000)\n+static int app__chunk_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t      struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < CHUNK_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Slowly reply with a series of lines.  This is to model an expensive to\n+ * compute chunked response (which might happen if this callback is running\n+ * in a thread and is fighting for a lock with other threads).\n+ */\n+#define SLOW_ROWS     (1000)\n+#define SLOW_DELAY_MS (10)\n+static int app__slow_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t     struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < SLOW_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t\tsleep_millisec(SLOW_DELAY_MS);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * The client sent a command followed by a (possibly very) large buffer.\n+ */\n+static int app__sendbytes_command(const char *received,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\tconst char *p = \"?\";\n+\tint len_ballast = 0;\n+\tint k;\n+\tint errs = 0;\n+\tint ret;\n+\n+\tif (skip_prefix(received, \"sendbytes \", &p))\n+\t\tlen_ballast = strlen(p);\n+\n+\t/*\n+\t * Verify that the ballast is n copies of a single letter.\n+\t * And that the multi-threaded IO layer didn't cross the streams.\n+\t */\n+\tfor (k = 1; k < len_ballast; k++)\n+\t\tif (p[k] != p[0])\n+\t\t\terrs++;\n+\n+\tif (errs)\n+\t\tstrbuf_addf(&buf_resp, \"errs:%d\\n\", errs);\n+\telse\n+\t\tstrbuf_addf(&buf_resp, \"rcvd:%c%08d\\n\", p[0], len_ballast);\n+\n+\tret = reply_cb(reply_data, buf_resp.buf, buf_resp.len);\n+\n+\tstrbuf_release(&buf_resp);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * An arbitrary fixed address to verify that the application instance\n+ * data is handled properly.\n+ */\n+static int my_app_data = 42;\n+\n+static ipc_server_application_cb test_app_cb;\n+\n+/*\n+ * This is the \"application callback\" that sits on top of the\n+ * \"ipc-server\".  It completely defines the set of commands supported\n+ * by this application.\n+ */\n+static int test_app_cb(void *application_data,\n+\t\t       const char *command,\n+\t\t       ipc_server_reply_cb *reply_cb,\n+\t\t       struct ipc_server_reply_data *reply_data)\n+{\n+\t/*\n+\t * Verify that we received the application-data that we passed\n+\t * when we started the ipc-server.  (We have several layers of\n+\t * callbacks calling callbacks and it's easy to get things mixed\n+\t * up (especially when some are \"void*\").)\n+\t */\n+\tif (application_data != (void*)&my_app_data)\n+\t\tBUG(\"application_cb: application_data pointer wrong\");\n+\n+\tif (!strcmp(command, \"quit\")) {\n+\t\t/*\n+\t\t * The client sent a \"quit\" command.  This is an async\n+\t\t * request for the server to shutdown.\n+\t\t *\n+\t\t * We DO NOT send the client a response message\n+\t\t * (because we have nothing to say and the other\n+\t\t * server threads have not yet stopped).\n+\t\t *\n+\t\t * Tell the ipc-server layer to start shutting down.\n+\t\t * This includes: stop listening for new connections\n+\t\t * on the socket/pipe and telling all worker threads\n+\t\t * to finish/drain their outgoing responses to other\n+\t\t * clients.\n+\t\t *\n+\t\t * This DOES NOT force an immediate sync shutdown.\n+\t\t */\n+\t\treturn SIMPLE_IPC_QUIT;\n+\t}\n+\n+\tif (!strcmp(command, \"ping\")) {\n+\t\tconst char *answer = \"pong\";\n+\t\treturn reply_cb(reply_data, answer, strlen(answer));\n+\t}\n+\n+\tif (!strcmp(command, \"big\"))\n+\t\treturn app__big_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"chunk\"))\n+\t\treturn app__chunk_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"slow\"))\n+\t\treturn app__slow_command(reply_cb, reply_data);\n+\n+\tif (starts_with(command, \"sendbytes \"))\n+\t\treturn app__sendbytes_command(command, reply_cb, reply_data);\n+\n+\treturn app__unhandled_command(command, reply_cb, reply_data);\n+}\n+\n+/*\n+ * This process will run as a simple-ipc server and listen for IPC commands\n+ * from client processes.\n+ */\n+static int daemon__run_server(const char *path, int argc, const char **argv)\n+{\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = 5\n+\t};\n+\n+\tconst char * const daemon_usage[] = {\n+\t\tN_(\"test-helper simple-ipc run-daemon [<options>\"),\n+\t\tNULL\n+\t};\n+\tstruct option daemon_options[] = {\n+\t\tOPT_INTEGER(0, \"threads\", &opts.nr_threads,\n+\t\t\t    N_(\"number of threads in server thread pool\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);\n+\n+\tif (opts.nr_threads < 1)\n+\t\topts.nr_threads = 1;\n+\n+\t/*\n+\t * Synchronously run the ipc-server.  We don't need any application\n+\t * instance data, so pass an arbitrary pointer (that we'll later\n+\t * verify made the round trip).\n+\t */\n+\treturn ipc_server_run(path, &opts, test_app_cb, (void*)&my_app_data);\n+}\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+/*\n+ * This is adapted from `daemonize()`.  Use `fork()` to directly create and\n+ * run the daemon in a child process.\n+ */\n+static int spawn_server(const char *path,\n+\t\t\tconst struct ipc_server_opts *opts,\n+\t\t\tpid_t *pid)\n+{\n+\t*pid = fork();\n+\n+\tswitch (*pid) {\n+\tcase 0:\n+\t\tif (setsid() == -1)\n+\t\t\terror_errno(_(\"setsid failed\"));\n+\t\tclose(0);\n+\t\tclose(1);\n+\t\tclose(2);\n+\t\tsanitize_stdfds();\n+\n+\t\treturn ipc_server_run(path, opts, test_app_cb, (void*)&my_app_data);\n+\n+\tcase -1:\n+\t\treturn error_errno(_(\"could not spawn daemon in the background\"));\n+\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+}\n+#else\n+/*\n+ * Conceptually like `daemonize()` but different because Windows does not\n+ * have `fork(2)`.  Spawn a normal Windows child process but without the\n+ * limitations of `start_command()` and `finish_command()`.\n+ */\n+static int spawn_server(const char *path,\n+\t\t\tconst struct ipc_server_opts *opts,\n+\t\t\tpid_t *pid)\n+{\n+\tchar test_tool_exe[MAX_PATH];\n+\tstruct strvec args = STRVEC_INIT;\n+\tint in, out;\n+\n+\tGetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);\n+\n+\tin = open(\"/dev/null\", O_RDONLY);\n+\tout = open(\"/dev/null\", O_WRONLY);\n+\n+\tstrvec_push(&args, test_tool_exe);\n+\tstrvec_push(&args, \"simple-ipc\");\n+\tstrvec_push(&args, \"run-daemon\");\n+\tstrvec_pushf(&args, \"--threads=%d\", opts->nr_threads);\n+\n+\t*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);\n+\tclose(in);\n+\tclose(out);\n+\n+\tstrvec_clear(&args);\n+\n+\tif (*pid < 0)\n+\t\treturn error(_(\"could not spawn daemon in the background\"));\n+\n+\treturn 0;\n+}\n+#endif\n+\n+/*\n+ * This is adapted from `wait_or_whine()`.  Watch the child process and\n+ * let it get started and begin listening for requests on the socket\n+ * before reporting our success.\n+ */\n+static int wait_for_server_startup(const char * path, pid_t pid_child,\n+\t\t\t\t   int max_wait_sec)\n+{\n+\tint status;\n+\tpid_t pid_seen;\n+\tenum ipc_active_state s;\n+\ttime_t time_limit, now;\n+\n+\ttime(&time_limit);\n+\ttime_limit += max_wait_sec;\n+\n+\tfor (;;) {\n+\t\tpid_seen = waitpid(pid_child, &status, WNOHANG);\n+\n+\t\tif (pid_seen == -1)\n+\t\t\treturn error_errno(_(\"waitpid failed\"));\n+\n+\t\telse if (pid_seen == 0) {\n+\t\t\t/*\n+\t\t\t * The child is still running (this should be\n+\t\t\t * the normal case).  Try to connect to it on\n+\t\t\t * the socket and see if it is ready for\n+\t\t\t * business.\n+\t\t\t *\n+\t\t\t * If there is another daemon already running,\n+\t\t\t * our child will fail to start (possibly\n+\t\t\t * after a timeout on the lock), but we don't\n+\t\t\t * care (who responds) if the socket is live.\n+\t\t\t */\n+\t\t\ts = ipc_get_active_state(path);\n+\t\t\tif (s == IPC_STATE__LISTENING)\n+\t\t\t\treturn 0;\n+\n+\t\t\ttime(&now);\n+\t\t\tif (now > time_limit)\n+\t\t\t\treturn error(_(\"daemon not online yet\"));\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\telse if (pid_seen == pid_child) {\n+\t\t\t/*\n+\t\t\t * The new child daemon process shutdown while\n+\t\t\t * it was starting up, so it is not listening\n+\t\t\t * on the socket.\n+\t\t\t *\n+\t\t\t * Try to ping the socket in the odd chance\n+\t\t\t * that another daemon started (or was already\n+\t\t\t * running) while our child was starting.\n+\t\t\t *\n+\t\t\t * Again, we don't care who services the socket.\n+\t\t\t */\n+\t\t\ts = ipc_get_active_state(path);\n+\t\t\tif (s == IPC_STATE__LISTENING)\n+\t\t\t\treturn 0;\n+\n+\t\t\t/*\n+\t\t\t * We don't care about the WEXITSTATUS() nor\n+\t\t\t * any of the WIF*(status) values because\n+\t\t\t * `cmd__simple_ipc()` does the `!!result`\n+\t\t\t * trick on all function return values.\n+\t\t\t *\n+\t\t\t * So it is sufficient to just report the\n+\t\t\t * early shutdown as an error.\n+\t\t\t */\n+\t\t\treturn error(_(\"daemon failed to start\"));\n+\t\t}\n+\n+\t\telse\n+\t\t\treturn error(_(\"waitpid is confused\"));\n+\t}\n+}\n+\n+/*\n+ * This process will start a simple-ipc server in a background process and\n+ * wait for it to become ready.  This is like `daemonize()` but gives us\n+ * more control and better error reporting (and makes it easier to write\n+ * unit tests).\n+ */\n+static int daemon__start_server(const char *path, int argc, const char **argv)\n+{\n+\tpid_t pid_child;\n+\tint ret;\n+\tint max_wait_sec = 60;\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = 5\n+\t};\n+\n+\tconst char * const daemon_usage[] = {\n+\t\tN_(\"test-helper simple-ipc start-daemon [<options>\"),\n+\t\tNULL\n+\t};\n+\n+\tstruct option daemon_options[] = {\n+\t\tOPT_INTEGER(0, \"max-wait\", &max_wait_sec,\n+\t\t\t    N_(\"seconds to wait for daemon to startup\")),\n+\t\tOPT_INTEGER(0, \"threads\", &opts.nr_threads,\n+\t\t\t    N_(\"number of threads in server thread pool\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);\n+\n+\tif (max_wait_sec < 0)\n+\t\tmax_wait_sec = 0;\n+\tif (opts.nr_threads < 1)\n+\t\topts.nr_threads = 1;\n+\n+\t/*\n+\t * Run the actual daemon in a background process.\n+\t */\n+\tret = spawn_server(path, &opts, &pid_child);\n+\tif (pid_child <= 0)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Let the parent wait for the child process to get started\n+\t * and begin listening for requests on the socket.\n+\t */\n+\tret = wait_for_server_startup(path, pid_child, max_wait_sec);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * This process will run a quick probe to see if a simple-ipc server\n+ * is active on this path.\n+ *\n+ * Returns 0 if the server is alive.\n+ */\n+static int client__probe_server(const char *path)\n+{\n+\tenum ipc_active_state s;\n+\n+\ts = ipc_get_active_state(path);\n+\tswitch (s) {\n+\tcase IPC_STATE__LISTENING:\n+\t\treturn 0;\n+\n+\tcase IPC_STATE__NOT_LISTENING:\n+\t\treturn error(\"no server listening at '%s'\", path);\n+\n+\tcase IPC_STATE__PATH_NOT_FOUND:\n+\t\treturn error(\"path not found '%s'\", path);\n+\n+\tcase IPC_STATE__INVALID_PATH:\n+\t\treturn error(\"invalid pipe/socket name '%s'\", path);\n+\n+\tcase IPC_STATE__OTHER_ERROR:\n+\tdefault:\n+\t\treturn error(\"other error for '%s'\", path);\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command to an already-running server daemon and print the\n+ * response.\n+ *\n+ * argv[2] contains a simple (1 word) command that `test_app_cb()` (in\n+ * the daemon process) will understand.\n+ */\n+static int client__send_ipc(int argc, const char **argv, const char *path)\n+{\n+\tconst char *command = argc > 2 ? argv[2] : \"(no command)\";\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (!ipc_client_send_command(path, &options, command, &buf)) {\n+\t\tif (buf.len) {\n+\t\t\tprintf(\"%s\\n\", buf.buf);\n+\t\t\tfflush(stdout);\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"failed to send '%s' to '%s'\", command, path);\n+}\n+\n+/*\n+ * Send an IPC command to an already-running server and ask it to\n+ * shutdown.  \"send quit\" is an async request and queues a shutdown\n+ * event in the server, so we spin and wait here for it to actually\n+ * shutdown to make the unit tests a little easier to write.\n+ */\n+static int client__stop_server(int argc, const char **argv, const char *path)\n+{\n+\tconst char *send_quit[] = { argv[0], \"send\", \"quit\", NULL };\n+\tint max_wait_sec = 60;\n+\tint ret;\n+\ttime_t time_limit, now;\n+\tenum ipc_active_state s;\n+\n+\tconst char * const stop_usage[] = {\n+\t\tN_(\"test-helper simple-ipc stop-daemon [<options>]\"),\n+\t\tNULL\n+\t};\n+\n+\tstruct option stop_options[] = {\n+\t\tOPT_INTEGER(0, \"max-wait\", &max_wait_sec,\n+\t\t\t    N_(\"seconds to wait for daemon to stop\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, stop_options, stop_usage, 0);\n+\n+\tif (max_wait_sec < 0)\n+\t\tmax_wait_sec = 0;\n+\n+\ttime(&time_limit);\n+\ttime_limit += max_wait_sec;\n+\n+\tret = client__send_ipc(3, send_quit, path);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tfor (;;) {\n+\t\tsleep_millisec(100);\n+\n+\t\ts = ipc_get_active_state(path);\n+\n+\t\tif (s != IPC_STATE__LISTENING) {\n+\t\t\t/*\n+\t\t\t * The socket/pipe is gone and/or has stopped\n+\t\t\t * responding.  Lets assume that the daemon\n+\t\t\t * process has exited too.\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\ttime(&now);\n+\t\tif (now > time_limit)\n+\t\t\treturn error(_(\"daemon has not shutdown yet\"));\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command followed by ballast to confirm that a large\n+ * message can be sent and that the kernel or pkt-line layers will\n+ * properly chunk it and that the daemon receives the entire message.\n+ */\n+static int do_sendbytes(int bytecount, char byte, const char *path,\n+\t\t\tconst struct ipc_client_connect_options *options)\n+{\n+\tstruct strbuf buf_send = STRBUF_INIT;\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_send, \"sendbytes \");\n+\tstrbuf_addchars(&buf_send, byte, bytecount);\n+\n+\tif (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {\n+\t\tstrbuf_rtrim(&buf_resp);\n+\t\tprintf(\"sent:%c%08d %s\\n\", byte, bytecount, buf_resp.buf);\n+\t\tfflush(stdout);\n+\t\tstrbuf_release(&buf_send);\n+\t\tstrbuf_release(&buf_resp);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"client failed to sendbytes(%d, '%c') to '%s'\",\n+\t\t     bytecount, byte, path);\n+}\n+\n+/*\n+ * Send an IPC command with ballast to an already-running server daemon.\n+ */\n+static int client__sendbytes(int argc, const char **argv, const char *path)\n+{\n+\tint bytecount = 1024;\n+\tchar *string = \"x\";\n+\tconst char * const sendbytes_usage[] = {\n+\t\tN_(\"test-helper simple-ipc sendbytes [<options>]\"),\n+\t\tNULL\n+\t};\n+\tstruct option sendbytes_options[] = {\n+\t\tOPT_INTEGER(0, \"bytecount\", &bytecount, N_(\"number of bytes\")),\n+\t\tOPT_STRING(0, \"byte\", &string, N_(\"byte\"), N_(\"ballast\")),\n+\t\tOPT_END()\n+\t};\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\toptions.uds_disallow_chdir = 0;\n+\n+\targc = parse_options(argc, argv, NULL, sendbytes_options, sendbytes_usage, 0);\n+\n+\treturn do_sendbytes(bytecount, string[0], path, &options);\n+}\n+\n+struct multiple_thread_data {\n+\tpthread_t pthread_id;\n+\tstruct multiple_thread_data *next;\n+\tconst char *path;\n+\tint bytecount;\n+\tint batchsize;\n+\tint sum_errors;\n+\tint sum_good;\n+\tchar letter;\n+};\n+\n+static void *multiple_thread_proc(void *_multiple_thread_data)\n+{\n+\tstruct multiple_thread_data *d = _multiple_thread_data;\n+\tint k;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\t/*\n+\t * A multi-threaded client should not be randomly calling chdir().\n+\t * The test will pass without this restriction because the test is\n+\t * not otherwise accessing the filesystem, but it makes us honest.\n+\t */\n+\toptions.uds_disallow_chdir = 1;\n+\n+\ttrace2_thread_start(\"multiple\");\n+\n+\tfor (k = 0; k < d->batchsize; k++) {\n+\t\tif (do_sendbytes(d->bytecount + k, d->letter, d->path, &options))\n+\t\t\td->sum_errors++;\n+\t\telse\n+\t\t\td->sum_good++;\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * Start a client-side thread pool.  Each thread sends a series of\n+ * IPC requests.  Each request is on a new connection to the server.\n+ */\n+static int client__multiple(int argc, const char **argv, const char *path)\n+{\n+\tstruct multiple_thread_data *list = NULL;\n+\tint k;\n+\tint nr_threads = 5;\n+\tint bytecount = 1;\n+\tint batchsize = 10;\n+\tint sum_join_errors = 0;\n+\tint sum_thread_errors = 0;\n+\tint sum_good = 0;\n+\n+\tconst char * const multiple_usage[] = {\n+\t\tN_(\"test-helper simple-ipc multiple [<options>]\"),\n+\t\tNULL\n+\t};\n+\tstruct option multiple_options[] = {\n+\t\tOPT_INTEGER(0, \"bytecount\", &bytecount, N_(\"number of bytes\")),\n+\t\tOPT_INTEGER(0, \"threads\", &nr_threads, N_(\"number of threads\")),\n+\t\tOPT_INTEGER(0, \"batchsize\", &batchsize, N_(\"number of requests per thread\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, multiple_options, multiple_usage, 0);\n+\n+\tif (bytecount < 1)\n+\t\tbytecount = 1;\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\tif (batchsize < 1)\n+\t\tbatchsize = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct multiple_thread_data *d = xcalloc(1, sizeof(*d));\n+\t\td->next = list;\n+\t\td->path = path;\n+\t\td->bytecount = bytecount + batchsize*(k/26);\n+\t\td->batchsize = batchsize;\n+\t\td->sum_errors = 0;\n+\t\td->sum_good = 0;\n+\t\td->letter = 'A' + (k % 26);\n+\n+\t\tif (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) {\n+\t\t\twarning(\"failed to create thread[%d] skipping remainder\", k);\n+\t\t\tfree(d);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tlist = d;\n+\t}\n+\n+\twhile (list) {\n+\t\tstruct multiple_thread_data *d = list;\n+\n+\t\tif (pthread_join(d->pthread_id, NULL))\n+\t\t\tsum_join_errors++;\n+\n+\t\tsum_thread_errors += d->sum_errors;\n+\t\tsum_good += d->sum_good;\n+\n+\t\tlist = d->next;\n+\t\tfree(d);\n+\t}\n+\n+\tprintf(\"client (good %d) (join %d), (errors %d)\\n\",\n+\t       sum_good, sum_join_errors, sum_thread_errors);\n+\n+\treturn (sum_join_errors + sum_thread_errors) ? 1 : 0;\n+}\n+\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tconst char *path = \"ipc-test\";\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"SUPPORTS_SIMPLE_IPC\"))\n+\t\treturn 0;\n+\n+\t/*\n+\t * Use '!!' on all dispatch functions to map from `error()` style\n+\t * (returns -1) style to `test_must_fail` style (expects 1).  This\n+\t * makes shell error messages less confusing.\n+\t */\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"is-active\"))\n+\t\treturn !!client__probe_server(path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"run-daemon\"))\n+\t\treturn !!daemon__run_server(path, argc, argv);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"start-daemon\"))\n+\t\treturn !!daemon__start_server(path, argc, argv);\n+\n+\t/*\n+\t * Client commands follow.  Ensure a server is running before\n+\t * going any further.\n+\t */\n+\tif (client__probe_server(path))\n+\t\treturn 1;\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"stop-daemon\"))\n+\t\treturn !!client__stop_server(argc, argv, path);\n+\n+\tif ((argc == 2 || argc == 3) && !strcmp(argv[1], \"send\"))\n+\t\treturn !!client__send_ipc(argc, argv, path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"sendbytes\"))\n+\t\treturn !!client__sendbytes(argc, argv, path);\n+\n+\tif (argc >= 2 && !strcmp(argv[1], \"multiple\"))\n+\t\treturn !!client__multiple(argc, argv, path);\n+\n+\tdie(\"Unhandled argv[1]: '%s'\", argv[1]);\n+}\n+#endif\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 9d6d14d92937..a409655f03b5 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -64,6 +64,7 @@ static struct test_cmd cmds[] = {\n \t{ \"sha1\", cmd__sha1 },\n \t{ \"sha256\", cmd__sha256 },\n \t{ \"sigchain\", cmd__sigchain },\n+\t{ \"simple-ipc\", cmd__simple_ipc },\n \t{ \"strcmp-offset\", cmd__strcmp_offset },\n \t{ \"string-list\", cmd__string_list },\n \t{ \"submodule-config\", cmd__submodule_config },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex a6470ff62c42..564eb3c8e911 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -54,6 +54,7 @@ int cmd__sha1(int argc, const char **argv);\n int cmd__oid_array(int argc, const char **argv);\n int cmd__sha256(int argc, const char **argv);\n int cmd__sigchain(int argc, const char **argv);\n+int cmd__simple_ipc(int argc, const char **argv);\n int cmd__strcmp_offset(int argc, const char **argv);\n int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\ndiff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\nnew file mode 100755\nindex 000000000000..18dcc8130728\n--- /dev/null\n+++ b/t/t0052-simple-ipc.sh\n@@ -0,0 +1,122 @@\n+#!/bin/sh\n+\n+test_description='simple command server'\n+\n+. ./test-lib.sh\n+\n+test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {\n+\tskip_all='simple IPC not supported on this platform'\n+\ttest_done\n+}\n+\n+stop_simple_IPC_server () {\n+\ttest-tool simple-ipc stop-daemon\n+}\n+\n+test_expect_success 'start simple command server' '\n+\ttest_atexit stop_simple_IPC_server &&\n+\ttest-tool simple-ipc start-daemon --threads=8 &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'simple command server' '\n+\ttest-tool simple-ipc send ping >actual &&\n+\techo pong >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'servers cannot share the same path' '\n+\ttest_must_fail test-tool simple-ipc run-daemon &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'big response' '\n+\ttest-tool simple-ipc send big >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'chunk response' '\n+\ttest-tool simple-ipc send chunk >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'slow response' '\n+\ttest-tool simple-ipc send slow >actual &&\n+\ttest_line_count -ge 100 actual &&\n+\tgrep -q \"big: [0]*99\\$\" actual\n+'\n+\n+# Send an IPC with n=100,000 bytes of ballast.  This should be large enough\n+# to force both the kernel and the pkt-line layer to chunk the message to the\n+# daemon and for the daemon to receive it in chunks.\n+#\n+test_expect_success 'sendbytes' '\n+\ttest-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual &&\n+\tgrep \"sent:A00100000 rcvd:A00100000\" actual\n+'\n+\n+# Start a series of <threads> client threads that each make <batchsize>\n+# IPC requests to the server.  Each (<threads> * <batchsize>) request\n+# will open a new connection to the server and randomly bind to a server\n+# thread.  Each client thread exits after completing its batch.  So the\n+# total number of live client threads will be smaller than the total.\n+# Each request will send a message containing at least <bytecount> bytes\n+# of ballast.  (Responses are small.)\n+#\n+# The purpose here is to test threading in the server and responding to\n+# many concurrent client requests (regardless of whether they come from\n+# 1 client process or many).  And to test that the server side of the\n+# named pipe/socket is stable.  (On Windows this means that the server\n+# pipe is properly recycled.)\n+#\n+# On Windows it also lets us adjust the connection timeout in the\n+# `ipc_client_send_command()`.\n+#\n+# Note it is easy to drive the system into failure by requesting an\n+# insane number of threads on client or server and/or increasing the\n+# per-thread batchsize or the per-request bytecount (ballast).\n+# On Windows these failures look like \"pipe is busy\" errors.\n+# So I've chosen fairly conservative values for now.\n+#\n+# We expect output of the form \"sent:<letter><length> ...\"\n+# With terms (7, 19, 13) we expect:\n+#   <letter> in [A-G]\n+#   <length> in [19+0 .. 19+(13-1)]\n+# and (7 * 13) successful responses.\n+#\n+test_expect_success 'stress test threads' '\n+\ttest-tool simple-ipc multiple \\\n+\t\t--threads=7 \\\n+\t\t--bytecount=19 \\\n+\t\t--batchsize=13 \\\n+\t\t>actual &&\n+\ttest_line_count = 92 actual &&\n+\tgrep \"good 91\" actual &&\n+\tgrep \"sent:A\" <actual >actual_a &&\n+\tcat >expect_a <<-EOF &&\n+\t\tsent:A00000019 rcvd:A00000019\n+\t\tsent:A00000020 rcvd:A00000020\n+\t\tsent:A00000021 rcvd:A00000021\n+\t\tsent:A00000022 rcvd:A00000022\n+\t\tsent:A00000023 rcvd:A00000023\n+\t\tsent:A00000024 rcvd:A00000024\n+\t\tsent:A00000025 rcvd:A00000025\n+\t\tsent:A00000026 rcvd:A00000026\n+\t\tsent:A00000027 rcvd:A00000027\n+\t\tsent:A00000028 rcvd:A00000028\n+\t\tsent:A00000029 rcvd:A00000029\n+\t\tsent:A00000030 rcvd:A00000030\n+\t\tsent:A00000031 rcvd:A00000031\n+\tEOF\n+\ttest_cmp expect_a actual_a\n+'\n+\n+test_expect_success 'stop-daemon works' '\n+\ttest-tool simple-ipc stop-daemon &&\n+\ttest_must_fail test-tool simple-ipc is-active &&\n+\ttest_must_fail test-tool simple-ipc send ping\n+'\n+\n+test_done\n-- \ngitgitgadget\n"},{"id":"417238","messageId":"43c8db9a4468c0ca50e8f4efa55ab01a77cafcf6.1613598529.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v4 11/12] simple-ipc: add Unix domain socket implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-17T21:48:47Z","receivedAt":"2021-02-17T21:51:30Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Unix domain socket based implementation of \"simple-ipc\".\n\nA set of `ipc_client` routines implement a client library to connect\nto an `ipc_server` over a Unix domain socket, send a simple request,\nand receive a single response.  Clients use blocking IO on the socket.\n\nA set of `ipc_server` routines implement a thread pool to listen for\nand concurrently service client connections.\n\nThe server creates a new Unix domain socket at a known location.  If a\nsocket already exists with that name, the server tries to determine if\nanother server is already listening on the socket or if the socket is\ndead.  If socket is busy, the server exits with an error rather than\nstealing the socket.  If the socket is dead, the server creates a new\none and starts up.\n\nIf while running, the server detects that its socket has been stolen\nby another server, it automatically exits.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   2 +\n compat/simple-ipc/ipc-unix-socket.c | 979 ++++++++++++++++++++++++++++\n contrib/buildsystems/CMakeLists.txt |   2 +\n simple-ipc.h                        |  13 +-\n 4 files changed, 995 insertions(+), 1 deletion(-)\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n\ndiff --git a/Makefile b/Makefile\nindex 40d5cab78d3f..08a4c88b92f5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1677,6 +1677,8 @@ ifdef NO_UNIX_SOCKETS\n \tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n else\n \tLIB_OBJS += unix-socket.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-unix-socket.o\n endif\n \n ifdef USE_WIN32_IPC\ndiff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c\nnew file mode 100644\nindex 000000000000..b7fd0b34329e\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-unix-socket.c\n@@ -0,0 +1,979 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+#include \"unix-socket.h\"\n+\n+#ifdef NO_UNIX_SOCKETS\n+#error compat/simple-ipc/ipc-unix-socket.c requires Unix sockets\n+#endif\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\tstruct stat st;\n+\tstruct ipc_client_connection *connection_test = NULL;\n+\n+\toptions.wait_if_busy = 0;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (lstat(path, &st) == -1) {\n+\t\tswitch (errno) {\n+\t\tcase ENOENT:\n+\t\tcase ENOTDIR:\n+\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__INVALID_PATH;\n+\t\t}\n+\t}\n+\n+\t/* also complain if a plain file is in the way */\n+\tif ((st.st_mode & S_IFMT) != S_IFSOCK)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\t/*\n+\t * Just because the filesystem has a S_IFSOCK type inode\n+\t * at `path`, doesn't mean it that there is a server listening.\n+\t * Ping it to be sure.\n+\t */\n+\tstate = ipc_client_try_connect(path, &options, &connection_test);\n+\tipc_client_close_connection(connection_test);\n+\n+\treturn state;\n+}\n+\n+/*\n+ * This value was chosen at random.\n+ */\n+#define WAIT_STEP_MS (50)\n+\n+/*\n+ * Try to connect to the server.  If the server is just starting up or\n+ * is very busy, we may not get a connection the first time.\n+ */\n+static enum ipc_active_state connect_to_server(\n+\tconst char *path,\n+\tint timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tint wait_ms = 50;\n+\tint k;\n+\n+\t*pfd = -1;\n+\n+\tfor (k = 0; k < timeout_ms; k += wait_ms) {\n+\t\tint fd = unix_stream_connect(path, options->uds_disallow_chdir);\n+\n+\t\tif (fd != -1) {\n+\t\t\t*pfd = fd;\n+\t\t\treturn IPC_STATE__LISTENING;\n+\t\t}\n+\n+\t\tif (errno == ENOENT) {\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ETIMEDOUT) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ECONNREFUSED) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\n+\tsleep_and_try_again:\n+\t\tsleep_millisec(wait_ms);\n+\t}\n+\n+\treturn IPC_STATE__NOT_LISTENING;\n+}\n+\n+/*\n+ * A randomly chosen timeout value.\n+ */\n+#define MY_CONNECTION_TIMEOUT_MS (1000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tstate = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n+\t\t\t\t\t       connection->fd) < 0 ||\n+\t    packet_flush_gently(connection->fd) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tif (read_packetized_to_strbuf(\n+\t\t    connection->fd, answer,\n+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, answer);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+static int set_socket_blocking_flag(int fd, int make_nonblocking)\n+{\n+\tint flags;\n+\n+\tflags = fcntl(fd, F_GETFL, NULL);\n+\n+\tif (flags < 0)\n+\t\treturn -1;\n+\n+\tif (make_nonblocking)\n+\t\tflags |= O_NONBLOCK;\n+\telse\n+\t\tflags &= ~O_NONBLOCK;\n+\n+\treturn fcntl(fd, F_SETFL, flags);\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_WORKER_THREAD_DATA,\n+\tMAGIC_ACCEPT_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_worker_thread_data *worker_thread_data;\n+};\n+\n+struct ipc_worker_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_worker_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+};\n+\n+struct ipc_accept_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_data *server_data;\n+\n+\tstruct unix_stream_server_socket *server_socket;\n+\n+\tint fd_send_shutdown;\n+\tint fd_wait_shutdown;\n+\tpthread_t pthread_id;\n+};\n+\n+/*\n+ * With unix-sockets, the conceptual \"ipc-server\" is implemented as a single\n+ * controller \"accept-thread\" thread and a pool of \"worker-thread\" threads.\n+ * The former does the usual `accept()` loop and dispatches connections\n+ * to an idle worker thread.  The worker threads wait in an idle loop for\n+ * a new connection, communicate with the client and relay data to/from\n+ * the `application_cb` and then wait for another connection from the\n+ * server thread.  This avoids the overhead of constantly creating and\n+ * destroying threads.\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\n+\tstruct ipc_accept_thread_data *accept_thread;\n+\tstruct ipc_worker_thread_data *worker_thread_list;\n+\n+\tpthread_mutex_t work_available_mutex;\n+\tpthread_cond_t work_available_cond;\n+\n+\t/*\n+\t * Accepted but not yet processed client connections are kept\n+\t * in a circular buffer FIFO.  The queue is empty when the\n+\t * positions are equal.\n+\t */\n+\tint *fifo_fds;\n+\tint queue_size;\n+\tint back_pos;\n+\tint front_pos;\n+\n+\tint shutdown_requested;\n+\tint is_stopped;\n+};\n+\n+/*\n+ * Remove and return the oldest queued connection.\n+ *\n+ * Returns -1 if empty.\n+ */\n+static int fifo_dequeue(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint fd;\n+\n+\tif (server_data->back_pos == server_data->front_pos)\n+\t\treturn -1;\n+\n+\tfd = server_data->fifo_fds[server_data->front_pos];\n+\tserver_data->fifo_fds[server_data->front_pos] = -1;\n+\n+\tserver_data->front_pos++;\n+\tif (server_data->front_pos == server_data->queue_size)\n+\t\tserver_data->front_pos = 0;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Push a new fd onto the back of the queue.\n+ *\n+ * Drop it and return -1 if queue is already full.\n+ */\n+static int fifo_enqueue(struct ipc_server_data *server_data, int fd)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint next_back_pos;\n+\n+\tnext_back_pos = server_data->back_pos + 1;\n+\tif (next_back_pos == server_data->queue_size)\n+\t\tnext_back_pos = 0;\n+\n+\tif (next_back_pos == server_data->front_pos) {\n+\t\t/* Queue is full. Just drop it. */\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data->fifo_fds[server_data->back_pos] = fd;\n+\tserver_data->back_pos = next_back_pos;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Wait for a connection to be queued to the FIFO and return it.\n+ *\n+ * Returns -1 if someone has already requested a shutdown.\n+ */\n+static int worker_thread__wait_for_connection(\n+\tstruct ipc_worker_thread_data *worker_thread_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tint fd = -1;\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\tfor (;;) {\n+\t\tif (server_data->shutdown_requested)\n+\t\t\tbreak;\n+\n+\t\tfd = fifo_dequeue(server_data);\n+\t\tif (fd >= 0)\n+\t\t\tbreak;\n+\n+\t\tpthread_cond_wait(&server_data->work_available_cond,\n+\t\t\t\t  &server_data->work_available_mutex);\n+\t}\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf_no_flush(response, response_len,\n+\t\t\t\t\t\t  reply_data->fd);\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_WAIT_POLL_TIMEOUT_MS (10)\n+\n+/*\n+ * If the client hangs up without sending any data on the wire, just\n+ * quietly close the socket and ignore this client.\n+ *\n+ * This worker thread is committed to reading the IPC request data\n+ * from the client at the other end of this fd.  Wait here for the\n+ * client to actually put something on the wire -- because if the\n+ * client just does a ping (connect and hangup without sending any\n+ * data), our use of the pkt-line read routines will spew an error\n+ * message.\n+ *\n+ * Return -1 if the client hung up.\n+ * Return 0 if data (possibly incomplete) is ready.\n+ */\n+static int worker_thread__wait_for_io_start(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tstruct pollfd pollfd[1];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = fd;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\tint in_shutdown;\n+\n+\t\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\t\tin_shutdown = server_data->shutdown_requested;\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\t\t\t/*\n+\t\t\t * If a shutdown is already in progress and this\n+\t\t\t * client has not started talking yet, just drop it.\n+\t\t\t */\n+\t\t\tif (in_shutdown)\n+\t\t\t\tgoto cleanup;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLHUP)\n+\t\t\tgoto cleanup;\n+\n+\t\tif (pollfd[0].revents & POLLIN)\n+\t\t\treturn 0;\n+\n+\t\tgoto cleanup;\n+\t}\n+\n+cleanup:\n+\tclose(fd);\n+\treturn -1;\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ */\n+static int worker_thread__do_io(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\t/* ASSERT NOT holding lock */\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.worker_thread_data = worker_thread_data;\n+\n+\treply_data.fd = fd;\n+\n+\tret = read_packetized_to_strbuf(\n+\t\treply_data.fd, &buf,\n+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);\n+\tif (ret >= 0) {\n+\t\tret = worker_thread_data->server_data->application_cb(\n+\t\t\tworker_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Block SIGPIPE on the current thread (so that we get EPIPE from\n+ * write() rather than an actual signal).\n+ *\n+ * Note that using sigchain_push() and _pop() to control SIGPIPE\n+ * around our IO calls is not thread safe:\n+ * [] It uses a global stack of handler frames.\n+ * [] It uses ALLOC_GROW() to resize it.\n+ * [] Finally, according to the `signal(2)` man-page:\n+ *    \"The effects of `signal()` in a multithreaded process are unspecified.\"\n+ */\n+static void thread_block_sigpipe(sigset_t *old_set)\n+{\n+\tsigset_t new_set;\n+\n+\tsigemptyset(&new_set);\n+\tsigaddset(&new_set, SIGPIPE);\n+\n+\tsigemptyset(old_set);\n+\tpthread_sigmask(SIG_BLOCK, &new_set, old_set);\n+}\n+\n+/*\n+ * Thread proc for an IPC worker thread.  It handles a series of\n+ * connections from clients.  It pulls the next fd from the queue\n+ * processes it, and then waits for the next client.\n+ *\n+ * Block SIGPIPE in this worker thread for the life of the thread.\n+ * This avoids stray (and sometimes delayed) SIGPIPE signals caused\n+ * by client errors and/or when we are under extremely heavy IO load.\n+ *\n+ * This means that the application callback will have SIGPIPE blocked.\n+ * The callback should not change it.\n+ */\n+static void *worker_thread_proc(void *_worker_thread_data)\n+{\n+\tstruct ipc_worker_thread_data *worker_thread_data = _worker_thread_data;\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tsigset_t old_set;\n+\tint fd, io;\n+\tint ret;\n+\n+\ttrace2_thread_start(\"ipc-worker\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tfd = worker_thread__wait_for_connection(worker_thread_data);\n+\t\tif (fd == -1)\n+\t\t\tbreak; /* in shutdown */\n+\n+\t\tio = worker_thread__wait_for_io_start(worker_thread_data, fd);\n+\t\tif (io == -1)\n+\t\t\tcontinue; /* client hung up without sending anything */\n+\n+\t\tret = worker_thread__do_io(worker_thread_data, fd);\n+\n+\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\ttrace2_data_string(\"ipc-worker\", NULL, \"queue_stop_async\",\n+\t\t\t\t\t   \"application_quit\");\n+\t\t\t/*\n+\t\t\t * The application layer is telling the ipc-server\n+\t\t\t * layer to shutdown.\n+\t\t\t *\n+\t\t\t * We DO NOT have a response to send to the client.\n+\t\t\t *\n+\t\t\t * Queue an async stop (to stop the other threads) and\n+\t\t\t * allow this worker thread to exit now (no sense waiting\n+\t\t\t * for the thread-pool shutdown signal).\n+\t\t\t *\n+\t\t\t * Other non-idle worker threads are allowed to finish\n+\t\t\t * responding to their current clients.\n+\t\t\t */\n+\t\t\tipc_server_stop_async(server_data);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)\n+\n+/*\n+ * Accept a new client connection on our socket.  This uses non-blocking\n+ * IO so that we can also wait for shutdown requests on our socket-pair\n+ * without actually spinning on a fast timeout.\n+ */\n+static int accept_thread__wait_for_connection(\n+\tstruct ipc_accept_thread_data *accept_thread_data)\n+{\n+\tstruct pollfd pollfd[2];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = accept_thread_data->fd_wait_shutdown;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tpollfd[1].fd = accept_thread_data->server_socket->fd_socket;\n+\t\tpollfd[1].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\treturn result;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\t/*\n+\t\t\t * If someone deletes or force-creates a new unix\n+\t\t\t * domain socket at our path, all future clients\n+\t\t\t * will be routed elsewhere and we silently starve.\n+\t\t\t * If that happens, just queue a shutdown.\n+\t\t\t */\n+\t\t\tif (unix_stream_server__was_stolen(\n+\t\t\t\t    accept_thread_data->server_socket)) {\n+\t\t\t\ttrace2_data_string(\"ipc-accept\", NULL,\n+\t\t\t\t\t\t   \"queue_stop_async\",\n+\t\t\t\t\t\t   \"socket_stolen\");\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\taccept_thread_data->server_data);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLIN) {\n+\t\t\t/* shutdown message queued to socketpair */\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (pollfd[1].revents & POLLIN) {\n+\t\t\t/* a connection is available on server_socket */\n+\n+\t\t\tint client_fd =\n+\t\t\t\taccept(accept_thread_data->server_socket->fd_socket,\n+\t\t\t\t       NULL, NULL);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\treturn client_fd;\n+\n+\t\t\t/*\n+\t\t\t * An error here is unlikely -- it probably\n+\t\t\t * indicates that the connecting process has\n+\t\t\t * already dropped the connection.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tBUG(\"unandled poll result errno=%d r[0]=%d r[1]=%d\",\n+\t\t    errno, pollfd[0].revents, pollfd[1].revents);\n+\t}\n+}\n+\n+/*\n+ * Thread proc for the IPC server \"accept thread\".  This waits for\n+ * an incoming socket connection, appends it to the queue of available\n+ * connections, and notifies a worker thread to process it.\n+ *\n+ * Block SIGPIPE in this thread for the life of the thread.  This\n+ * avoids any stray SIGPIPE signals when closing pipe fds under\n+ * extremely heavy loads (such as when the fifo queue is full and we\n+ * drop incomming connections).\n+ */\n+static void *accept_thread_proc(void *_accept_thread_data)\n+{\n+\tstruct ipc_accept_thread_data *accept_thread_data = _accept_thread_data;\n+\tstruct ipc_server_data *server_data = accept_thread_data->server_data;\n+\tsigset_t old_set;\n+\n+\ttrace2_thread_start(\"ipc-accept\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tint client_fd = accept_thread__wait_for_connection(\n+\t\t\taccept_thread_data);\n+\n+\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\tif (server_data->shutdown_requested) {\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\tclose(client_fd);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (client_fd < 0) {\n+\t\t\t/* ignore transient accept() errors */\n+\t\t}\n+\t\telse {\n+\t\t\tfifo_enqueue(server_data, client_fd);\n+\t\t\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\t\t}\n+\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * We can't predict the connection arrival rate relative to the worker\n+ * processing rate, therefore we allow the \"accept-thread\" to queue up\n+ * a generous number of connections, since we'd rather have the client\n+ * not unnecessarily timeout if we can avoid it.  (The assumption is\n+ * that this will be used for FSMonitor and a few second wait on a\n+ * connection is better than having the client timeout and do the full\n+ * computation itself.)\n+ *\n+ * The FIFO queue size is set to a multiple of the worker pool size.\n+ * This value chosen at random.\n+ */\n+#define FIFO_SCALE (100)\n+\n+/*\n+ * The backlog value for `listen(2)`.  This doesn't need to huge,\n+ * rather just large enough for our \"accept-thread\" to wake up and\n+ * queue incoming connections onto the FIFO without the kernel\n+ * dropping any.\n+ *\n+ * This value chosen at random.\n+ */\n+#define LISTEN_BACKLOG (50)\n+\n+static struct unix_stream_server_socket *create_listener_socket(\n+\tconst char *path,\n+\tconst struct ipc_server_opts *ipc_opts)\n+{\n+\tstruct unix_stream_server_socket *server_socket = NULL;\n+\tstruct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n+\n+\tuslg_opts.listen_backlog_size = LISTEN_BACKLOG;\n+\tuslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;\n+\n+\tserver_socket = unix_stream_server__listen_with_lock(path, &uslg_opts);\n+\tif (!server_socket)\n+\t\treturn NULL;\n+\n+\tif (set_socket_blocking_flag(server_socket->fd_socket, 1)) {\n+\t\tint saved_errno = errno;\n+\t\terror_errno(_(\"could not set listener socket nonblocking '%s'\"),\n+\t\t\t    path);\n+\t\tunix_stream_server__free(server_socket);\n+\t\terrno = saved_errno;\n+\t\treturn NULL;\n+\t}\n+\n+\ttrace2_data_string(\"ipc-server\", NULL, \"listen-with-lock\", path);\n+\treturn server_socket;\n+}\n+\n+static struct unix_stream_server_socket *setup_listener_socket(\n+\tconst char *path,\n+\tconst struct ipc_server_opts *ipc_opts)\n+{\n+\tstruct unix_stream_server_socket *server_socket;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n+\tserver_socket = create_listener_socket(path, ipc_opts);\n+\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n+\n+\treturn server_socket;\n+}\n+\n+/*\n+ * Start IPC server in a pool of background threads.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct unix_stream_server_socket *server_socket = NULL;\n+\tstruct ipc_server_data *server_data;\n+\tint sv[2];\n+\tint k;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\t/*\n+\t * Create a socketpair and set sv[1] to non-blocking.  This\n+\t * will used to send a shutdown message to the accept-thread\n+\t * and allows the accept-thread to wait on EITHER a client\n+\t * connection or a shutdown request without spinning.\n+\t */\n+\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)\n+\t\treturn error_errno(_(\"could not create socketpair for '%s'\"),\n+\t\t\t\t   path);\n+\n+\tif (set_socket_blocking_flag(sv[1], 1)) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn error_errno(_(\"making socketpair nonblocking '%s'\"),\n+\t\t\t\t   path);\n+\t}\n+\n+\tserver_socket = setup_listener_socket(path, opts);\n+\tif (!server_socket) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tpthread_mutex_init(&server_data->work_available_mutex, NULL);\n+\tpthread_cond_init(&server_data->work_available_cond, NULL);\n+\n+\tserver_data->queue_size = nr_threads * FIFO_SCALE;\n+\tserver_data->fifo_fds = xcalloc(server_data->queue_size,\n+\t\t\t\t\tsizeof(*server_data->fifo_fds));\n+\n+\tserver_data->accept_thread =\n+\t\txcalloc(1, sizeof(*server_data->accept_thread));\n+\tserver_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;\n+\tserver_data->accept_thread->server_data = server_data;\n+\tserver_data->accept_thread->server_socket = server_socket;\n+\tserver_data->accept_thread->fd_send_shutdown = sv[0];\n+\tserver_data->accept_thread->fd_wait_shutdown = sv[1];\n+\n+\tif (pthread_create(&server_data->accept_thread->pthread_id, NULL,\n+\t\t\t   accept_thread_proc, server_data->accept_thread))\n+\t\tdie_errno(_(\"could not start accept_thread '%s'\"), path);\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_worker_thread_data *wtd;\n+\n+\t\twtd = xcalloc(1, sizeof(*wtd));\n+\t\twtd->magic = MAGIC_WORKER_THREAD_DATA;\n+\t\twtd->server_data = server_data;\n+\n+\t\tif (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc,\n+\t\t\t\t   wtd)) {\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start worker[0] for '%s'\"),\n+\t\t\t\t    path);\n+\t\t\t/*\n+\t\t\t * Limp along with the thread pool that we have.\n+\t\t\t */\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\twtd->next_thread = server_data->worker_thread_list;\n+\t\tserver_data->worker_thread_list = wtd;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+/*\n+ * Gently tell the IPC server treads to shutdown.\n+ * Can be run on any thread.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tint fd;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\n+\tserver_data->shutdown_requested = 1;\n+\n+\t/*\n+\t * Write a byte to the shutdown socket pair to wake up the\n+\t * accept-thread.\n+\t */\n+\tif (write(server_data->accept_thread->fd_send_shutdown, \"Q\", 1) < 0)\n+\t\terror_errno(\"could not write to fd_send_shutdown\");\n+\n+\t/*\n+\t * Drain the queue of existing connections.\n+\t */\n+\twhile ((fd = fifo_dequeue(server_data)) != -1)\n+\t\tclose(fd);\n+\n+\t/*\n+\t * Gently tell worker threads to stop processing new connections\n+\t * and exit.  (This does not abort in-process conversations.)\n+\t */\n+\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\ttrace2_region_leave(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Wait for all IPC server threads to stop.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tpthread_join(server_data->accept_thread->pthread_id, NULL);\n+\n+\tif (!server_data->shutdown_requested)\n+\t\tBUG(\"ipc-server: accept-thread stopped for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tpthread_join(wtd->pthread_id, NULL);\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tstruct ipc_accept_thread_data * accept_thread_data;\n+\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\taccept_thread_data = server_data->accept_thread;\n+\tif (accept_thread_data) {\n+\t\tunix_stream_server__free(accept_thread_data->server_socket);\n+\n+\t\tif (accept_thread_data->fd_send_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_send_shutdown);\n+\t\tif (accept_thread_data->fd_wait_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_wait_shutdown);\n+\n+\t\tfree(server_data->accept_thread);\n+\t}\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tpthread_cond_destroy(&server_data->work_available_cond);\n+\tpthread_mutex_destroy(&server_data->work_available_mutex);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tfree(server_data->fifo_fds);\n+\tfree(server_data);\n+}\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 4bd41054ee70..4c27a373414a 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -248,6 +248,8 @@ endif()\n \n if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+else()\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c)\n endif()\n \n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\ndiff --git a/simple-ipc.h b/simple-ipc.h\nindex a3f96b42cca2..f7e72e966f9a 100644\n--- a/simple-ipc.h\n+++ b/simple-ipc.h\n@@ -5,7 +5,7 @@\n  * See Documentation/technical/api-simple-ipc.txt\n  */\n \n-#if defined(GIT_WINDOWS_NATIVE)\n+#if defined(GIT_WINDOWS_NATIVE) || !defined(NO_UNIX_SOCKETS)\n #define SUPPORTS_SIMPLE_IPC\n #endif\n \n@@ -62,11 +62,17 @@ struct ipc_client_connect_options {\n \t * the service and need to wait for it to become ready.\n \t */\n \tunsigned int wait_if_not_found:1;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n #define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n \t.wait_if_busy = 0, \\\n \t.wait_if_not_found = 0, \\\n+\t.uds_disallow_chdir = 0, \\\n }\n \n /*\n@@ -159,6 +165,11 @@ struct ipc_server_data;\n struct ipc_server_opts\n {\n \tint nr_threads;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"417838","messageId":"xmqq8s7cuebo.fsf@gitster.g","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/12] Simple IPC Mechanism","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-25T19:39:39Z","receivedAt":"2021-02-25T19:42:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Here is V4 of my \"Simple IPC\" series. It addresses Gábor's comment WRT\n> shutting down the server to make unit tests more predictable on CI servers.\n> (https://lore.kernel.org/git/20210213093052.GJ1015009@szeder.dev)\n>\n> Jeff\n>\n> cc: Ævar Arnfjörð Bjarmason avarab@gmail.com cc: Jeff Hostetler\n> git@jeffhostetler.com cc: Jeff King peff@peff.net cc: Chris Torek\n> chris.torek@gmail.com\n\nIt seems that the discussions around the topic has mostly done\nduring the v2 review, and has quieted down since then.\n\nLet's merge it down to 'next'?\n\n"},{"id":"417878","messageId":"YDihb2Kspbh4FIlW@coredump.intra.peff.net","threadId":"54978","inReplyTo":"2d6858b1625aa3c96688c6c6a9157c2d2b16f43e.1613598529.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-26T07:21:19Z","receivedAt":"2021-02-26T07:22:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 17, 2021 at 09:48:37PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> Change the API of `write_packetized_from_fd()` to accept a scratch space\n> argument from its caller to avoid similar issues here.\n\nOK, but...\n\n> diff --git a/convert.c b/convert.c\n> index ee360c2f07ce..41012c2d301c 100644\n> --- a/convert.c\n> +++ b/convert.c\n> @@ -883,9 +883,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n>  \tif (err)\n>  \t\tgoto done;\n>  \n> -\tif (fd >= 0)\n> -\t\terr = write_packetized_from_fd(fd, process->in);\n> -\telse\n> +\tif (fd >= 0) {\n> +\t\tstruct packet_scratch_space scratch;\n> +\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n> +\t} else\n>  \t\terr = write_packetized_from_buf(src, len, process->in);\n\nIsn't this just putting the buffer onto the stack anyway? Your\nscratch_space struct is really just a big array. You'd want to make\nit static here, but then we haven't really solved anything. :)\n\nI think instead that:\n\n> -int write_packetized_from_fd(int fd_in, int fd_out)\n> +int write_packetized_from_fd(int fd_in, int fd_out,\n> +\t\t\t     struct packet_scratch_space *scratch)\n>  {\n> -\tstatic char buf[LARGE_PACKET_DATA_MAX];\n>  \tint err = 0;\n>  \tssize_t bytes_to_write;\n>  \n>  \twhile (!err) {\n> -\t\tbytes_to_write = xread(fd_in, buf, sizeof(buf));\n> +\t\tbytes_to_write = xread(fd_in, scratch->buffer,\n> +\t\t\t\t       sizeof(scratch->buffer));\n>  \t\tif (bytes_to_write < 0)\n>  \t\t\treturn COPY_READ_ERROR;\n>  \t\tif (bytes_to_write == 0)\n>  \t\t\tbreak;\n> -\t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n> +\t\terr = packet_write_gently(fd_out, scratch->buffer,\n> +\t\t\t\t\t  bytes_to_write);\n>  \t}\n\n...just heap-allocating the buffer in this function would be fine. It's\none malloc for the whole sequence of pktlines, which is unlikely to be a\nproblem.\n\n-Peff\n"},{"id":"417879","messageId":"YDiicvQEE9L7m3T9@coredump.intra.peff.net","threadId":"54978","inReplyTo":"b368318e6a23f8c4e60f77a8b81b558c523d5b03.1613598529.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 07/12] unix-socket: elimiate static unix_stream_socket() helper function","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-26T07:25:38Z","receivedAt":"2021-02-26T07:26:33Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 17, 2021 at 09:48:43PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n> The static helper function `unix_stream_socket()` calls `die()`.  This\n> is not appropriate for all callers.  Eliminate the wrapper function\n> and make the callers propagate the error.\n\nThanks for breaking it up this way. It's (IMHO) much easier to see the\nmotivation and impact of the changes now.\n\nThere's a small typo in the subject:\n\n> Subject: unix-socket: elimiate static unix_stream_socket() helper function\n\n-Peff\n"},{"id":"417880","messageId":"YDijhRaib5It/apG@coredump.intra.peff.net","threadId":"54978","inReplyTo":"985b2e02b2df7725d70f1365f7cd2e525c9f3ade.1613598529.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 08/12] unix-socket: add backlog size option to unix_stream_listen()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-26T07:30:13Z","receivedAt":"2021-02-26T07:31:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 17, 2021 at 09:48:44PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> @@ -106,7 +108,10 @@ int unix_stream_listen(const char *path)\n>  \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n>  \t\tgoto fail;\n>  \n> -\tif (listen(fd, 5) < 0)\n> +\tbacklog = opts->listen_backlog_size;\n> +\tif (backlog <= 0)\n> +\t\tbacklog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;\n> +\tif (listen(fd, backlog) < 0)\n>  \t\tgoto fail;\n\nOK, so we still have the fallback-on-zero here, which is good...\n\n> +struct unix_stream_listen_opts {\n> +\tint listen_backlog_size;\n> +};\n> +\n> +#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n> +\n> +#define UNIX_STREAM_LISTEN_OPTS_INIT \\\n> +{ \\\n> +\t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n> +}\n\n...but I thought the plan was to drop this initialization in favor of a\nzero-initialization. What you have certainly wouldn't do the wrong\nthing, but it just seems weirdly redundant. Unless some caller really\nwants to know what the default will be?\n\n-Peff\n"},{"id":"417882","messageId":"YDipqpvCoE0WnJSi@coredump.intra.peff.net","threadId":"54978","inReplyTo":"b443e11ac32fd3082a59ada42ada8c8973fa0b8a.1613598529.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 10/12] unix-socket: create `unix_stream_server__listen_with_lock()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-26T07:56:26Z","receivedAt":"2021-02-26T07:57:15Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 17, 2021 at 09:48:46PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n> Create a version of `unix_stream_listen()` that uses a \".lock\" lockfile\n> to create the unix domain socket in a race-free manner.\n\nThe \"unix_stream_server__listen_with_lock\" name is quite a mouthful.  My\nfirst question was: don't we have an \"options\" struct that we can use to\ntell it we're interested in using the locking strategy?\n\nBut I do find it a little weird for the feature to be at this layer at\nall. I'd have thought it would make more sense in the simple-ipc layer\nthat implements the unix-socket backend, where app-level logic like\n\"it's OK to just connect to this socket and hang up in order to ping it\"\nmight be more appropriate. We might even want to have a more robust\ncheck (e.g., an actual \"ping\" that expects the server to say \"yes, I'm\nhere\").\n\n(But also see below where I am less certain about this...)\n\n> Unix domain sockets have a fundamental problem on Unix systems because\n> they persist in the filesystem until they are deleted.  This is\n> independent of whether a server is actually listening for connections.\n> Well-behaved servers are expected to delete the socket when they\n> shutdown.  A new server cannot easily tell if a found socket is\n> attached to an active server or is leftover cruft from a dead server.\n> The traditional solution used by `unix_stream_listen()` is to force\n> delete the socket pathname and then create a new socket.  This solves\n> the latter (cruft) problem, but in the case of the former, it orphans\n> the existing server (by stealing the pathname associated with the\n> socket it is listening on).\n\nNicely explained.\n\n> We cannot directly use a .lock lockfile to create the socket because\n> the socket is created by `bind(2)` rather than the `open(2)` mechanism\n> used by `tempfile.c`.\n> \n> As an alternative, we hold a plain lockfile (\"<path>.lock\") as a\n> mutual exclusion device.  Under the lock, we test if an existing\n> socket (\"<path>\") is has an active server.  If not, create a new\n> socket and begin listening.  Then we rollback the lockfile in all\n> cases.\n\nMake sense.\n\n> +static int is_another_server_alive(const char *path,\n> +\t\t\t\t   const struct unix_stream_listen_opts *opts)\n> +{\n> +\tstruct stat st;\n> +\tint fd;\n> +\n> +\tif (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {\n> +\t\t/*\n> +\t\t * A socket-inode exists on disk at `path`, but we\n> +\t\t * don't know whether it belongs to an active server\n> +\t\t * or whether the last server died without cleaning\n> +\t\t * up.\n> +\t\t *\n> +\t\t * Poke it with a trivial connection to try to find\n> +\t\t * out.\n> +\t\t */\n> +\t\tfd = unix_stream_connect(path, opts->disallow_chdir);\n> +\t\tif (fd >= 0) {\n> +\t\t\tclose(fd);\n> +\t\t\treturn 1;\n> +\t\t}\n> +\t}\n\nThe lstat() seems redundant here. unix_stream_connect() will tell us\nwhether there is something to connect to or not. (It's also racy with\nrespect to the actual connect, but since you're doing this under lock, I\ndon't think that matters).\n\n> +struct unix_stream_server_socket *unix_stream_server__listen_with_lock(\n> +\tconst char *path,\n> +\tconst struct unix_stream_listen_opts *opts)\n> +{\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\tint fd_socket;\n> +\tstruct unix_stream_server_socket *server_socket;\n> +\n> +\t/*\n> +\t * Create a lock at \"<path>.lock\" if we can.\n> +\t */\n> +\tif (hold_lock_file_for_update_timeout(&lock, path, 0,\n> +\t\t\t\t\t      opts->timeout_ms) < 0) {\n> +\t\terror_errno(_(\"could not lock listener socket '%s'\"), path);\n> +\t\treturn NULL;\n> +\t}\n\nWould you want to ping to see if it's alive before creating the lock?\nThat would be the fast-path if we assume that a server will usually be\nthere once started. Or is that supposed to happen in the caller (in\nwhich case I'd again wonder if this really should be happening in the\nsimple-ipc code).\n\n> +\t/*\n> +\t * If another server is listening on \"<path>\" give up.  We do not\n> +\t * want to create a socket and steal future connections from them.\n> +\t */\n> +\tif (is_another_server_alive(path, opts)) {\n> +\t\terrno = EADDRINUSE;\n> +\t\terror_errno(_(\"listener socket already in use '%s'\"), path);\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn NULL;\n> +\t}\n\nWouldn't this be a \"success\" case for a caller? They did not open the\nserver themselves, but they are presumably happy that there is one there\nnow to talk to. So do we actually want to print an error to stderr?\nLikewise, how do they tell the difference between this NULL and the NULL\nwe returned above because we couldn't take the lock? Or the NULL we\nreturn below because there is some error creating a listening socket?\n\nI'd think in those three cases you'd want:\n\n  - if lock contention, pause a moment and wait for the winner to spin\n    up and serve requests\n\n  - if another server is live while we hold the lock, then we raced them\n    and they won. Release the lock and start using them.\n\n  - if we really tried to call unix_stream_listen() and that failed,\n    give up now. There is some system error that is not likely to be\n    fixed by trying anything more (e.g., ENAMETOOLONG).\n\n> +\tserver_socket = xcalloc(1, sizeof(*server_socket));\n> +\tserver_socket->path_socket = strdup(path);\n> +\tserver_socket->fd_socket = fd_socket;\n\nWhat do we need this server_socket for? The caller already knows the\npath; they fed it to us. We do need to return the descriptor, but we\ncould do that directly.\n\n> +\tlstat(path, &server_socket->st_socket);\n\nThis lstat I guess is part of your \"periodically check to see if we're\nstill the one holding the socket\" strategy. We _shouldn't_ need that\nanymore, with the dotlocking, but I'm OK with it as a\nbelt-and-suspenders check. But why are we filling in the lstat here?\nThis seems like something that the unix-socket code doesn't really need\nto know about (though you do at least provide the complementary\n\"was_stolen\" function here, so that part makes sense).\n\nAgain, I guess I'd find it less weird if it were happening at a layer\nabove. Maybe I'm really just complaining that this is in unix-socket.c.\nI guess it is a separate unix_stream_server data type. Arguably that\nshould go in a separate file, but I guess the whole conditional\ncompilation of unix-socket.c makes that awkward. So maybe this is the\nleast-bad thing.\n\n> +\t/*\n> +\t * Always rollback (just delete) \"<path>.lock\" because we already created\n> +\t * \"<path>\" as a socket and do not want to commit_lock to do the atomic\n> +\t * rename trick.\n> +\t */\n> +\trollback_lock_file(&lock);\n> +\n> +\treturn server_socket;\n> +}\n\nOK, this part makes sense to me.\n\n> +void unix_stream_server__free(\n> +\tstruct unix_stream_server_socket *server_socket)\n> +{\n> +\tif (!server_socket)\n> +\t\treturn;\n> +\n> +\tif (server_socket->fd_socket >= 0) {\n> +\t\tif (!unix_stream_server__was_stolen(server_socket))\n> +\t\t\tunlink(server_socket->path_socket);\n> +\t\tclose(server_socket->fd_socket);\n> +\t}\n> +\n> +\tfree(server_socket->path_socket);\n> +\tfree(server_socket);\n> +}\n\nOK, this makes sense. We only remove it if we're still the ones holding\nit. That's not done under lock, though, so it's possibly racy (somebody\nsteals from us while _they_ hold the lock; we check and see \"not stolen\"\nright before they steal it, and then we unlink their stolen copy).\n\n> +int unix_stream_server__was_stolen(\n> +\tstruct unix_stream_server_socket *server_socket)\n> +{\n> +\tstruct stat st_now;\n> +\n> +\tif (!server_socket)\n> +\t\treturn 0;\n> +\n> +\tif (lstat(server_socket->path_socket, &st_now) == -1)\n> +\t\treturn 1;\n> +\n> +\tif (st_now.st_ino != server_socket->st_socket.st_ino)\n> +\t\treturn 1;\n> +\n> +\t/* We might also consider the ctime on some platforms. */\n> +\n> +\treturn 0;\n> +}\n\nYou probably should confirm that st.dev matches, too, since that is the\nnamespace for st.ino. Maybe also double check that it's still a socket\nwith S_ISSOCK(st_mode)?\n\n-Peff\n"},{"id":"417884","messageId":"YDiqeaNX/BeROFGf@coredump.intra.peff.net","threadId":"54978","inReplyTo":"xmqq8s7cuebo.fsf@gitster.g","subject":"Re: [PATCH v4 00/12] Simple IPC Mechanism","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-26T07:59:53Z","receivedAt":"2021-02-26T08:01:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Feb 25, 2021 at 11:39:39AM -0800, Junio C Hamano wrote:\n\n> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n> > Here is V4 of my \"Simple IPC\" series. It addresses Gábor's comment WRT\n> > shutting down the server to make unit tests more predictable on CI servers.\n> > (https://lore.kernel.org/git/20210213093052.GJ1015009@szeder.dev)\n> >\n> > Jeff\n> >\n> > cc: Ævar Arnfjörð Bjarmason avarab@gmail.com cc: Jeff Hostetler\n> > git@jeffhostetler.com cc: Jeff King peff@peff.net cc: Chris Torek\n> > chris.torek@gmail.com\n> \n> It seems that the discussions around the topic has mostly done\n> during the v2 review, and has quieted down since then.\n> \n> Let's merge it down to 'next'?\n\nSorry, I hadn't gotten around to looking at the latest version. I left\nanother round of comments. Some of them are arguably bikeshedding, but\nthere's at least one I think we'd want to address (the big stack buffer\nin patch 1).\n\nI also haven't carefully looked at the simple-ipc design at all; my\nfocus has just been on the details of socket and pktline code being\ntouched. Since there are no simple-ipc users yet, and since it's\ninternal and would be easy to change later, I'm mostly content for Jeff\nto proceed as he sees fit and iterate on it as necessary.\n\n-Peff\n"},{"id":"417907","messageId":"9304ac66-f493-2150-95e3-15303c914ee3@jeffhostetler.com","threadId":"54978","inReplyTo":"YDihb2Kspbh4FIlW@coredump.intra.peff.net","subject":"Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-02-26T19:52:22Z","receivedAt":"2021-02-26T19:53:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/26/21 2:21 AM, Jeff King wrote:\n> On Wed, Feb 17, 2021 at 09:48:37PM +0000, Jeff Hostetler via GitGitGadget wrote:\n> \n>> Change the API of `write_packetized_from_fd()` to accept a scratch space\n>> argument from its caller to avoid similar issues here.\n> \n> OK, but...\n> \n>> diff --git a/convert.c b/convert.c\n>> index ee360c2f07ce..41012c2d301c 100644\n>> --- a/convert.c\n>> +++ b/convert.c\n>> @@ -883,9 +883,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n>>   \tif (err)\n>>   \t\tgoto done;\n>>   \n>> -\tif (fd >= 0)\n>> -\t\terr = write_packetized_from_fd(fd, process->in);\n>> -\telse\n>> +\tif (fd >= 0) {\n>> +\t\tstruct packet_scratch_space scratch;\n>> +\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n>> +\t} else\n>>   \t\terr = write_packetized_from_buf(src, len, process->in);\n> \n> Isn't this just putting the buffer onto the stack anyway? Your\n> scratch_space struct is really just a big array. You'd want to make\n> it static here, but then we haven't really solved anything. :)\n\nYeah, I was letting the caller decide how to provide the buffer.\nThey could put it on the stack or allocate it once across a whole\nset of files or use a static buffer -- the caller has context for\nwhat works best that we don't have here.  For example, the caller\nmay know that is not in threaded code at all, but we cannot assume\nthat here.\n\n> \n> I think instead that:\n> \n>> -int write_packetized_from_fd(int fd_in, int fd_out)\n>> +int write_packetized_from_fd(int fd_in, int fd_out,\n>> +\t\t\t     struct packet_scratch_space *scratch)\n>>   {\n>> -\tstatic char buf[LARGE_PACKET_DATA_MAX];\n>>   \tint err = 0;\n>>   \tssize_t bytes_to_write;\n>>   \n>>   \twhile (!err) {\n>> -\t\tbytes_to_write = xread(fd_in, buf, sizeof(buf));\n>> +\t\tbytes_to_write = xread(fd_in, scratch->buffer,\n>> +\t\t\t\t       sizeof(scratch->buffer));\n>>   \t\tif (bytes_to_write < 0)\n>>   \t\t\treturn COPY_READ_ERROR;\n>>   \t\tif (bytes_to_write == 0)\n>>   \t\t\tbreak;\n>> -\t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n>> +\t\terr = packet_write_gently(fd_out, scratch->buffer,\n>> +\t\t\t\t\t  bytes_to_write);\n>>   \t}\n> \n> ...just heap-allocating the buffer in this function would be fine. It's\n> one malloc for the whole sequence of pktlines, which is unlikely to be a\n> problem.\n\nRight, I think it would be fine to malloc it here, but I didn't\nwant to assume that everyone would think that.\n\nI'll change it.\n\nThanks\nJeff\n\n"},{"id":"417908","messageId":"ff2eb93c-9b96-6fec-961b-adbe0fbda6fb@jeffhostetler.com","threadId":"54978","inReplyTo":"YDiqeaNX/BeROFGf@coredump.intra.peff.net","subject":"Re: [PATCH v4 00/12] Simple IPC Mechanism","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-02-26T20:18:26Z","receivedAt":"2021-02-26T20:21:46Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/26/21 2:59 AM, Jeff King wrote:\n> On Thu, Feb 25, 2021 at 11:39:39AM -0800, Junio C Hamano wrote:\n> \n>> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>\n>>> Here is V4 of my \"Simple IPC\" series. It addresses Gábor's comment WRT\n>>> shutting down the server to make unit tests more predictable on CI servers.\n>>> (https://lore.kernel.org/git/20210213093052.GJ1015009@szeder.dev)\n>>>\n>>> Jeff\n>>>\n>>> cc: Ævar Arnfjörð Bjarmason avarab@gmail.com cc: Jeff Hostetler\n>>> git@jeffhostetler.com cc: Jeff King peff@peff.net cc: Chris Torek\n>>> chris.torek@gmail.com\n>>\n>> It seems that the discussions around the topic has mostly done\n>> during the v2 review, and has quieted down since then.\n>>\n>> Let's merge it down to 'next'?\n> \n> Sorry, I hadn't gotten around to looking at the latest version. I left\n> another round of comments. Some of them are arguably bikeshedding, but\n> there's at least one I think we'd want to address (the big stack buffer\n> in patch 1).\n> \n> I also haven't carefully looked at the simple-ipc design at all; my\n> focus has just been on the details of socket and pktline code being\n> touched. Since there are no simple-ipc users yet, and since it's\n> internal and would be easy to change later, I'm mostly content for Jeff\n> to proceed as he sees fit and iterate on it as necessary.\n> \n> -Peff\n> \n\nWe can wait until next week on moving this 'next' if you want.\nI'll attend to the buffer alloc in patch 1.  I'm still reading the\nother comments and will see where that takes me.\n\nI'm about ready to push an RFC for my fsmonitor--daemon series that\nsits on top of this simple-ipc series, so you can see an actual use\ncase if that would help understand (my madness).\n\nThanks\nJeff\n\n"},{"id":"417909","messageId":"YDldiMC6qx0z/NBf@coredump.intra.peff.net","threadId":"54978","inReplyTo":"9304ac66-f493-2150-95e3-15303c914ee3@jeffhostetler.com","subject":"Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-26T20:43:52Z","receivedAt":"2021-02-26T20:44:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Feb 26, 2021 at 02:52:22PM -0500, Jeff Hostetler wrote:\n\n> > > -\tif (fd >= 0)\n> > > -\t\terr = write_packetized_from_fd(fd, process->in);\n> > > -\telse\n> > > +\tif (fd >= 0) {\n> > > +\t\tstruct packet_scratch_space scratch;\n> > > +\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n> > > +\t} else\n> > >   \t\terr = write_packetized_from_buf(src, len, process->in);\n> > \n> > Isn't this just putting the buffer onto the stack anyway? Your\n> > scratch_space struct is really just a big array. You'd want to make\n> > it static here, but then we haven't really solved anything. :)\n> \n> Yeah, I was letting the caller decide how to provide the buffer.\n> They could put it on the stack or allocate it once across a whole\n> set of files or use a static buffer -- the caller has context for\n> what works best that we don't have here.  For example, the caller\n> may know that is not in threaded code at all, but we cannot assume\n> that here.\n\nYeah, I think it's successfully pushed the problem up to the caller. But\nit introduced a _new_ problem in putting the large buffer on the stack.\nSo if this were \"static struct packet_scratch_space scratch\", I think\nwe'd be OK.\n\nAnd perhaps that would meet your needs (if you just need to call\nwrite_packed_from_fd() in a thread, and not this other caller).\n\nBut I do think the heap approach is nice in that it keeps the interface\nclean, and I think the performance should be comparable.\n\n> Right, I think it would be fine to malloc it here, but I didn't\n> want to assume that everyone would think that.\n> \n> I'll change it.\n\nThanks. :)\n\n-Peff\n"},{"id":"417911","messageId":"YDlfDZcMkcfJ8N7e@coredump.intra.peff.net","threadId":"54978","inReplyTo":"ff2eb93c-9b96-6fec-961b-adbe0fbda6fb@jeffhostetler.com","subject":"Re: [PATCH v4 00/12] Simple IPC Mechanism","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-26T20:50:21Z","receivedAt":"2021-02-26T20:51:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Feb 26, 2021 at 03:18:26PM -0500, Jeff Hostetler wrote:\n\n> > Sorry, I hadn't gotten around to looking at the latest version. I left\n> > another round of comments. Some of them are arguably bikeshedding, but\n> > there's at least one I think we'd want to address (the big stack buffer\n> > in patch 1).\n> > \n> > I also haven't carefully looked at the simple-ipc design at all; my\n> > focus has just been on the details of socket and pktline code being\n> > touched. Since there are no simple-ipc users yet, and since it's\n> > internal and would be easy to change later, I'm mostly content for Jeff\n> > to proceed as he sees fit and iterate on it as necessary.\n> \n> We can wait until next week on moving this 'next' if you want.\n> I'll attend to the buffer alloc in patch 1.  I'm still reading the\n> other comments and will see where that takes me.\n\nI could have been a bit more clear here: modulo any response you have to\nmy latest round of comments, I'm mostly happy to let this proceed to\nnext. So I was thinking you'd have one more re-roll dealing with the\npatch 1 problems plus anything else you think worth addressing from my\nbatch of comments, and then that result would probably be ready for\n'next'.\n\n> I'm about ready to push an RFC for my fsmonitor--daemon series that\n> sits on top of this simple-ipc series, so you can see an actual use\n> case if that would help understand (my madness).\n\nI may have dug my own grave here. ;) I'm actually not incredibly\ninterested in the overall topic. So I wasn't saying so much \"I'll\nreserve judgement on simple-ipc until I see callers\" so much as \"I\nexpect you'll find any shortcomings in its design yourself as you build\non top of it\".\n\nAnd by \"not interested\" I don't mean that I think the topic is without\nvalue. Far from it; I think this is an important area to be working in.\nBut it's complex and time-consuming to review. So I was hoping somebody\nwith more expertise and interest in the problem space would do that part\nof the review, and I could continue to focus on other stuff. That may be\nwishful thinking, though. :)\n\n-Peff\n"},{"id":"418106","messageId":"YD4JAvK0epzm9b2y@coredump.intra.peff.net","threadId":"54978","inReplyTo":"09568a6500dde4a592a994b661a7beec23af32b4.1613598529.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-03-02T09:44:34Z","receivedAt":"2021-03-02T15:28:36Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 17, 2021 at 09:48:48PM +0000, Jeff Hostetler via GitGitGadget wrote:\n\n> Create t/helper/test-simple-ipc test tool to exercise the \"simple-ipc\"\n> functions.\n\nBTW, one oddity I noticed in this (because of my -Wunused-parameters\nbranch):\n\n> +#ifndef GIT_WINDOWS_NATIVE\n> +/*\n> + * This is adapted from `daemonize()`.  Use `fork()` to directly create and\n> + * run the daemon in a child process.\n> + */\n> +static int spawn_server(const char *path,\n> +\t\t\tconst struct ipc_server_opts *opts,\n> +\t\t\tpid_t *pid)\n> +{\n> +\t*pid = fork();\n> +\n> +\tswitch (*pid) {\n> +\tcase 0:\n> +\t\tif (setsid() == -1)\n> +\t\t\terror_errno(_(\"setsid failed\"));\n> +\t\tclose(0);\n> +\t\tclose(1);\n> +\t\tclose(2);\n> +\t\tsanitize_stdfds();\n> +\n> +\t\treturn ipc_server_run(path, opts, test_app_cb, (void*)&my_app_data);\n> +\n> +\tcase -1:\n> +\t\treturn error_errno(_(\"could not spawn daemon in the background\"));\n> +\n> +\tdefault:\n> +\t\treturn 0;\n> +\t}\n> +}\n\nIn the non-Windows version, we spawn a server using the \"path\" parameter\nwe got from the caller.\n\nBut in the Windows version:\n\n> +#else\n> +/*\n> + * Conceptually like `daemonize()` but different because Windows does not\n> + * have `fork(2)`.  Spawn a normal Windows child process but without the\n> + * limitations of `start_command()` and `finish_command()`.\n> + */\n> +static int spawn_server(const char *path,\n> +\t\t\tconst struct ipc_server_opts *opts,\n> +\t\t\tpid_t *pid)\n> +{\n> +\tchar test_tool_exe[MAX_PATH];\n> +\tstruct strvec args = STRVEC_INIT;\n> +\tint in, out;\n> +\n> +\tGetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);\n> +\n> +\tin = open(\"/dev/null\", O_RDONLY);\n> +\tout = open(\"/dev/null\", O_WRONLY);\n> +\n> +\tstrvec_push(&args, test_tool_exe);\n> +\tstrvec_push(&args, \"simple-ipc\");\n> +\tstrvec_push(&args, \"run-daemon\");\n> +\tstrvec_pushf(&args, \"--threads=%d\", opts->nr_threads);\n> +\n> +\t*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);\n> +\tclose(in);\n> +\tclose(out);\n> +\n> +\tstrvec_clear(&args);\n> +\n> +\tif (*pid < 0)\n> +\t\treturn error(_(\"could not spawn daemon in the background\"));\n> +\n> +\treturn 0;\n> +}\n> +#endif\n\nWe ignore the \"path\" parameter entirely. Should we be passing it along\nas an option to the child process? I think it doesn't really matter at\nthis point because both the parent and child processes will use the\nhard-coded string \"ipc-test\", but it seems like something the test\nscript might want to be able to specify.\n\n-Peff\n"},{"id":"418121","messageId":"d29f0f18-a292-8090-fe69-70576aa10506@jeffhostetler.com","threadId":"54978","inReplyTo":"YDipqpvCoE0WnJSi@coredump.intra.peff.net","subject":"Re: [PATCH v4 10/12] unix-socket: create `unix_stream_server__listen_with_lock()`","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-03-02T23:50:51Z","receivedAt":"2021-03-03T06:42:17Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/26/21 2:56 AM, Jeff King wrote:\n> On Wed, Feb 17, 2021 at 09:48:46PM +0000, Jeff Hostetler via GitGitGadget wrote:\n> \n>> From: Jeff Hostetler <jeffhost@microsoft.com>\n>>\n>> Create a version of `unix_stream_listen()` that uses a \".lock\" lockfile\n>> to create the unix domain socket in a race-free manner.\n> \n> The \"unix_stream_server__listen_with_lock\" name is quite a mouthful.  My\n> first question was: don't we have an \"options\" struct that we can use to\n> tell it we're interested in using the locking strategy?\n> \n> But I do find it a little weird for the feature to be at this layer at\n> all. I'd have thought it would make more sense in the simple-ipc layer\n> that implements the unix-socket backend, where app-level logic like\n> \"it's OK to just connect to this socket and hang up in order to ping it\"\n> might be more appropriate. We might even want to have a more robust\n> check (e.g., an actual \"ping\" that expects the server to say \"yes, I'm\n> here\").\n\nI think when I started this, the \"safe listen\" was much closer to the\noriginal `unix_stream_listen()` and it made sense to keep it nearby,\nbut as it evolved (and we added lockfiles and etc.) it grew to be more\nlike its own level between the original socket code and the simple-ipc\nlayer.  Pulling it out into its own source file is probably a good idea\nfor clarity.\n\nI was thinking that the \"ping\" is just to see if a server is listening\nor not.  (And I viewed that as kind of a hack, but it works.)  If we\nstart sending data back and forth, we get into protocols and blocking\nand stuff that this layer (even if we move it up a level) doesn't know\nabout.\n\nI'll pull this out into a new file.\n\n\n> \n> (But also see below where I am less certain about this...)\n> \n>> Unix domain sockets have a fundamental problem on Unix systems because\n>> they persist in the filesystem until they are deleted.  This is\n>> independent of whether a server is actually listening for connections.\n>> Well-behaved servers are expected to delete the socket when they\n>> shutdown.  A new server cannot easily tell if a found socket is\n>> attached to an active server or is leftover cruft from a dead server.\n>> The traditional solution used by `unix_stream_listen()` is to force\n>> delete the socket pathname and then create a new socket.  This solves\n>> the latter (cruft) problem, but in the case of the former, it orphans\n>> the existing server (by stealing the pathname associated with the\n>> socket it is listening on).\n> \n> Nicely explained.\n> \n>> We cannot directly use a .lock lockfile to create the socket because\n>> the socket is created by `bind(2)` rather than the `open(2)` mechanism\n>> used by `tempfile.c`.\n>>\n>> As an alternative, we hold a plain lockfile (\"<path>.lock\") as a\n>> mutual exclusion device.  Under the lock, we test if an existing\n>> socket (\"<path>\") is has an active server.  If not, create a new\n>> socket and begin listening.  Then we rollback the lockfile in all\n>> cases.\n> \n> Make sense.\n> \n>> +static int is_another_server_alive(const char *path,\n>> +\t\t\t\t   const struct unix_stream_listen_opts *opts)\n>> +{\n>> +\tstruct stat st;\n>> +\tint fd;\n>> +\n>> +\tif (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {\n>> +\t\t/*\n>> +\t\t * A socket-inode exists on disk at `path`, but we\n>> +\t\t * don't know whether it belongs to an active server\n>> +\t\t * or whether the last server died without cleaning\n>> +\t\t * up.\n>> +\t\t *\n>> +\t\t * Poke it with a trivial connection to try to find\n>> +\t\t * out.\n>> +\t\t */\n>> +\t\tfd = unix_stream_connect(path, opts->disallow_chdir);\n>> +\t\tif (fd >= 0) {\n>> +\t\t\tclose(fd);\n>> +\t\t\treturn 1;\n>> +\t\t}\n>> +\t}\n> \n> The lstat() seems redundant here. unix_stream_connect() will tell us\n> whether there is something to connect to or not. (It's also racy with\n> respect to the actual connect, but since you're doing this under lock, I\n> don't think that matters).\n\nI agree.  I'll get rid of the lstat().\n\n\n> \n>> +struct unix_stream_server_socket *unix_stream_server__listen_with_lock(\n>> +\tconst char *path,\n>> +\tconst struct unix_stream_listen_opts *opts)\n>> +{\n>> +\tstruct lock_file lock = LOCK_INIT;\n>> +\tint fd_socket;\n>> +\tstruct unix_stream_server_socket *server_socket;\n>> +\n>> +\t/*\n>> +\t * Create a lock at \"<path>.lock\" if we can.\n>> +\t */\n>> +\tif (hold_lock_file_for_update_timeout(&lock, path, 0,\n>> +\t\t\t\t\t      opts->timeout_ms) < 0) {\n>> +\t\terror_errno(_(\"could not lock listener socket '%s'\"), path);\n>> +\t\treturn NULL;\n>> +\t}\n> \n> Would you want to ping to see if it's alive before creating the lock?\n> That would be the fast-path if we assume that a server will usually be\n> there once started. Or is that supposed to happen in the caller (in\n> which case I'd again wonder if this really should be happening in the\n> simple-ipc code).\n\nStarting a server should not happen that often, so I'm not sure it\nmatters.  And yes, a server once started should run for a long time.\nPinging without the lock puts us back in another race, so we might as\nwell lock first.\n\n> \n>> +\t/*\n>> +\t * If another server is listening on \"<path>\" give up.  We do not\n>> +\t * want to create a socket and steal future connections from them.\n>> +\t */\n>> +\tif (is_another_server_alive(path, opts)) {\n>> +\t\terrno = EADDRINUSE;\n>> +\t\terror_errno(_(\"listener socket already in use '%s'\"), path);\n>> +\t\trollback_lock_file(&lock);\n>> +\t\treturn NULL;\n>> +\t}\n> \n> Wouldn't this be a \"success\" case for a caller? They did not open the\n> server themselves, but they are presumably happy that there is one there\n> now to talk to. So do we actually want to print an error to stderr?\n> Likewise, how do they tell the difference between this NULL and the NULL\n> we returned above because we couldn't take the lock? Or the NULL we\n> return below because there is some error creating a listening socket?\n> \n> I'd think in those three cases you'd want:\n> \n>    - if lock contention, pause a moment and wait for the winner to spin\n>      up and serve requests\n> \n>    - if another server is live while we hold the lock, then we raced them\n>      and they won. Release the lock and start using them.\n> \n>    - if we really tried to call unix_stream_listen() and that failed,\n>      give up now. There is some system error that is not likely to be\n>      fixed by trying anything more (e.g., ENAMETOOLONG).\n\nYes, I want to move the error messages out of these library layers.\n\nAnd yes, if another server is running, our server instance should\nshutdown gracefully.  Other client processes can just talk to them\nrather than us.\n\n> \n>> +\tserver_socket = xcalloc(1, sizeof(*server_socket));\n>> +\tserver_socket->path_socket = strdup(path);\n>> +\tserver_socket->fd_socket = fd_socket;\n> \n> What do we need this server_socket for? The caller already knows the\n> path; they fed it to us. We do need to return the descriptor, but we\n> could do that directly.\n\nI wanted a wrapper struct to persist a copy of the pathname near\nthe fd.  Later when we get ready to shutdown, we can close and unlink\nwithout worrying whether our caller kept their copy of the path buffer.\n\nThis also lets me have the pathname to poll and check for theft during\nthe accept thread's event loop.\n\n> \n>> +\tlstat(path, &server_socket->st_socket);\n> \n> This lstat I guess is part of your \"periodically check to see if we're\n> still the one holding the socket\" strategy. We _shouldn't_ need that\n> anymore, with the dotlocking, but I'm OK with it as a\n> belt-and-suspenders check. But why are we filling in the lstat here?\n> This seems like something that the unix-socket code doesn't really need\n> to know about (though you do at least provide the complementary\n> \"was_stolen\" function here, so that part makes sense).\n\nThe dotlock is only on disk for the duration of the socket setup.\nWe do the rollback (to delete the lockfile) once we have the socket\nopen and ready for business.\n\nThe lstat gives me the inode of the socket on disk and we can watch\nit with future lstat's in the event loop and see if it changes and\ndetect theft and auto-shutdown.\n\n> \n> Again, I guess I'd find it less weird if it were happening at a layer\n> above. Maybe I'm really just complaining that this is in unix-socket.c.\n> I guess it is a separate unix_stream_server data type. Arguably that\n> should go in a separate file, but I guess the whole conditional\n> compilation of unix-socket.c makes that awkward. So maybe this is the\n> least-bad thing.\n\nYeah, I'll move it out.\n\nAnd yes, the whole conditional compilation thing was something I was\nhesitating on, but it really isn't that bad.  (But I should not brag\nhere until all of the build servers have had their say....)\n\n> \n>> +\t/*\n>> +\t * Always rollback (just delete) \"<path>.lock\" because we already created\n>> +\t * \"<path>\" as a socket and do not want to commit_lock to do the atomic\n>> +\t * rename trick.\n>> +\t */\n>> +\trollback_lock_file(&lock);\n>> +\n>> +\treturn server_socket;\n>> +}\n> \n> OK, this part makes sense to me.\n> \n>> +void unix_stream_server__free(\n>> +\tstruct unix_stream_server_socket *server_socket)\n>> +{\n>> +\tif (!server_socket)\n>> +\t\treturn;\n>> +\n>> +\tif (server_socket->fd_socket >= 0) {\n>> +\t\tif (!unix_stream_server__was_stolen(server_socket))\n>> +\t\t\tunlink(server_socket->path_socket);\n>> +\t\tclose(server_socket->fd_socket);\n>> +\t}\n>> +\n>> +\tfree(server_socket->path_socket);\n>> +\tfree(server_socket);\n>> +}\n> \n> OK, this makes sense. We only remove it if we're still the ones holding\n> it. That's not done under lock, though, so it's possibly racy (somebody\n> steals from us while _they_ hold the lock; we check and see \"not stolen\"\n> right before they steal it, and then we unlink their stolen copy).\n\nRight, I didn't bother with the lock here.  I don't think we need it.\n\nWe technically still have the socket open and are listening on it when\nwe lstat and unlink it.  The other process should create the lock and\ntry to connect.  That should hang in the kernel because of the accept()\ngrace period.  Then we close the socket and the client's connection\nrequest errors because we didn't accept it.  They will see the error\nas no one is listening and then create their own socket.\n\n> \n>> +int unix_stream_server__was_stolen(\n>> +\tstruct unix_stream_server_socket *server_socket)\n>> +{\n>> +\tstruct stat st_now;\n>> +\n>> +\tif (!server_socket)\n>> +\t\treturn 0;\n>> +\n>> +\tif (lstat(server_socket->path_socket, &st_now) == -1)\n>> +\t\treturn 1;\n>> +\n>> +\tif (st_now.st_ino != server_socket->st_socket.st_ino)\n>> +\t\treturn 1;\n>> +\n>> +\t/* We might also consider the ctime on some platforms. */\n>> +\n>> +\treturn 0;\n>> +}\n> \n> You probably should confirm that st.dev matches, too, since that is the\n> namespace for st.ino. Maybe also double check that it's still a socket\n> with S_ISSOCK(st_mode)?\n\nGood point.\n\n> \n> -Peff\n> \n\nThanks for all the careful study.  I'll push up a new series to\naddress them shortly.\n\nJeff\n"},{"id":"418163","messageId":"3ad0d153-0f02-341e-1828-688b82a91ecf@jeffhostetler.com","threadId":"54978","inReplyTo":"YD4JAvK0epzm9b2y@coredump.intra.peff.net","subject":"Re: [PATCH v4 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-03-03T15:25:38Z","receivedAt":"2021-03-04T00:23:31Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 3/2/21 4:44 AM, Jeff King wrote:\n> On Wed, Feb 17, 2021 at 09:48:48PM +0000, Jeff Hostetler via GitGitGadget wrote:\n> \n>> Create t/helper/test-simple-ipc test tool to exercise the \"simple-ipc\"\n>> functions.\n> \n> BTW, one oddity I noticed in this (because of my -Wunused-parameters\n> branch):\n> \n>> +#ifndef GIT_WINDOWS_NATIVE\n>> +/*\n>> + * This is adapted from `daemonize()`.  Use `fork()` to directly create and\n>> + * run the daemon in a child process.\n>> + */\n>> +static int spawn_server(const char *path,\n>> +\t\t\tconst struct ipc_server_opts *opts,\n>> +\t\t\tpid_t *pid)\n>> +{\n>> +\t*pid = fork();\n>> +\n>> +\tswitch (*pid) {\n>> +\tcase 0:\n>> +\t\tif (setsid() == -1)\n>> +\t\t\terror_errno(_(\"setsid failed\"));\n>> +\t\tclose(0);\n>> +\t\tclose(1);\n>> +\t\tclose(2);\n>> +\t\tsanitize_stdfds();\n>> +\n>> +\t\treturn ipc_server_run(path, opts, test_app_cb, (void*)&my_app_data);\n>> +\n>> +\tcase -1:\n>> +\t\treturn error_errno(_(\"could not spawn daemon in the background\"));\n>> +\n>> +\tdefault:\n>> +\t\treturn 0;\n>> +\t}\n>> +}\n> \n> In the non-Windows version, we spawn a server using the \"path\" parameter\n> we got from the caller.\n> \n> But in the Windows version:\n> \n>> +#else\n>> +/*\n>> + * Conceptually like `daemonize()` but different because Windows does not\n>> + * have `fork(2)`.  Spawn a normal Windows child process but without the\n>> + * limitations of `start_command()` and `finish_command()`.\n>> + */\n>> +static int spawn_server(const char *path,\n>> +\t\t\tconst struct ipc_server_opts *opts,\n>> +\t\t\tpid_t *pid)\n>> +{\n>> +\tchar test_tool_exe[MAX_PATH];\n>> +\tstruct strvec args = STRVEC_INIT;\n>> +\tint in, out;\n>> +\n>> +\tGetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);\n>> +\n>> +\tin = open(\"/dev/null\", O_RDONLY);\n>> +\tout = open(\"/dev/null\", O_WRONLY);\n>> +\n>> +\tstrvec_push(&args, test_tool_exe);\n>> +\tstrvec_push(&args, \"simple-ipc\");\n>> +\tstrvec_push(&args, \"run-daemon\");\n>> +\tstrvec_pushf(&args, \"--threads=%d\", opts->nr_threads);\n>> +\n>> +\t*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);\n>> +\tclose(in);\n>> +\tclose(out);\n>> +\n>> +\tstrvec_clear(&args);\n>> +\n>> +\tif (*pid < 0)\n>> +\t\treturn error(_(\"could not spawn daemon in the background\"));\n>> +\n>> +\treturn 0;\n>> +}\n>> +#endif\n> \n> We ignore the \"path\" parameter entirely. Should we be passing it along\n> as an option to the child process? I think it doesn't really matter at\n> this point because both the parent and child processes will use the\n> hard-coded string \"ipc-test\", but it seems like something the test\n> script might want to be able to specify.\n> \n> -Peff\n> \n\nYeah, since it was a test helper I hesitated to add a command line\narg to pass it to the child process (when all callers were right here\nand using the same default value).  However it would be good to do so\nin case we want to write more complicated tests.\n\nJeff\n"},{"id":"418174","messageId":"xmqqr1kwhtxb.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"22eec60761a88107b2e337ce13eed1020352aa73.1613598529.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 05/12] simple-ipc: design documentation for new IPC mechanism","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-03T20:19:12Z","receivedAt":"2021-03-04T00:24:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +How the simple-ipc server is started is also outside the scope of the\n> +IPC mechanism.  For example, the server might be started during\n> +maintenance operations.\n\nJust a tiny nit.\n\nI would expect to see \"might be <re>started\" if it is followed by\n\"during maintenance operations\"; in other words, I expect \"might be\nstarted\" to be followed by \"as part of the boot-up sequence\".\n\n> +The IPC protocol consists of a single request message from the client and\n> +an optional request message from the server.  For simplicity, pkt-line\n> +routines are used to hide chunking and buffering concerns.  Each side\n> +terminates their message with a flush packet.\n> +(Documentation/technical/protocol-common.txt)\n\nHidign chunking and buffering concerns is good, but it introduces\nsome limitations, like 64k chunk limit, which probably want to be\nmentioned (if not explained or described) here.\n\nDo we give any extra meaning over \"here, a message ends\" to the\nflush packet?  The lack of \"now it is your turn to speak\" (aka\n\"delim\") has long been a weakness of the over-the-wire protocol,\nand we'd probably want to learn from the past experience.\n\n> +The actual format of the client and server messages is application\n> +specific.  The IPC layer transmits and receives an opaque buffer without\n> +any concern for the content within.\n\nPlease sell why such a semantic-agnostic layer exists and what\nbenefit the callers would get out of it.  Perhaps you offer some\nmechanism to allow them to send and receive without having to worry\nabout deadlocks[*]?\n\nTHanks.\n\n[Footnote]\n\n*1* ...just an example benefit that may or may not exist.\n\n\n"},{"id":"418175","messageId":"xmqqmtvkhswd.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"b368318e6a23f8c4e60f77a8b81b558c523d5b03.1613598529.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 07/12] unix-socket: elimiate static unix_stream_socket() helper function","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-03T20:41:22Z","receivedAt":"2021-03-04T00:24:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  int unix_stream_connect(const char *path)\n>  {\n> -\tint fd, saved_errno;\n> +\tint fd = -1, saved_errno;\n>  \tstruct sockaddr_un sa;\n>  \tstruct unix_sockaddr_context ctx;\n>  \n>  \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n>  \t\treturn -1;\n> -\tfd = unix_stream_socket();\n> +\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n> +\tif (fd < 0)\n> +\t\tgoto fail;\n> +\n>  \tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n>  \t\tgoto fail;\n>  \tunix_sockaddr_cleanup(&ctx);\n> @@ -87,15 +82,16 @@ int unix_stream_connect(const char *path)\n>  \n>  fail:\n>  \tsaved_errno = errno;\n> +\tif (fd != -1)\n> +\t\tclose(fd);\n>  \tunix_sockaddr_cleanup(&ctx);\n> -\tclose(fd);\n>  \terrno = saved_errno;\n>  \treturn -1;\n>  }\n\nSo, the difference is that the caller must be prepared to see and\nhandle error return from this function when creating socket fails,\nbut existing callers must be prepared to handle error returns from\nthis function for different reasons (e.g. we may successfully make a\nsocket, but connect may fail) already anyway, so this should be a\nfairly safe thing to do.  The sole caller send_request() in\ncredential-cache.c will relay the error return back to do_cache()\nwhich cares what errno it got, and that code does seem to care what\nkind of error caused unix_stream_connect() to fail.  And the new\nerror case introduced by this patch won't result in ENOENT or\nECONNREFUSED to cause the code to fall back to \"if the thing is not\nrunning, let's try starting it and try again\".\n\nOK.\n\n\n>  int unix_stream_listen(const char *path)\n>  {\n\nThis one is simpler to vet its caller.  It immediately dies upon any\nerror return.\n\nThanks.\n"},{"id":"418176","messageId":"xmqqim68hsb8.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"YDijhRaib5It/apG@coredump.intra.peff.net","subject":"Re: [PATCH v4 08/12] unix-socket: add backlog size option to unix_stream_listen()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-03T20:54:03Z","receivedAt":"2021-03-04T00:24:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Wed, Feb 17, 2021 at 09:48:44PM +0000, Jeff Hostetler via GitGitGadget wrote:\n>\n>> @@ -106,7 +108,10 @@ int unix_stream_listen(const char *path)\n>>  \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n>>  \t\tgoto fail;\n>>  \n>> -\tif (listen(fd, 5) < 0)\n>> +\tbacklog = opts->listen_backlog_size;\n>> +\tif (backlog <= 0)\n>> +\t\tbacklog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;\n>> +\tif (listen(fd, backlog) < 0)\n>>  \t\tgoto fail;\n\n\nLuckily there is no \"pass 0 and the platforms will choose an\nappropriate backlog value\", so \"pass 0 to get the default Git\nchooses\" is OK, but do we even want to allow passing any negative\nvalue?  Shouldn't it be diagnosed as an error instead?\n\n> OK, so we still have the fallback-on-zero here, which is good...\n>\n>> +struct unix_stream_listen_opts {\n>> +\tint listen_backlog_size;\n>> +};\n>> +\n>> +#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n>> +\n>> +#define UNIX_STREAM_LISTEN_OPTS_INIT \\\n>> +{ \\\n>> +\t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n>> +}\n>\n> ...but I thought the plan was to drop this initialization in favor of a\n> zero-initialization. What you have certainly wouldn't do the wrong\n> thing, but it just seems weirdly redundant. Unless some caller really\n> wants to know what the default will be?\n\nVery true.  The code knows the exact value input 0 has to fall back\nto; we shouldn't have to initialize to that same exact value and I\ndo not offhand see why the DEFAULT_UNIX_STREAM_LISTEN_BACKLOG needs\nto be a public constant.\n\nThanks.\n"},{"id":"418183","messageId":"xmqq1rcwjacv.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"9304ac66-f493-2150-95e3-15303c914ee3@jeffhostetler.com","subject":"Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-03T19:38:56Z","receivedAt":"2021-03-04T00:27:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff Hostetler <git@jeffhostetler.com> writes:\n\n> Right, I think it would be fine to malloc it here, but I didn't\n> want to assume that everyone would think that.\n>\n> I'll change it.\n\nI agree with both of you that the code is unnice in its stack usage\nand we want fix with malloc(), or something like that, but sorry, I\nthink I merged this round by mistake to 'next'.\n\nAs we won't be merging the topic to the upcoming release anyway, I\nam willing to revert the merge to 'next' and requeue an updated one,\nwhen it appears (I am also OK to see an incremental update, \"oops,\nno, we realize we don't want to have it on the stack\" fix-up, if\nthis is the only glitch in the series that need to be fixed).\n\nThanks.\n"},{"id":"418184","messageId":"xmqqwnuohv4t.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"e05467def4e158a5f1cfa3aafffdb5c77097859a.1613598529.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 03/12] pkt-line: (optionally) libify the packet readers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-03T19:53:06Z","receivedAt":"2021-03-04T00:27:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> @@ -313,6 +316,8 @@ static int get_packet_data(int fd, char **src_buf, \n>  \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n>  \t\t\treturn -1;\n>  \n> +\t\tif (options & PACKET_READ_NEVER_DIE)\n> +\t\t\treturn error(_(\"the remote end hung up unexpectedly\"));\n>  \t\tdie(_(\"the remote end hung up unexpectedly\"));\n>  \t}\n\nThis hunk treats READ_NEVER_DIE as a less quiet version of\nGENTRL_ON_EOF, i.e. the new flag allows to continue even after the\n\"hung up unexpectedly\" condition that usually causes the process to\ndie..\n\n> @@ -355,12 +363,19 @@ enum packet_read_status packet_read_with_status(i\n> ...\n> -\tif ((unsigned)len >= size)\n> +\tif ((unsigned)len >= size) {\n> +\t\tif (options & PACKET_READ_NEVER_DIE)\n> +\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n> +\t\t\t\t     len);\n>  \t\tdie(_(\"protocol error: bad line length %d\"), len);\n> +\t}\n>  \n>  \tif (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) {\n>  \t\t*pktlen = -1;\n\nIn the post-context of this hunk, there is this code:\n\n\tif ((options & PACKET_READ_DIE_ON_ERR_PACKET) &&\n\t    starts_with(buffer, \"ERR \"))\n\t\tdie(_(\"remote error: %s\"), buffer + 4);\n\n\t*pktlen = len;\n\treturn PACKET_READ_NORMAL;\n\nBut here, there is no way to override the DIE_ON_ERR with\nREAD_NEVER_DIE.\n\nThe asymmetry is somewhat annoying (i.e. if \"if you do not want to\ndie upon ERR, don't pass DIE_ON_ERR\" could be a valid suggestion to\nthe callers, then \"if you do not want to die upon an unexpected\nhung-up, pass GENTLE_ON_EOF\" would equally be valid suggestion),\nbut I'll let it pass.\n\n> diff --git a/pkt-line.h b/pkt-line.h\n> index a7149429ac35..2e472efaf2c5 100644\n> --- a/pkt-line.h\n> +++ b/pkt-line.h\n> @@ -75,10 +75,14 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou\n>   *\n>   * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n>   * ERR packet.\n> + *\n> + * With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except\n> + * an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect).\n>   */\n>  #define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n>  #define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n>  #define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n> +#define PACKET_READ_NEVER_DIE         (1u<<3)\n>  int packet_read(int fd, char **src_buffer, size_t *src_len, char\n>  \t\t*buffer, unsigned size, int options);\n"},{"id":"418182","messageId":"xmqq8s74jat7.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"YDlfDZcMkcfJ8N7e@coredump.intra.peff.net","subject":"Re: [PATCH v4 00/12] Simple IPC Mechanism","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-03T19:29:08Z","receivedAt":"2021-03-04T00:27:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> And by \"not interested\" I don't mean that I think the topic is without\n> value. Far from it; I think this is an important area to be working in.\n> But it's complex and time-consuming to review. So I was hoping somebody\n> with more expertise and interest in the problem space would do that part\n> of the review, and I could continue to focus on other stuff. That may be\n> wishful thinking, though. :)\n\nI was not paying close attention to this series, and was planning to\nvisit it before merging it to 'next' but only to ensure that changes\nto any existing code would not regress existing callers, so it seems\nthat we two have been with pretty much the same attitude;-)\n"},{"id":"418185","messageId":"xmqqblbzj1cs.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"1bfa36409d0706d5e22703f80bf95dfa1a313a83.1613598529.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-03T22:53:23Z","receivedAt":"2021-03-04T00:27:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n>\n> Calls to `chdir()` are dangerous in a multi-threaded context.  If\n> `unix_stream_listen()` or `unix_stream_connect()` is given a socket\n> pathname that is too long to fit in a `sockaddr_un` structure, it will\n> `chdir()` to the parent directory of the requested socket pathname,\n> create the socket using a relative pathname, and then `chdir()` back.\n> This is not thread-safe.\n>\n> Teach `unix_sockaddr_init()` to not allow calls to `chdir()` when this\n> flag is set.\n\nWhile it is clear that this will not affect any existing callers, I\nam not sure if this is a good direction to go in the longer term.\n\nI have to wonder if somebody actually relies on this \"feature\",\nthough.  As long as ENAMETOOLONG is passed back to the caller so\nthat it can react to it, any caller that knows it is safe to chdir()\nat the point of calling \"send_request()\" should be able to chdir()\nitself and come back (or fork a child that chdirs and opens a unix\ndomain socket there, and then send the file descriptor back to the\nparent process).\n\nThanks.\n"},{"id":"418231","messageId":"85cd4d20-f68d-ef1d-c95b-f34f61f906b1@jeffhostetler.com","threadId":"54978","inReplyTo":"xmqq1rcwjacv.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-03-04T13:29:27Z","receivedAt":"2021-03-04T13:30:59Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 3/3/21 2:38 PM, Junio C Hamano wrote:\n> Jeff Hostetler <git@jeffhostetler.com> writes:\n> \n>> Right, I think it would be fine to malloc it here, but I didn't\n>> want to assume that everyone would think that.\n>>\n>> I'll change it.\n> \n> I agree with both of you that the code is unnice in its stack usage\n> and we want fix with malloc(), or something like that, but sorry, I\n> think I merged this round by mistake to 'next'.\n> \n> As we won't be merging the topic to the upcoming release anyway, I\n> am willing to revert the merge to 'next' and requeue an updated one,\n> when it appears (I am also OK to see an incremental update, \"oops,\n> no, we realize we don't want to have it on the stack\" fix-up, if\n> this is the only glitch in the series that need to be fixed).\n> \n> Thanks.\n> \n\nI'm preparing a follow-on patch series to address Peff's comments\nfrom Friday/Monday and yours from yesterday.  I thought I'd send\nit as a set of new changes to sit on top of what we have in \"next\"\nif that would make things easier for you.\n\nAfter the upcoming release we can talk about whether it would be\nbetter for me to smash together the 2 series or not.\n\nJeff\n"},{"id":"418232","messageId":"6b1ce8c0-0881-77a0-deda-677e34560cc0@jeffhostetler.com","threadId":"54978","inReplyTo":"xmqqwnuohv4t.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 03/12] pkt-line: (optionally) libify the packet readers","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-03-04T14:17:41Z","receivedAt":"2021-03-04T14:19:14Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 3/3/21 2:53 PM, Junio C Hamano wrote:\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n> \n>> @@ -313,6 +316,8 @@ static int get_packet_data(int fd, char **src_buf,\n>>   \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n>>   \t\t\treturn -1;\n>>   \n>> +\t\tif (options & PACKET_READ_NEVER_DIE)\n>> +\t\t\treturn error(_(\"the remote end hung up unexpectedly\"));\n>>   \t\tdie(_(\"the remote end hung up unexpectedly\"));\n>>   \t}\n> \n> This hunk treats READ_NEVER_DIE as a less quiet version of\n> GENTRL_ON_EOF, i.e. the new flag allows to continue even after the\n> \"hung up unexpectedly\" condition that usually causes the process to\n> die..\n> \n>> @@ -355,12 +363,19 @@ enum packet_read_status packet_read_with_status(i\n>> ...\n>> -\tif ((unsigned)len >= size)\n>> +\tif ((unsigned)len >= size) {\n>> +\t\tif (options & PACKET_READ_NEVER_DIE)\n>> +\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n>> +\t\t\t\t     len);\n>>   \t\tdie(_(\"protocol error: bad line length %d\"), len);\n>> +\t}\n>>   \n>>   \tif (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) {\n>>   \t\t*pktlen = -1;\n> \n> In the post-context of this hunk, there is this code:\n> \n> \tif ((options & PACKET_READ_DIE_ON_ERR_PACKET) &&\n> \t    starts_with(buffer, \"ERR \"))\n> \t\tdie(_(\"remote error: %s\"), buffer + 4);\n> \n> \t*pktlen = len;\n> \treturn PACKET_READ_NORMAL;\n> \n> But here, there is no way to override the DIE_ON_ERR with\n> READ_NEVER_DIE.\n> \n> The asymmetry is somewhat annoying (i.e. if \"if you do not want to\n> die upon ERR, don't pass DIE_ON_ERR\" could be a valid suggestion to\n> the callers, then \"if you do not want to die upon an unexpected\n> hung-up, pass GENTLE_ON_EOF\" would equally be valid suggestion),\n> but I'll let it pass.\n\nI agree that there is something odd about all of these flags,\nbut I don't have the context on all the various caller combinations\nto make a better suggestion at this time.  And I certainly don't\nwant to stir up a bigger mess than I already have. :-)\n\nWe did document in the .h that READ_NEVER_DIE excludes ERR packets\nwhen READ_DIE_ON_ERR is set, so I think we're safe from unexpected\nsurprises.\n\n> \n>> diff --git a/pkt-line.h b/pkt-line.h\n>> index a7149429ac35..2e472efaf2c5 100644\n>> --- a/pkt-line.h\n>> +++ b/pkt-line.h\n>> @@ -75,10 +75,14 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou\n>>    *\n>>    * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n>>    * ERR packet.\n>> + *\n>> + * With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except\n>> + * an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect).\n>>    */\n>>   #define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n>>   #define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n>>   #define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n>> +#define PACKET_READ_NEVER_DIE         (1u<<3)\n>>   int packet_read(int fd, char **src_buffer, size_t *src_len, char\n>>   \t\t*buffer, unsigned size, int options);\n"},{"id":"418233","messageId":"YEDxUFJmPH4nP6Qk@coredump.intra.peff.net","threadId":"54978","inReplyTo":"6b1ce8c0-0881-77a0-deda-677e34560cc0@jeffhostetler.com","subject":"Re: [PATCH v4 03/12] pkt-line: (optionally) libify the packet readers","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-03-04T14:40:16Z","receivedAt":"2021-03-04T14:42:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Mar 04, 2021 at 09:17:41AM -0500, Jeff Hostetler wrote:\n\n> > In the post-context of this hunk, there is this code:\n> > \n> > \tif ((options & PACKET_READ_DIE_ON_ERR_PACKET) &&\n> > \t    starts_with(buffer, \"ERR \"))\n> > \t\tdie(_(\"remote error: %s\"), buffer + 4);\n> > \n> > \t*pktlen = len;\n> > \treturn PACKET_READ_NORMAL;\n> > \n> > But here, there is no way to override the DIE_ON_ERR with\n> > READ_NEVER_DIE.\n> > \n> > The asymmetry is somewhat annoying (i.e. if \"if you do not want to\n> > die upon ERR, don't pass DIE_ON_ERR\" could be a valid suggestion to\n> > the callers, then \"if you do not want to die upon an unexpected\n> > hung-up, pass GENTLE_ON_EOF\" would equally be valid suggestion),\n> > but I'll let it pass.\n> \n> I agree that there is something odd about all of these flags,\n> but I don't have the context on all the various caller combinations\n> to make a better suggestion at this time.  And I certainly don't\n> want to stir up a bigger mess than I already have. :-)\n> \n> We did document in the .h that READ_NEVER_DIE excludes ERR packets\n> when READ_DIE_ON_ERR is set, so I think we're safe from unexpected\n> surprises.\n\nI think the flag is doing sensible things; it's just that the word\n\"never\" in the name is confusing, since it is \"never except this one\ntime\".\n\nWould PACKET_READ_GENTLE_ON_READ_ERROR be a better name, to match\nGENTLE_ON_EOF? I was tempted to just call it \"ON_ERROR\", since it also\ninclude parsing errors, but maybe somebody would think that includes ERR\npackets (that is more of a stretch, though, I think).\n\nLikewise, I kind of wonder if callers would really prefer suppressing\nthe error() calls, too. Saying \"error: the remote end hung up\nunexpectedly\" is not that helpful if the \"remote end\" we are talking\nabout is fsmonitor, and not the server side of a fetch.\n\n-Peff\n"},{"id":"418235","messageId":"YED1DmLWd+ciySNa@coredump.intra.peff.net","threadId":"54978","inReplyTo":"xmqqblbzj1cs.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-03-04T14:56:14Z","receivedAt":"2021-03-04T14:57:56Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Mar 03, 2021 at 02:53:23PM -0800, Junio C Hamano wrote:\n\n> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n> > From: Jeff Hostetler <jeffhost@microsoft.com>\n> >\n> > Calls to `chdir()` are dangerous in a multi-threaded context.  If\n> > `unix_stream_listen()` or `unix_stream_connect()` is given a socket\n> > pathname that is too long to fit in a `sockaddr_un` structure, it will\n> > `chdir()` to the parent directory of the requested socket pathname,\n> > create the socket using a relative pathname, and then `chdir()` back.\n> > This is not thread-safe.\n> >\n> > Teach `unix_sockaddr_init()` to not allow calls to `chdir()` when this\n> > flag is set.\n> \n> While it is clear that this will not affect any existing callers, I\n> am not sure if this is a good direction to go in the longer term.\n> \n> I have to wonder if somebody actually relies on this \"feature\",\n> though.  As long as ENAMETOOLONG is passed back to the caller so\n> that it can react to it, any caller that knows it is safe to chdir()\n> at the point of calling \"send_request()\" should be able to chdir()\n> itself and come back (or fork a child that chdirs and opens a unix\n> domain socket there, and then send the file descriptor back to the\n> parent process).\n\nThe feature is definitely useful; I think I did 1eb10f4091 (unix-socket:\nhandle long socket pathnames, 2012-01-09) in response to a real problem.\n\nCertainly callers could handle the error themselves. The reason I pushed\nit down into the socket code was to avoid having to implement in\nmultiple callers. There are only two, but we'd have needed it in both\nsides (credential-cache--daemon as the listener, and credential-cache as\nthe client).\n\nIronically, the listening side now does a permanent chdir() to the\nsocket directory anyway, since 6e61449051 (credential-cache--daemon:\nchange to the socket dir on startup, 2016-02-23). So we could just do\nthat first, and then feed the basename to the socket code.\n\nThe client side would still need to handle it, though. It could probably\nalso chdir to the socket directory without any real downside (once\nstarted, I don't think the helper program needs to access the filesystem\nat all outside of the socket).\n\nSo I dunno. I'd be OK to just rip the feature out in favor of doing\nthose chdir()s. But that seems like a non-zero amount of work versus\nleaving, and the existing code has the benefit that if another caller\nshows up, it could benefit from the feature.\n\n-Peff\n"},{"id":"418236","messageId":"YED5N1QnnVQ6qbE6@coredump.intra.peff.net","threadId":"54978","inReplyTo":"d29f0f18-a292-8090-fe69-70576aa10506@jeffhostetler.com","subject":"Re: [PATCH v4 10/12] unix-socket: create `unix_stream_server__listen_with_lock()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-03-04T15:13:59Z","receivedAt":"2021-03-04T15:15:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Mar 02, 2021 at 06:50:51PM -0500, Jeff Hostetler wrote:\n\n> I was thinking that the \"ping\" is just to see if a server is listening\n> or not.  (And I viewed that as kind of a hack, but it works.)  If we\n> start sending data back and forth, we get into protocols and blocking\n> and stuff that this layer (even if we move it up a level) doesn't know\n> about.\n\nRight. Definitely the higher up the stack the ping happens, the more\nvalue it has. But I also see the appeal of keeping this as its own\nlayer.\n\n> > > +\tif (hold_lock_file_for_update_timeout(&lock, path, 0,\n> > > +\t\t\t\t\t      opts->timeout_ms) < 0) {\n> > > +\t\terror_errno(_(\"could not lock listener socket '%s'\"), path);\n> > > +\t\treturn NULL;\n> > > +\t}\n> > \n> > Would you want to ping to see if it's alive before creating the lock?\n> > That would be the fast-path if we assume that a server will usually be\n> > there once started. Or is that supposed to happen in the caller (in\n> > which case I'd again wonder if this really should be happening in the\n> > simple-ipc code).\n> \n> Starting a server should not happen that often, so I'm not sure it\n> matters.  And yes, a server once started should run for a long time.\n> Pinging without the lock puts us back in another race, so we might as\n> well lock first.\n\nDefinitely you need to ping under lock to avoid races. But I was\nthinking of an additional optimistic ping before we take the lock. I\nagree that starting the server should be rare, which is why I think\nthere's value in seeing \"is it up\" before taking any lock.\n\nBut I suspect your thinking is that this ping happens in the caller\nanyway, before we hit any of this unix_socket_listen() code at all.  And\nthat makes sense to me. In fact, I guess it has to happen that way,\nbecause \"try to connect\" and \"try to spin up a server\" are likely\nhappening in two separate processes entirely (we only spawn the second\none if the first one failed its ping).\n\n> > I'd think in those three cases you'd want:\n> > \n> >    - if lock contention, pause a moment and wait for the winner to spin\n> >      up and serve requests\n> > \n> >    - if another server is live while we hold the lock, then we raced them\n> >      and they won. Release the lock and start using them.\n> > \n> >    - if we really tried to call unix_stream_listen() and that failed,\n> >      give up now. There is some system error that is not likely to be\n> >      fixed by trying anything more (e.g., ENAMETOOLONG).\n> \n> Yes, I want to move the error messages out of these library layers.\n> \n> And yes, if another server is running, our server instance should\n> shutdown gracefully.  Other client processes can just talk to them\n> rather than us.\n\nRight, that makes sense. Again, I was thinking earlier of the whole \"try\nto connect, but spin up a server otherwise\" thing happening in a single\nprocess. But by the time we get to the listen code, we have probably\nalready spawned a server process, and have redirected its stderr\nsomewhere. And likewise the caller doesn't even care that much if the\nserver reports an error because it somebody else won the race. It only\ncares that after a few connect attempts it manages to talk to\n_somebody_.\n\n> > > +\tlstat(path, &server_socket->st_socket);\n> > \n> > This lstat I guess is part of your \"periodically check to see if we're\n> > still the one holding the socket\" strategy. We _shouldn't_ need that\n> > anymore, with the dotlocking, but I'm OK with it as a\n> > belt-and-suspenders check. But why are we filling in the lstat here?\n> > This seems like something that the unix-socket code doesn't really need\n> > to know about (though you do at least provide the complementary\n> > \"was_stolen\" function here, so that part makes sense).\n> \n> The dotlock is only on disk for the duration of the socket setup.\n> We do the rollback (to delete the lockfile) once we have the socket\n> open and ready for business.\n> \n> The lstat gives me the inode of the socket on disk and we can watch\n> it with future lstat's in the event loop and see if it changes and\n> detect theft and auto-shutdown.\n\nRight, I gradually came to the understanding of what your extra layer\nwas trying to accomplish while reading (sometimes I'll go back and edit\nearlier comments in my review before sending out the mail, but in this\ncase it seemed less confusing to leave my train of thought in place.\nThat might not have been correct, though. ;) ).\n\nI think if everybody is abiding by the lock system to create the socket,\nwe probably don't strictly _need_ the theft detection. But it might not\nhurt as a belt-and-suspenders, or for cases where somebody thinks the\nsocket is stale but it isn't (perhaps due to listen backlog or\nsomething while trying to do the connect() ping).\n\n> > > +void unix_stream_server__free(\n> > > +\tstruct unix_stream_server_socket *server_socket)\n> > > +{\n> > > +\tif (!server_socket)\n> > > +\t\treturn;\n> > > +\n> > > +\tif (server_socket->fd_socket >= 0) {\n> > > +\t\tif (!unix_stream_server__was_stolen(server_socket))\n> > > +\t\t\tunlink(server_socket->path_socket);\n> > > +\t\tclose(server_socket->fd_socket);\n> > > +\t}\n> > > +\n> > > +\tfree(server_socket->path_socket);\n> > > +\tfree(server_socket);\n> > > +}\n> > \n> > OK, this makes sense. We only remove it if we're still the ones holding\n> > it. That's not done under lock, though, so it's possibly racy (somebody\n> > steals from us while _they_ hold the lock; we check and see \"not stolen\"\n> > right before they steal it, and then we unlink their stolen copy).\n> \n> Right, I didn't bother with the lock here.  I don't think we need it.\n> \n> We technically still have the socket open and are listening on it when\n> we lstat and unlink it.  The other process should create the lock and\n> try to connect.  That should hang in the kernel because of the accept()\n> grace period.  Then we close the socket and the client's connection\n> request errors because we didn't accept it.  They will see the error\n> as no one is listening and then create their own socket.\n\nI think there are still some races (at least if we believe that anything\ncan be stolen in the first place). Something like:\n\n  - process A holds the socket but plans to exit\n\n  - process B takes the lock\n\n  - process B tries to ping us, but it doesn't work for some reason\n    (this part is vague, but it's also the thing that makes stealing\n    possible at all)\n\n  - process A calls was_stolen(), which says \"no\"\n\n  - process B decides nobody is there, so it unlinks the socket and\n    creates its own\n\n  - process A calls unlink(), removing B's socket\n\nA is OK with this; it was exiting anyway. But it just stranded B, who\n_thinks_ it owns the socket, but doesn't.\n\nAgain, there's a vagueness to \"B somehow doesn't see A as listening\" in\nthe middle step. But without that step, I don't see how you'd really\nhave stealing in the first place.\n\n-Peff\n"},{"id":"418255","messageId":"xmqqim66ekck.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"85cd4d20-f68d-ef1d-c95b-f34f61f906b1@jeffhostetler.com","subject":"Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-04T20:26:35Z","receivedAt":"2021-03-04T20:28:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff Hostetler <git@jeffhostetler.com> writes:\n\n> On 3/3/21 2:38 PM, Junio C Hamano wrote:\n>\n>> I agree with both of you that the code is unnice in its stack usage\n>> and we want fix with malloc(), or something like that, but sorry, I\n>> think I merged this round by mistake to 'next'.\n>> As we won't be merging the topic to the upcoming release anyway, I\n>> am willing to revert the merge to 'next' and requeue an updated one,\n>> when it appears (I am also OK to see an incremental update, \"oops,\n>> no, we realize we don't want to have it on the stack\" fix-up, if\n>> this is the only glitch in the series that need to be fixed).\n>\n> I'm preparing a follow-on patch series to address Peff's comments\n> from Friday/Monday and yours from yesterday.  I thought I'd send\n> it as a set of new changes to sit on top of what we have in \"next\"\n> if that would make things easier for you.\n\nYeah, that is OK, too.  Sorry for the mistake of merging it too\nearly.\n\n"},{"id":"418256","messageId":"xmqqeeguek9n.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"YEDxUFJmPH4nP6Qk@coredump.intra.peff.net","subject":"Re: [PATCH v4 03/12] pkt-line: (optionally) libify the packet readers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-04T20:28:20Z","receivedAt":"2021-03-04T20:29:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I think the flag is doing sensible things; it's just that the word\n> \"never\" in the name is confusing, since it is \"never except this one\n> time\".\n>\n> Would PACKET_READ_GENTLE_ON_READ_ERROR be a better name, to match\n> GENTLE_ON_EOF? I was tempted to just call it \"ON_ERROR\", since it also\n> include parsing errors, but maybe somebody would think that includes ERR\n> packets (that is more of a stretch, though, I think).\n>\n> Likewise, I kind of wonder if callers would really prefer suppressing\n> the error() calls, too. Saying \"error: the remote end hung up\n> unexpectedly\" is not that helpful if the \"remote end\" we are talking\n> about is fsmonitor, and not the server side of a fetch.\n\nBoth sounds sensible.\n"},{"id":"418257","messageId":"xmqqa6riejyp.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"YED1DmLWd+ciySNa@coredump.intra.peff.net","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-04T20:34:54Z","receivedAt":"2021-03-04T20:38:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> The feature is definitely useful; I think I did 1eb10f4091 (unix-socket:\n> handle long socket pathnames, 2012-01-09) in response to a real problem.\n>\n> Certainly callers could handle the error themselves. The reason I pushed\n> it down into the socket code was to avoid having to implement in\n> multiple callers. There are only two, but we'd have needed it in both\n> sides (credential-cache--daemon as the listener, and credential-cache as\n> the client).\n>\n> Ironically, the listening side now does a permanent chdir() to the\n> socket directory anyway, since 6e61449051 (credential-cache--daemon:\n> change to the socket dir on startup, 2016-02-23). So we could just do\n> that first, and then feed the basename to the socket code.\n>\n> The client side would still need to handle it, though. It could probably\n> also chdir to the socket directory without any real downside (once\n> started, I don't think the helper program needs to access the filesystem\n> at all outside of the socket).\n>\n> So I dunno. I'd be OK to just rip the feature out in favor of doing\n> those chdir()s. But that seems like a non-zero amount of work versus\n> leaving, and the existing code has the benefit that if another caller\n> shows up, it could benefit from the feature.\n\nI am OK to keep the series as-is, and leave it to a possible future\nwork to remove the need for chdir even for long paths and not having\nto return an error with ENAMETOOLONG; when such an update happens,\nthe \"fail if need to chdir\" feature this patch is adding will become\na no-op.\n\n"},{"id":"418274","messageId":"xmqqtupqbij4.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"xmqqa6riejyp.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-04T23:34:07Z","receivedAt":"2021-03-04T23:34:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> So I dunno. I'd be OK to just rip the feature out in favor of doing\n>> those chdir()s. But that seems like a non-zero amount of work versus\n>> leaving, and the existing code has the benefit that if another caller\n>> shows up, it could benefit from the feature.\n>\n> I am OK to keep the series as-is, and leave it to a possible future\n> work to remove the need for chdir even for long paths and not having\n> to return an error with ENAMETOOLONG; when such an update happens,\n> the \"fail if need to chdir\" feature this patch is adding will become\n> a no-op.\n\nFor example, as this is UNIX-only codepath, I wonder if something\nlike this would be a good way to avoid chdir() that would cause\ntrouble.\n\n    - obtain a fd from socket(2)\n    - check if path is too long to fit in sa->sun_path\n      - if it does, bind(2) the fd to the address\n      - if it does not, fork(2) a child and\n        - in the child, chdir(2) there and use the shortened path\n\t  to bind(2), and exit(3)\n        - the parents just wait(2)s for the child to return. By the\n          time it dies, the fd would be successfully bound to the\n\t  path.\n    - now we have a file descriptor that is bound at that path.\n\n"},{"id":"418306","messageId":"YEHzmIOYgRtI1Ak1@coredump.intra.peff.net","threadId":"54978","inReplyTo":"xmqqtupqbij4.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-03-05T09:02:16Z","receivedAt":"2021-03-05T09:02:59Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Mar 04, 2021 at 03:34:07PM -0800, Junio C Hamano wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> >> So I dunno. I'd be OK to just rip the feature out in favor of doing\n> >> those chdir()s. But that seems like a non-zero amount of work versus\n> >> leaving, and the existing code has the benefit that if another caller\n> >> shows up, it could benefit from the feature.\n> >\n> > I am OK to keep the series as-is, and leave it to a possible future\n> > work to remove the need for chdir even for long paths and not having\n> > to return an error with ENAMETOOLONG; when such an update happens,\n> > the \"fail if need to chdir\" feature this patch is adding will become\n> > a no-op.\n> \n> For example, as this is UNIX-only codepath, I wonder if something\n> like this would be a good way to avoid chdir() that would cause\n> trouble.\n> \n>     - obtain a fd from socket(2)\n>     - check if path is too long to fit in sa->sun_path\n>       - if it does, bind(2) the fd to the address\n>       - if it does not, fork(2) a child and\n>         - in the child, chdir(2) there and use the shortened path\n> \t  to bind(2), and exit(3)\n>         - the parents just wait(2)s for the child to return. By the\n>           time it dies, the fd would be successfully bound to the\n> \t  path.\n>     - now we have a file descriptor that is bound at that path.\n\nIf the trouble is that chdir() isn't thread-safe, I wonder if fork()\ncreates its own headaches. :) I guess libc usually takes care of the\nbasics with pthread_atfork(), etc, and the child otherwise would not\nneed to access much data.\n\nI don't know offhand if this trick actually works. I can imagine it\ndoes, but it hinges on the subtlety between an integer descriptor and\nthe underlying \"file description\" (the term used in POSIX). Does binding\na socket operate on the former (like close() does not close the parent's\ndescriptor) or the latter (like lseek() impacts other descriptors).\n\nI'd guess the latter, but I wasn't sure if you were suggesting this from\nexperience or if you just invented the technique. ;)\n\n-Peff\n"},{"id":"418307","messageId":"YEH5AUxgFxWTxb6u@coredump.intra.peff.net","threadId":"54978","inReplyTo":"YEHzmIOYgRtI1Ak1@coredump.intra.peff.net","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-03-05T09:25:21Z","receivedAt":"2021-03-05T09:26:07Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 05, 2021 at 04:02:16AM -0500, Jeff King wrote:\n\n> I don't know offhand if this trick actually works. I can imagine it\n> does, but it hinges on the subtlety between an integer descriptor and\n> the underlying \"file description\" (the term used in POSIX). Does binding\n> a socket operate on the former (like close() does not close the parent's\n> descriptor) or the latter (like lseek() impacts other descriptors).\n> \n> I'd guess the latter, but I wasn't sure if you were suggesting this from\n> experience or if you just invented the technique. ;)\n\nI was curious, but this does indeed work:\n\n-- >8 --\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netdb.h>\n#include <sys/wait.h>\n#include <unistd.h>\n#include <stdlib.h>\n\nint main(void)\n{\n\tint listen_fd, client_fd;\n\tstruct addrinfo *ai;\n\tpid_t pid;\n\n\tgetaddrinfo(\"127.0.0.1\", \"1234\", NULL, &ai);\n\tlisten_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);\n\tpid = fork();\n\tif (!pid) {\n\t\tbind(listen_fd, ai->ai_addr, ai->ai_addrlen);\n\t\treturn 0;\n\t}\n\twaitpid(pid, NULL, 0);\n\n\tlisten(listen_fd, 5);\n\tclient_fd = accept(listen_fd, NULL, NULL);\n\twrite(client_fd, \"foo\\n\", 4);\n\treturn 0;\n}\n-- >8 --\n\n-Peff\n"},{"id":"418311","messageId":"CAPx1GvfXO9Xd+9Fqp-M13WUUNWVtemWm__O4N5WUk7=s4up1Gg@mail.gmail.com","threadId":"54978","inReplyTo":"YEH5AUxgFxWTxb6u@coredump.intra.peff.net","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2021-03-05T11:59:57Z","receivedAt":"2021-03-05T12:01:15Z","isPatch":true,"sender":{"key":"chris.torek@gmail.com","avatar":"https://avatars.githubusercontent.com/u/16826774?v=4"},"body":"> On Fri, Mar 05, 2021 at 04:02:16AM -0500, Jeff King wrote:\n>\n> > I don't know offhand if this [bind in a child] trick actually works. ...\n\nOn Fri, Mar 5, 2021 at 1:29 AM Jeff King <peff@peff.net> wrote:\n> I was curious, but this does indeed work:\n[working example snipped]\n\nYes, it definitely works.  The bind() call, on a Unix domain socket,\ncreates a file system entity linked to the underlying socket instance.\nThe file descriptors, in whatever processes have them, provide\nread/write/send/recv/etc linkage to the underlying socket instance\n(and also a refcount or other GC protection: with the ability to\nsend sockets over sockets, simple refcounts stop working and we\nneed real GC in the kernel...).\n\nOf course, once all the file descriptor references are gone, the\nsocket (eventually, depending on GC) evaporates.  The file system\nentity does not count for keeping the underlying socket alive.  At\nthis point the file system entity is \"dead\".  Unfortunately there's no\nway to test and clean out the dead entity atomically.  The whole\nthing is kind of a mess.\n\nChris\n"},{"id":"418327","messageId":"449a73be-52e3-8363-b771-959f8114e3a0@jeffhostetler.com","threadId":"54978","inReplyTo":"CAPx1GvfXO9Xd+9Fqp-M13WUUNWVtemWm__O4N5WUk7=s4up1Gg@mail.gmail.com","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-03-05T17:33:11Z","receivedAt":"2021-03-05T17:34:11Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 3/5/21 6:59 AM, Chris Torek wrote:\n>> On Fri, Mar 05, 2021 at 04:02:16AM -0500, Jeff King wrote:\n>>\n>>> I don't know offhand if this [bind in a child] trick actually works. ...\n> \n> On Fri, Mar 5, 2021 at 1:29 AM Jeff King <peff@peff.net> wrote:\n>> I was curious, but this does indeed work:\n> [working example snipped]\n> \n> Yes, it definitely works.  The bind() call, on a Unix domain socket,\n> creates a file system entity linked to the underlying socket instance.\n> The file descriptors, in whatever processes have them, provide\n> read/write/send/recv/etc linkage to the underlying socket instance\n> (and also a refcount or other GC protection: with the ability to\n> send sockets over sockets, simple refcounts stop working and we\n> need real GC in the kernel...).\n> \n> Of course, once all the file descriptor references are gone, the\n> socket (eventually, depending on GC) evaporates.  The file system\n> entity does not count for keeping the underlying socket alive.  At\n> this point the file system entity is \"dead\".  Unfortunately there's no\n> way to test and clean out the dead entity atomically.  The whole\n> thing is kind of a mess.\n> \n> Chris\n> \n\nThe original problem was that chdir() is not safe in a multi-threaded\nprocess because one thread calling chdir() will affect any concurrent\nfile operations (open(), mkdir(), etc.) that use relative paths.\n\nI think Adding a fork() at this layer would just create new types of \nproblems.  For example, if another thread was concurrently writing to\na socket while we were setting up this new socket, we would suddenly\nhave 1 thread in each process now writing to that socket and the\nreceiver would get a mixture of output from both processes.  Right?\n\nJeff\n\n\n"},{"id":"418328","messageId":"xmqqczwdbi80.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"449a73be-52e3-8363-b771-959f8114e3a0@jeffhostetler.com","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-05T17:53:03Z","receivedAt":"2021-03-05T17:53:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff Hostetler <git@jeffhostetler.com> writes:\n\n> The original problem was that chdir() is not safe in a multi-threaded\n> process because one thread calling chdir() will affect any concurrent\n> file operations (open(), mkdir(), etc.) that use relative paths.\n>\n> I think Adding a fork() at this layer would just create new types of\n> problems.  For example, if another thread was concurrently writing to\n> a socket while we were setting up this new socket, we would suddenly\n> have 1 thread in each process now writing to that socket and the\n> receiver would get a mixture of output from both processes.  Right?\n\ncf. https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html\n\nThe fork() function shall create a new process. The new process\n(child process) shall be an exact copy of the calling process\n(parent process) except as detailed below:\n\n...\n\n * A process shall be created with a single thread. If a\n   multi-threaded process calls fork(), the new process shall\n   contain a replica of the calling thread and its entire address\n   space, possibly including the states of mutexes and other\n   resources. Consequently, to avoid errors, the child process may\n   only execute async-signal-safe operations until such time as one\n   of the exec functions is called.\n\nSo, probably not.\n"},{"id":"418341","messageId":"ed933c34-85d2-c238-122f-13751a11ca7f@jeffhostetler.com","threadId":"54978","inReplyTo":"xmqqa6riejyp.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-03-05T21:30:57Z","receivedAt":"2021-03-05T21:31:55Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 3/4/21 3:34 PM, Junio C Hamano wrote:\n> Jeff King <peff@peff.net> writes:\n> \n>> The feature is definitely useful; I think I did 1eb10f4091 (unix-socket:\n>> handle long socket pathnames, 2012-01-09) in response to a real problem.\n>>\n>> Certainly callers could handle the error themselves. The reason I pushed\n>> it down into the socket code was to avoid having to implement in\n>> multiple callers. There are only two, but we'd have needed it in both\n>> sides (credential-cache--daemon as the listener, and credential-cache as\n>> the client).\n>>\n>> Ironically, the listening side now does a permanent chdir() to the\n>> socket directory anyway, since 6e61449051 (credential-cache--daemon:\n>> change to the socket dir on startup, 2016-02-23). So we could just do\n>> that first, and then feed the basename to the socket code.\n>>\n>> The client side would still need to handle it, though. It could probably\n>> also chdir to the socket directory without any real downside (once\n>> started, I don't think the helper program needs to access the filesystem\n>> at all outside of the socket).\n>>\n>> So I dunno. I'd be OK to just rip the feature out in favor of doing\n>> those chdir()s. But that seems like a non-zero amount of work versus\n>> leaving, and the existing code has the benefit that if another caller\n>> shows up, it could benefit from the feature.\n> \n> I am OK to keep the series as-is, and leave it to a possible future\n> work to remove the need for chdir even for long paths and not having\n> to return an error with ENAMETOOLONG; when such an update happens,\n> the \"fail if need to chdir\" feature this patch is adding will become\n> a no-op.\n> \n\nI think I'd like to keep things as I have them now with the \"disallow\nchdir()\" option bit and save the \"fork() / bind()\" solution for a\nlater patch series.  Simple IPC is large enough as it is and the new\nENAMETOOLONG error will only affect callers who set the bit.  A later\npatch series can easily test and confirm the \"fork() / bind() solution\nin isolation and test it on the other Unix hosts and then remove the\nbit from those callers (if we want).\n\nJeff\n"},{"id":"418346","messageId":"xmqq5z259skb.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"ed933c34-85d2-c238-122f-13751a11ca7f@jeffhostetler.com","subject":"Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-05T21:52:36Z","receivedAt":"2021-03-05T21:53:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff Hostetler <git@jeffhostetler.com> writes:\n\n>> I am OK to keep the series as-is, and leave it to a possible future\n>> work to remove the need for chdir even for long paths and not having\n>> to return an error with ENAMETOOLONG; when such an update happens,\n>> the \"fail if need to chdir\" feature this patch is adding will become\n>> a no-op.\n>\n> I think I'd like to keep things as I have them now with the \"disallow\n> chdir()\" option bit\n\nSo we are on the same page.\n\n> and save the \"fork() / bind()\" solution for a\n> later patch series.  Simple IPC is large enough as it is and the new\n> ENAMETOOLONG error will only affect callers who set the bit.  A later\n> patch series can easily test and confirm the \"fork() / bind() solution\n> in isolation and test it on the other Unix hosts and then remove the\n> bit from those callers (if we want).\n\nThe bit will then become an unused API relic, but that is OK (I do\nnot think fork/bind would be the best and/or only way to avoid\nchdir, though, but it won't matter in the context ofh tis\ndiscussion).\n\n"},{"id":"418592","messageId":"311ea4a5cd71c5dd2407348ad4608d2f7dd77ce5.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:26Z","receivedAt":"2021-03-09T15:03:22Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nTeach `packet_write_gently()` to write the pkt-line header and the actual\nbuffer in 2 separate calls to `write_in_full()` and avoid the need for a\nstatic buffer, thread-safe scratch space, or an excessively large stack\nbuffer.\n\nChange `write_packetized_from_fd()` to allocate a temporary buffer rather\nthan using a static buffer to avoid similar issues here.\n\nThese changes are intended to make it easier to use pkt-line routines in\na multi-threaded context with multiple concurrent writers writing to\ndifferent streams.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n pkt-line.c | 27 +++++++++++++++++++--------\n 1 file changed, 19 insertions(+), 8 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex d633005ef746..8b3512190442 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -196,17 +196,25 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)\n \n static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n {\n-\tstatic char packet_write_buffer[LARGE_PACKET_MAX];\n+\tchar header[4];\n \tsize_t packet_size;\n \n-\tif (size > sizeof(packet_write_buffer) - 4)\n+\tif (size > LARGE_PACKET_DATA_MAX)\n \t\treturn error(_(\"packet write failed - data exceeds max packet size\"));\n \n \tpacket_trace(buf, size, 1);\n \tpacket_size = size + 4;\n-\tset_packet_header(packet_write_buffer, packet_size);\n-\tmemcpy(packet_write_buffer + 4, buf, size);\n-\tif (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)\n+\n+\tset_packet_header(header, packet_size);\n+\n+\t/*\n+\t * Write the header and the buffer in 2 parts so that we do not need\n+\t * to allocate a buffer or rely on a static buffer.  This avoids perf\n+\t * and multi-threading issues.\n+\t */\n+\n+\tif (write_in_full(fd_out, header, 4) < 0 ||\n+\t    write_in_full(fd_out, buf, size) < 0)\n \t\treturn error(_(\"packet write failed\"));\n \treturn 0;\n }\n@@ -244,20 +252,23 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \n int write_packetized_from_fd(int fd_in, int fd_out)\n {\n-\tstatic char buf[LARGE_PACKET_DATA_MAX];\n+\tchar *buf = xmalloc(LARGE_PACKET_DATA_MAX);\n \tint err = 0;\n \tssize_t bytes_to_write;\n \n \twhile (!err) {\n-\t\tbytes_to_write = xread(fd_in, buf, sizeof(buf));\n-\t\tif (bytes_to_write < 0)\n+\t\tbytes_to_write = xread(fd_in, buf, LARGE_PACKET_DATA_MAX);\n+\t\tif (bytes_to_write < 0) {\n+\t\t\tfree(buf);\n \t\t\treturn COPY_READ_ERROR;\n+\t\t}\n \t\tif (bytes_to_write == 0)\n \t\t\tbreak;\n \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n \t}\n \tif (!err)\n \t\terr = packet_flush_gently(fd_out);\n+\tfree(buf);\n \treturn err;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"418593","messageId":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v4.git.1613598529.gitgitgadget@gmail.com","subject":"[PATCH v5 00/12] Simple IPC Mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:25Z","receivedAt":"2021-03-09T15:03:22Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"Here is V5 of my \"Simple IPC\" series. This version is a reiteration of the\nseries and combines the original \"Simple IPC Mechanism V4\" series [1] and\nthe \"Simple IPC Cleanups V1\" series [2]. It squashes them together and then\nincludes responses to last minute comments received on both.\n\nThese include: (a) Rename PACKET_READ_NEVER_DIE to\nPACKET_READ_GENTLE_ON_READ_ERROR. (b) Accept zero length timeout in\nunix_stream_socket__conect(). Only supply the default timeout when a\nnegative value is passed. Make the default timeout value private to the .c\nfile.\n\nI apologize for rerolling something that is in \"next\". I think the combined\nresult is better long term than preserving them as two sequential series.\nGiven where we are in the release cycle, I thought it best to have a cleaned\nup series for consideration post v2.31.\n\n[1] Upstream in jh/simple-ipc via\nhttps://github.com/gitgitgadget/git/pull/766 (and in \"next\" relative to\nv2.30.1)\nhttps://lore.kernel.org/git/pull.766.v4.git.1613598529.gitgitgadget@gmail.com/T/#mbd1da5ff93ef273049090f697aeab68c74f698f1\n\n[2] Upstream in `jh/simple-ipc-cleanups via\nhttps://github.com/gitgitgadget/git/pull/893\nhttps://lore.kernel.org/git/8ea6401c-6ee6-94cb-4e33-9dfffaf466e8@jeffhostetler.com/T/#t\n\nJeff Hostetler (9):\n  pkt-line: eliminate the need for static buffer in\n    packet_write_gently()\n  simple-ipc: design documentation for new IPC mechanism\n  simple-ipc: add win32 implementation\n  unix-socket: eliminate static unix_stream_socket() helper function\n  unix-socket: add backlog size option to unix_stream_listen()\n  unix-socket: disallow chdir() when creating unix domain sockets\n  unix-stream-server: create unix domain socket under lock\n  simple-ipc: add Unix domain socket implementation\n  t0052: add simple-ipc tests and t/helper/test-simple-ipc tool\n\nJohannes Schindelin (3):\n  pkt-line: do not issue flush packets in write_packetized_*()\n  pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option\n  pkt-line: add options argument to read_packetized_to_strbuf()\n\n Documentation/technical/api-simple-ipc.txt | 105 +++\n Makefile                                   |   9 +\n builtin/credential-cache--daemon.c         |   3 +-\n builtin/credential-cache.c                 |   2 +-\n compat/simple-ipc/ipc-shared.c             |  28 +\n compat/simple-ipc/ipc-unix-socket.c        | 986 +++++++++++++++++++++\n compat/simple-ipc/ipc-win32.c              | 751 ++++++++++++++++\n config.mak.uname                           |   2 +\n contrib/buildsystems/CMakeLists.txt        |   8 +-\n convert.c                                  |  11 +-\n pkt-line.c                                 |  58 +-\n pkt-line.h                                 |  17 +-\n simple-ipc.h                               | 239 +++++\n t/helper/test-simple-ipc.c                 | 787 ++++++++++++++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t0052-simple-ipc.sh                      | 122 +++\n unix-socket.c                              |  53 +-\n unix-socket.h                              |  12 +-\n unix-stream-server.c                       | 128 +++\n unix-stream-server.h                       |  36 +\n 21 files changed, 3307 insertions(+), 52 deletions(-)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n create mode 100644 unix-stream-server.c\n create mode 100644 unix-stream-server.h\n\n\nbase-commit: f01623b2c9d14207e497b21ebc6b3ec4afaf4b46\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/766\n\nRange-diff vs v4:\n\n  1:  2d6858b1625a !  1:  311ea4a5cd71 pkt-line: eliminate the need for static buffer in packet_write_gently()\n     @@ Commit message\n          static buffer, thread-safe scratch space, or an excessively large stack\n          buffer.\n      \n     -    Change the API of `write_packetized_from_fd()` to accept a scratch space\n     -    argument from its caller to avoid similar issues here.\n     +    Change `write_packetized_from_fd()` to allocate a temporary buffer rather\n     +    than using a static buffer to avoid similar issues here.\n      \n          These changes are intended to make it easier to use pkt-line routines in\n          a multi-threaded context with multiple concurrent writers writing to\n     @@ Commit message\n      \n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n     - ## convert.c ##\n     -@@ convert.c: static int apply_multi_file_filter(const char *path, const char *src, size_t len\n     - \tif (err)\n     - \t\tgoto done;\n     - \n     --\tif (fd >= 0)\n     --\t\terr = write_packetized_from_fd(fd, process->in);\n     --\telse\n     -+\tif (fd >= 0) {\n     -+\t\tstruct packet_scratch_space scratch;\n     -+\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n     -+\t} else\n     - \t\terr = write_packetized_from_buf(src, len, process->in);\n     - \tif (err)\n     - \t\tgoto done;\n     -\n       ## pkt-line.c ##\n      @@ pkt-line.c: int packet_write_fmt_gently(int fd, const char *fmt, ...)\n       \n     @@ pkt-line.c: int packet_write_fmt_gently(int fd, const char *fmt, ...)\n       \treturn 0;\n       }\n      @@ pkt-line.c: void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n     - \tpacket_trace(data, len, 1);\n     - }\n       \n     --int write_packetized_from_fd(int fd_in, int fd_out)\n     -+int write_packetized_from_fd(int fd_in, int fd_out,\n     -+\t\t\t     struct packet_scratch_space *scratch)\n     + int write_packetized_from_fd(int fd_in, int fd_out)\n       {\n      -\tstatic char buf[LARGE_PACKET_DATA_MAX];\n     ++\tchar *buf = xmalloc(LARGE_PACKET_DATA_MAX);\n       \tint err = 0;\n       \tssize_t bytes_to_write;\n       \n       \twhile (!err) {\n      -\t\tbytes_to_write = xread(fd_in, buf, sizeof(buf));\n     -+\t\tbytes_to_write = xread(fd_in, scratch->buffer,\n     -+\t\t\t\t       sizeof(scratch->buffer));\n     - \t\tif (bytes_to_write < 0)\n     +-\t\tif (bytes_to_write < 0)\n     ++\t\tbytes_to_write = xread(fd_in, buf, LARGE_PACKET_DATA_MAX);\n     ++\t\tif (bytes_to_write < 0) {\n     ++\t\t\tfree(buf);\n       \t\t\treturn COPY_READ_ERROR;\n     ++\t\t}\n       \t\tif (bytes_to_write == 0)\n       \t\t\tbreak;\n     --\t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n     -+\t\terr = packet_write_gently(fd_out, scratch->buffer,\n     -+\t\t\t\t\t  bytes_to_write);\n     + \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n       \t}\n       \tif (!err)\n       \t\terr = packet_flush_gently(fd_out);\n     -\n     - ## pkt-line.h ##\n     -@@\n     - #include \"strbuf.h\"\n     - #include \"sideband.h\"\n     - \n     -+#define LARGE_PACKET_MAX 65520\n     -+#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)\n     -+\n     -+struct packet_scratch_space {\n     -+\tchar buffer[LARGE_PACKET_DATA_MAX]; /* does not include header bytes */\n     -+};\n     -+\n     - /*\n     -  * Write a packetized stream, where each line is preceded by\n     -  * its length (including the header) as a 4-byte hex number.\n     -@@ pkt-line.h: void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f\n     - void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n     - int packet_flush_gently(int fd);\n     - int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n     --int write_packetized_from_fd(int fd_in, int fd_out);\n     -+int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n     - int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n     - \n     - /*\n     -@@ pkt-line.h: enum packet_read_status packet_reader_read(struct packet_reader *reader);\n     - enum packet_read_status packet_reader_peek(struct packet_reader *reader);\n     - \n     - #define DEFAULT_PACKET_MAX 1000\n     --#define LARGE_PACKET_MAX 65520\n     --#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)\n     -+\n     - extern char packet_buffer[LARGE_PACKET_MAX];\n     ++\tfree(buf);\n     + \treturn err;\n     + }\n       \n     - struct packet_writer {\n  2:  91a9f63d6692 !  2:  25157c1f4873 pkt-line: do not issue flush packets in write_packetized_*()\n     @@ Commit message\n      \n       ## convert.c ##\n      @@ convert.c: static int apply_multi_file_filter(const char *path, const char *src, size_t len\n     + \t\tgoto done;\n       \n     - \tif (fd >= 0) {\n     - \t\tstruct packet_scratch_space scratch;\n     --\t\terr = write_packetized_from_fd(fd, process->in, &scratch);\n     -+\t\terr = write_packetized_from_fd_no_flush(fd, process->in, &scratch);\n     - \t} else\n     + \tif (fd >= 0)\n     +-\t\terr = write_packetized_from_fd(fd, process->in);\n     ++\t\terr = write_packetized_from_fd_no_flush(fd, process->in);\n     + \telse\n      -\t\terr = write_packetized_from_buf(src, len, process->in);\n      +\t\terr = write_packetized_from_buf_no_flush(src, len, process->in);\n      +\tif (err)\n     @@ pkt-line.c: void packet_buf_write_len(struct strbuf *buf, const char *data, size\n       \tpacket_trace(data, len, 1);\n       }\n       \n     --int write_packetized_from_fd(int fd_in, int fd_out,\n     --\t\t\t     struct packet_scratch_space *scratch)\n     -+int write_packetized_from_fd_no_flush(int fd_in, int fd_out,\n     -+\t\t\t\t      struct packet_scratch_space *scratch)\n     +-int write_packetized_from_fd(int fd_in, int fd_out)\n     ++int write_packetized_from_fd_no_flush(int fd_in, int fd_out)\n       {\n     + \tchar *buf = xmalloc(LARGE_PACKET_DATA_MAX);\n       \tint err = 0;\n     - \tssize_t bytes_to_write;\n     -@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out,\n     - \t\terr = packet_write_gently(fd_out, scratch->buffer,\n     - \t\t\t\t\t  bytes_to_write);\n     +@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out)\n     + \t\t\tbreak;\n     + \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n       \t}\n      -\tif (!err)\n      -\t\terr = packet_flush_gently(fd_out);\n     + \tfree(buf);\n       \treturn err;\n       }\n       \n     @@ pkt-line.h: void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __at\n       void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n       int packet_flush_gently(int fd);\n       int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n     --int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n     +-int write_packetized_from_fd(int fd_in, int fd_out);\n      -int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n     -+int write_packetized_from_fd_no_flush(int fd_in, int fd_out, struct packet_scratch_space *scratch);\n     ++int write_packetized_from_fd_no_flush(int fd_in, int fd_out);\n      +int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);\n       \n       /*\n  3:  e05467def4e1 !  3:  af3d13113bc9 pkt-line: (optionally) libify the packet readers\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    pkt-line: (optionally) libify the packet readers\n     +    pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option\n     +\n     +    Introduce PACKET_READ_GENTLE_ON_READ_ERROR option to help libify the\n     +    packet readers.\n      \n          So far, the (possibly indirect) callers of `get_packet_data()` can ask\n          that function to return an error instead of `die()`ing upon end-of-file.\n          However, random read errors will still cause the process to die.\n      \n          So let's introduce an explicit option to tell the packet reader\n     -    machinery to please be nice and only return an error.\n     +    machinery to please be nice and only return an error on read errors.\n      \n          This change prepares pkt-line for use by long-running daemon processes.\n          Such processes should be able to serve multiple concurrent clients and\n     @@ Commit message\n          a daemon should be able to drop that connection and continue serving\n          existing and future connections.\n      \n     -    This ability will be used by a Git-aware \"Internal FSMonitor\" feature\n     +    This ability will be used by a Git-aware \"Builtin FSMonitor\" feature\n          in a later patch series.\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n     +    Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## pkt-line.c ##\n      @@ pkt-line.c: static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n     @@ pkt-line.c: static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n       \t\tret = read_in_full(fd, dst, size);\n      -\t\tif (ret < 0)\n      +\t\tif (ret < 0) {\n     -+\t\t\tif (options & PACKET_READ_NEVER_DIE)\n     ++\t\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n      +\t\t\t\treturn error_errno(_(\"read error\"));\n       \t\t\tdie_errno(_(\"read error\"));\n      +\t\t}\n     @@ pkt-line.c: static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n       \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n       \t\t\treturn -1;\n       \n     -+\t\tif (options & PACKET_READ_NEVER_DIE)\n     ++\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n      +\t\t\treturn error(_(\"the remote end hung up unexpectedly\"));\n       \t\tdie(_(\"the remote end hung up unexpectedly\"));\n       \t}\n     @@ pkt-line.c: enum packet_read_status packet_read_with_status(int fd, char **src_b\n       \tlen = packet_length(linelen);\n       \n       \tif (len < 0) {\n     -+\t\tif (options & PACKET_READ_NEVER_DIE)\n     ++\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n      +\t\t\treturn error(_(\"protocol error: bad line length \"\n      +\t\t\t\t       \"character: %.4s\"), linelen);\n       \t\tdie(_(\"protocol error: bad line length character: %.4s\"), linelen);\n     @@ pkt-line.c: enum packet_read_status packet_read_with_status(int fd, char **src_b\n       \t\t*pktlen = 0;\n       \t\treturn PACKET_READ_RESPONSE_END;\n       \t} else if (len < 4) {\n     -+\t\tif (options & PACKET_READ_NEVER_DIE)\n     ++\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n      +\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n      +\t\t\t\t     len);\n       \t\tdie(_(\"protocol error: bad line length %d\"), len);\n     @@ pkt-line.c: enum packet_read_status packet_read_with_status(int fd, char **src_b\n       \tlen -= 4;\n      -\tif ((unsigned)len >= size)\n      +\tif ((unsigned)len >= size) {\n     -+\t\tif (options & PACKET_READ_NEVER_DIE)\n     ++\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n      +\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n      +\t\t\t\t     len);\n       \t\tdie(_(\"protocol error: bad line length %d\"), len);\n     @@ pkt-line.h: int write_packetized_from_buf_no_flush(const char *src_in, size_t le\n        * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n        * ERR packet.\n      + *\n     -+ * With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except\n     -+ * an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect).\n     ++ * If options contains PACKET_READ_GENTLE_ON_READ_ERROR, we will not die\n     ++ * on read errors, but instead return -1.  However, we may still die on an\n     ++ * ERR packet (if requested).\n        */\n     - #define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n     - #define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n     - #define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n     -+#define PACKET_READ_NEVER_DIE         (1u<<3)\n     +-#define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n     +-#define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n     +-#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n     ++#define PACKET_READ_GENTLE_ON_EOF        (1u<<0)\n     ++#define PACKET_READ_CHOMP_NEWLINE        (1u<<1)\n     ++#define PACKET_READ_DIE_ON_ERR_PACKET    (1u<<2)\n     ++#define PACKET_READ_GENTLE_ON_READ_ERROR (1u<<3)\n       int packet_read(int fd, char **src_buffer, size_t *src_len, char\n       \t\t*buffer, unsigned size, int options);\n       \n  4:  81e14bed955c =  4:  b73e66a69b61 pkt-line: add options argument to read_packetized_to_strbuf()\n  5:  22eec60761a8 <  -:  ------------ simple-ipc: design documentation for new IPC mechanism\n  -:  ------------ >  5:  1ae99d824a21 simple-ipc: design documentation for new IPC mechanism\n  6:  171ec43ecfa4 !  6:  8b3ce40e4538 simple-ipc: add win32 implementation\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +\n      +\tif (read_packetized_to_strbuf(\n      +\t\t    connection->fd, answer,\n     -+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {\n     ++\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {\n      +\t\tret = error(_(\"could not read IPC response\"));\n      +\t\tgoto done;\n      +\t}\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +\n      +\tret = read_packetized_to_strbuf(\n      +\t\treply_data.fd, &buf,\n     -+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);\n     ++\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);\n      +\tif (ret >= 0) {\n      +\t\tret = server_thread_data->server_data->application_cb(\n      +\t\t\tserver_thread_data->server_data->application_data,\n     @@ compat/simple-ipc/ipc-win32.c (new)\n      +\t*returned_server_data = NULL;\n      +\n      +\tret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));\n     -+\tif (ret < 0)\n     -+\t\treturn error(\n     -+\t\t\t_(\"could not create normalized wchar_t path for '%s'\"),\n     -+\t\t\tpath);\n     ++\tif (ret < 0) {\n     ++\t\terrno = EINVAL;\n     ++\t\treturn -1;\n     ++\t}\n      +\n      +\thPipeFirst = create_new_pipe(wpath, 1);\n     -+\tif (hPipeFirst == INVALID_HANDLE_VALUE)\n     -+\t\treturn error(_(\"IPC server already running on '%s'\"), path);\n     ++\tif (hPipeFirst == INVALID_HANDLE_VALUE) {\n     ++\t\terrno = EADDRINUSE;\n     ++\t\treturn -2;\n     ++\t}\n      +\n      +\tserver_data = xcalloc(1, sizeof(*server_data));\n      +\tserver_data->magic = MAGIC_SERVER_DATA;\n     @@ simple-ipc.h (new)\n      + *\n      + * Returns 0 if the asynchronous server pool was started successfully.\n      + * Returns -1 if not.\n     ++ * Returns -2 if we could not startup because another server is using\n     ++ * the socket or named pipe.\n      + *\n      + * When a client IPC message is received, the `application_cb` will be\n      + * called (possibly on a random thread) to handle the message and\n     @@ simple-ipc.h (new)\n      + *\n      + * Returns 0 after the server has completed successfully.\n      + * Returns -1 if the server cannot be started.\n     ++ * Returns -2 if we could not startup because another server is using\n     ++ * the socket or named pipe.\n      + *\n      + * When a client IPC message is received, the `application_cb` will be\n      + * called (possibly on a random thread) to handle the message and\n  7:  b368318e6a23 !  7:  34df1af98e5b unix-socket: elimiate static unix_stream_socket() helper function\n     @@ Metadata\n      Author: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## Commit message ##\n     -    unix-socket: elimiate static unix_stream_socket() helper function\n     +    unix-socket: eliminate static unix_stream_socket() helper function\n      \n          The static helper function `unix_stream_socket()` calls `die()`.  This\n          is not appropriate for all callers.  Eliminate the wrapper function\n  8:  985b2e02b2df !  8:  d6ff6e0e050a unix-socket: add backlog size option to unix_stream_listen()\n     @@ builtin/credential-cache--daemon.c: static int serve_cache_loop(int fd)\n       \n      \n       ## unix-socket.c ##\n     +@@\n     + #include \"cache.h\"\n     + #include \"unix-socket.h\"\n     + \n     ++#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n     ++\n     + static int chdir_len(const char *orig, int len)\n     + {\n     + \tchar *path = xmemdupz(orig, len);\n      @@ unix-socket.c: int unix_stream_connect(const char *path)\n       \treturn -1;\n       }\n     @@ unix-socket.h\n      +\tint listen_backlog_size;\n      +};\n      +\n     -+#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n     -+\n     -+#define UNIX_STREAM_LISTEN_OPTS_INIT \\\n     -+{ \\\n     -+\t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n     -+}\n     ++#define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }\n      +\n       int unix_stream_connect(const char *path);\n      -int unix_stream_listen(const char *path);\n  9:  1bfa36409d07 !  9:  21b8d3c63dbf unix-socket: disallow chdir() when creating unix domain sockets\n     @@ unix-socket.h\n      +\tunsigned int disallow_chdir:1;\n       };\n       \n     - #define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n     -@@ unix-socket.h: struct unix_stream_listen_opts {\n     - #define UNIX_STREAM_LISTEN_OPTS_INIT \\\n     - { \\\n     - \t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n     -+\t.disallow_chdir = 0, \\\n     - }\n     + #define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }\n       \n      -int unix_stream_connect(const char *path);\n      +int unix_stream_connect(const char *path, int disallow_chdir);\n 10:  b443e11ac32f ! 10:  1ee9de55a106 unix-socket: create `unix_stream_server__listen_with_lock()`\n     @@ Metadata\n      Author: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       ## Commit message ##\n     -    unix-socket: create `unix_stream_server__listen_with_lock()`\n     +    unix-stream-server: create unix domain socket under lock\n      \n     -    Create a version of `unix_stream_listen()` that uses a \".lock\" lockfile\n     -    to create the unix domain socket in a race-free manner.\n     +    Create a wrapper class for `unix_stream_listen()` that uses a \".lock\"\n     +    lockfile to create the unix domain socket in a race-free manner.\n      \n          Unix domain sockets have a fundamental problem on Unix systems because\n          they persist in the filesystem until they are deleted.  This is\n     @@ Commit message\n      \n          As an alternative, we hold a plain lockfile (\"<path>.lock\") as a\n          mutual exclusion device.  Under the lock, we test if an existing\n     -    socket (\"<path>\") is has an active server.  If not, create a new\n     -    socket and begin listening.  Then we rollback the lockfile in all\n     -    cases.\n     +    socket (\"<path>\") is has an active server.  If not, we create a new\n     +    socket and begin listening.  Then we use \"rollback\" to delete the\n     +    lockfile in all cases.\n     +\n     +    This wrapper code conceptually exists at a higher-level than the core\n     +    unix_stream_connect() and unix_stream_listen() routines that it\n     +    consumes.  It is isolated in a wrapper class for clarity.\n      \n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n     - ## unix-socket.c ##\n     + ## Makefile ##\n     +@@ Makefile: ifdef NO_UNIX_SOCKETS\n     + \tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n     + else\n     + \tLIB_OBJS += unix-socket.o\n     ++\tLIB_OBJS += unix-stream-server.o\n     + endif\n     + \n     + ifdef USE_WIN32_IPC\n     +\n     + ## contrib/buildsystems/CMakeLists.txt ##\n     +@@ contrib/buildsystems/CMakeLists.txt: if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n     + \n     + elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n     + \tadd_compile_definitions(PROCFS_EXECUTABLE_PATH=\"/proc/self/exe\" HAVE_DEV_TTY )\n     +-\tlist(APPEND compat_SOURCES unix-socket.c)\n     ++\tlist(APPEND compat_SOURCES unix-socket.c unix-stream-server.c)\n     + endif()\n     + \n     + if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n     +\n     + ## unix-stream-server.c (new) ##\n      @@\n     - #include \"cache.h\"\n     ++#include \"cache.h\"\n      +#include \"lockfile.h\"\n     - #include \"unix-socket.h\"\n     - \n     - static int chdir_len(const char *orig, int len)\n     -@@ unix-socket.c: int unix_stream_listen(const char *path,\n     - \terrno = saved_errno;\n     - \treturn -1;\n     - }\n     ++#include \"unix-socket.h\"\n     ++#include \"unix-stream-server.h\"\n      +\n     ++#define DEFAULT_LOCK_TIMEOUT (100)\n     ++\n     ++/*\n     ++ * Try to connect to a unix domain socket at `path` (if it exists) and\n     ++ * see if there is a server listening.\n     ++ *\n     ++ * We don't know if the socket exists, whether a server died and\n     ++ * failed to cleanup, or whether we have a live server listening, so\n     ++ * we \"poke\" it.\n     ++ *\n     ++ * We immediately hangup without sending/receiving any data because we\n     ++ * don't know anything about the protocol spoken and don't want to\n     ++ * block while writing/reading data.  It is sufficient to just know\n     ++ * that someone is listening.\n     ++ */\n      +static int is_another_server_alive(const char *path,\n      +\t\t\t\t   const struct unix_stream_listen_opts *opts)\n      +{\n     -+\tstruct stat st;\n     -+\tint fd;\n     -+\n     -+\tif (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {\n     -+\t\t/*\n     -+\t\t * A socket-inode exists on disk at `path`, but we\n     -+\t\t * don't know whether it belongs to an active server\n     -+\t\t * or whether the last server died without cleaning\n     -+\t\t * up.\n     -+\t\t *\n     -+\t\t * Poke it with a trivial connection to try to find\n     -+\t\t * out.\n     -+\t\t */\n     -+\t\tfd = unix_stream_connect(path, opts->disallow_chdir);\n     -+\t\tif (fd >= 0) {\n     -+\t\t\tclose(fd);\n     -+\t\t\treturn 1;\n     -+\t\t}\n     ++\tint fd = unix_stream_connect(path, opts->disallow_chdir);\n     ++\tif (fd >= 0) {\n     ++\t\tclose(fd);\n     ++\t\treturn 1;\n      +\t}\n      +\n      +\treturn 0;\n      +}\n      +\n     -+struct unix_stream_server_socket *unix_stream_server__listen_with_lock(\n     ++int unix_stream_server__create(\n      +\tconst char *path,\n     -+\tconst struct unix_stream_listen_opts *opts)\n     ++\tconst struct unix_stream_listen_opts *opts,\n     ++\tlong timeout_ms,\n     ++\tstruct unix_stream_server_socket **new_server_socket)\n      +{\n      +\tstruct lock_file lock = LOCK_INIT;\n      +\tint fd_socket;\n      +\tstruct unix_stream_server_socket *server_socket;\n      +\n     ++\t*new_server_socket = NULL;\n     ++\n     ++\tif (timeout_ms < 0)\n     ++\t\ttimeout_ms = DEFAULT_LOCK_TIMEOUT;\n     ++\n      +\t/*\n      +\t * Create a lock at \"<path>.lock\" if we can.\n      +\t */\n     -+\tif (hold_lock_file_for_update_timeout(&lock, path, 0,\n     -+\t\t\t\t\t      opts->timeout_ms) < 0) {\n     -+\t\terror_errno(_(\"could not lock listener socket '%s'\"), path);\n     -+\t\treturn NULL;\n     -+\t}\n     ++\tif (hold_lock_file_for_update_timeout(&lock, path, 0, timeout_ms) < 0)\n     ++\t\treturn -1;\n      +\n      +\t/*\n      +\t * If another server is listening on \"<path>\" give up.  We do not\n      +\t * want to create a socket and steal future connections from them.\n      +\t */\n      +\tif (is_another_server_alive(path, opts)) {\n     -+\t\terrno = EADDRINUSE;\n     -+\t\terror_errno(_(\"listener socket already in use '%s'\"), path);\n      +\t\trollback_lock_file(&lock);\n     -+\t\treturn NULL;\n     ++\t\terrno = EADDRINUSE;\n     ++\t\treturn -2;\n      +\t}\n      +\n      +\t/*\n     @@ unix-socket.c: int unix_stream_listen(const char *path,\n      +\t */\n      +\tfd_socket = unix_stream_listen(path, opts);\n      +\tif (fd_socket < 0) {\n     -+\t\terror_errno(_(\"could not create listener socket '%s'\"), path);\n     ++\t\tint saved_errno = errno;\n      +\t\trollback_lock_file(&lock);\n     -+\t\treturn NULL;\n     ++\t\terrno = saved_errno;\n     ++\t\treturn -1;\n      +\t}\n      +\n      +\tserver_socket = xcalloc(1, sizeof(*server_socket));\n     @@ unix-socket.c: int unix_stream_listen(const char *path,\n      +\tserver_socket->fd_socket = fd_socket;\n      +\tlstat(path, &server_socket->st_socket);\n      +\n     ++\t*new_server_socket = server_socket;\n     ++\n      +\t/*\n      +\t * Always rollback (just delete) \"<path>.lock\" because we already created\n      +\t * \"<path>\" as a socket and do not want to commit_lock to do the atomic\n     @@ unix-socket.c: int unix_stream_listen(const char *path,\n      +\t */\n      +\trollback_lock_file(&lock);\n      +\n     -+\treturn server_socket;\n     ++\treturn 0;\n      +}\n      +\n      +void unix_stream_server__free(\n     @@ unix-socket.c: int unix_stream_listen(const char *path,\n      +\n      +\tif (st_now.st_ino != server_socket->st_socket.st_ino)\n      +\t\treturn 1;\n     ++\tif (st_now.st_dev != server_socket->st_socket.st_dev)\n     ++\t\treturn 1;\n      +\n     -+\t/* We might also consider the ctime on some platforms. */\n     ++\tif (!S_ISSOCK(st_now.st_mode))\n     ++\t\treturn 1;\n      +\n      +\treturn 0;\n      +}\n      \n     - ## unix-socket.h ##\n     + ## unix-stream-server.h (new) ##\n      @@\n     - #define UNIX_SOCKET_H\n     - \n     - struct unix_stream_listen_opts {\n     -+\tlong timeout_ms;\n     - \tint listen_backlog_size;\n     - \tunsigned int disallow_chdir:1;\n     - };\n     - \n     -+#define DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT (100)\n     - #define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n     - \n     - #define UNIX_STREAM_LISTEN_OPTS_INIT \\\n     - { \\\n     -+\t.timeout_ms = DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT, \\\n     - \t.listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \\\n     - \t.disallow_chdir = 0, \\\n     - }\n     -@@ unix-socket.h: int unix_stream_connect(const char *path, int disallow_chdir);\n     - int unix_stream_listen(const char *path,\n     - \t\t       const struct unix_stream_listen_opts *opts);\n     - \n     ++#ifndef UNIX_STREAM_SERVER_H\n     ++#define UNIX_STREAM_SERVER_H\n     ++\n     ++#include \"unix-socket.h\"\n     ++\n      +struct unix_stream_server_socket {\n      +\tchar *path_socket;\n      +\tstruct stat st_socket;\n     @@ unix-socket.h: int unix_stream_connect(const char *path, int disallow_chdir);\n      +/*\n      + * Create a Unix Domain Socket at the given path under the protection\n      + * of a '.lock' lockfile.\n     ++ *\n     ++ * Returns 0 on success, -1 on error, -2 if socket is in use.\n      + */\n     -+struct unix_stream_server_socket *unix_stream_server__listen_with_lock(\n     ++int unix_stream_server__create(\n      +\tconst char *path,\n     -+\tconst struct unix_stream_listen_opts *opts);\n     ++\tconst struct unix_stream_listen_opts *opts,\n     ++\tlong timeout_ms,\n     ++\tstruct unix_stream_server_socket **server_socket);\n      +\n      +/*\n      + * Close and delete the socket.\n     @@ unix-socket.h: int unix_stream_connect(const char *path, int disallow_chdir);\n      +int unix_stream_server__was_stolen(\n      +\tstruct unix_stream_server_socket *server_socket);\n      +\n     - #endif /* UNIX_SOCKET_H */\n     ++#endif /* UNIX_STREAM_SERVER_H */\n 11:  43c8db9a4468 ! 11:  f2e3b046cc8f simple-ipc: add Unix domain socket implementation\n     @@ Commit message\n      \n       ## Makefile ##\n      @@ Makefile: ifdef NO_UNIX_SOCKETS\n     - \tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n       else\n       \tLIB_OBJS += unix-socket.o\n     + \tLIB_OBJS += unix-stream-server.o\n      +\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n      +\tLIB_OBJS += compat/simple-ipc/ipc-unix-socket.o\n       endif\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +#include \"pkt-line.h\"\n      +#include \"thread-utils.h\"\n      +#include \"unix-socket.h\"\n     ++#include \"unix-stream-server.h\"\n      +\n      +#ifdef NO_UNIX_SOCKETS\n      +#error compat/simple-ipc/ipc-unix-socket.c requires Unix sockets\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\n      +\tif (read_packetized_to_strbuf(\n      +\t\t    connection->fd, answer,\n     -+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {\n     ++\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {\n      +\t\tret = error(_(\"could not read IPC response\"));\n      +\t\tgoto done;\n      +\t}\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\n      +\tret = read_packetized_to_strbuf(\n      +\t\treply_data.fd, &buf,\n     -+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);\n     ++\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);\n      +\tif (ret >= 0) {\n      +\t\tret = worker_thread_data->server_data->application_cb(\n      +\t\t\tworker_thread_data->server_data->application_data,\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      + */\n      +#define LISTEN_BACKLOG (50)\n      +\n     -+static struct unix_stream_server_socket *create_listener_socket(\n     ++static int create_listener_socket(\n      +\tconst char *path,\n     -+\tconst struct ipc_server_opts *ipc_opts)\n     ++\tconst struct ipc_server_opts *ipc_opts,\n     ++\tstruct unix_stream_server_socket **new_server_socket)\n      +{\n      +\tstruct unix_stream_server_socket *server_socket = NULL;\n      +\tstruct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n     ++\tint ret;\n      +\n      +\tuslg_opts.listen_backlog_size = LISTEN_BACKLOG;\n      +\tuslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;\n      +\n     -+\tserver_socket = unix_stream_server__listen_with_lock(path, &uslg_opts);\n     -+\tif (!server_socket)\n     -+\t\treturn NULL;\n     ++\tret = unix_stream_server__create(path, &uslg_opts, -1, &server_socket);\n     ++\tif (ret)\n     ++\t\treturn ret;\n      +\n      +\tif (set_socket_blocking_flag(server_socket->fd_socket, 1)) {\n      +\t\tint saved_errno = errno;\n     -+\t\terror_errno(_(\"could not set listener socket nonblocking '%s'\"),\n     -+\t\t\t    path);\n      +\t\tunix_stream_server__free(server_socket);\n      +\t\terrno = saved_errno;\n     -+\t\treturn NULL;\n     ++\t\treturn -1;\n      +\t}\n      +\n     ++\t*new_server_socket = server_socket;\n     ++\n      +\ttrace2_data_string(\"ipc-server\", NULL, \"listen-with-lock\", path);\n     -+\treturn server_socket;\n     ++\treturn 0;\n      +}\n      +\n     -+static struct unix_stream_server_socket *setup_listener_socket(\n     ++static int setup_listener_socket(\n      +\tconst char *path,\n     -+\tconst struct ipc_server_opts *ipc_opts)\n     ++\tconst struct ipc_server_opts *ipc_opts,\n     ++\tstruct unix_stream_server_socket **new_server_socket)\n      +{\n     -+\tstruct unix_stream_server_socket *server_socket;\n     ++\tint ret, saved_errno;\n      +\n      +\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n     -+\tserver_socket = create_listener_socket(path, ipc_opts);\n     ++\n     ++\tret = create_listener_socket(path, ipc_opts, new_server_socket);\n     ++\n     ++\tsaved_errno = errno;\n      +\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n     ++\terrno = saved_errno;\n      +\n     -+\treturn server_socket;\n     ++\treturn ret;\n      +}\n      +\n      +/*\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\tstruct ipc_server_data *server_data;\n      +\tint sv[2];\n      +\tint k;\n     ++\tint ret;\n      +\tint nr_threads = opts->nr_threads;\n      +\n      +\t*returned_server_data = NULL;\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t * connection or a shutdown request without spinning.\n      +\t */\n      +\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)\n     -+\t\treturn error_errno(_(\"could not create socketpair for '%s'\"),\n     -+\t\t\t\t   path);\n     ++\t\treturn -1;\n      +\n      +\tif (set_socket_blocking_flag(sv[1], 1)) {\n      +\t\tint saved_errno = errno;\n      +\t\tclose(sv[0]);\n      +\t\tclose(sv[1]);\n      +\t\terrno = saved_errno;\n     -+\t\treturn error_errno(_(\"making socketpair nonblocking '%s'\"),\n     -+\t\t\t\t   path);\n     ++\t\treturn -1;\n      +\t}\n      +\n     -+\tserver_socket = setup_listener_socket(path, opts);\n     -+\tif (!server_socket) {\n     ++\tret = setup_listener_socket(path, opts, &server_socket);\n     ++\tif (ret) {\n      +\t\tint saved_errno = errno;\n      +\t\tclose(sv[0]);\n      +\t\tclose(sv[1]);\n      +\t\terrno = saved_errno;\n     -+\t\treturn -1;\n     ++\t\treturn ret;\n      +\t}\n      +\n      +\tserver_data = xcalloc(1, sizeof(*server_data));\n 12:  09568a6500dd ! 12:  6ccc7472096f t0052: add simple-ipc tests and t/helper/test-simple-ipc tool\n     @@ t/helper/test-simple-ipc.c (new)\n      +\treturn app__unhandled_command(command, reply_cb, reply_data);\n      +}\n      +\n     ++struct cl_args\n     ++{\n     ++\tconst char *subcommand;\n     ++\tconst char *path;\n     ++\tconst char *token;\n     ++\n     ++\tint nr_threads;\n     ++\tint max_wait_sec;\n     ++\tint bytecount;\n     ++\tint batchsize;\n     ++\n     ++\tchar bytevalue;\n     ++};\n     ++\n     ++static struct cl_args cl_args = {\n     ++\t.subcommand = NULL,\n     ++\t.path = \"ipc-test\",\n     ++\t.token = NULL,\n     ++\n     ++\t.nr_threads = 5,\n     ++\t.max_wait_sec = 60,\n     ++\t.bytecount = 1024,\n     ++\t.batchsize = 10,\n     ++\n     ++\t.bytevalue = 'x',\n     ++};\n     ++\n      +/*\n      + * This process will run as a simple-ipc server and listen for IPC commands\n      + * from client processes.\n      + */\n     -+static int daemon__run_server(const char *path, int argc, const char **argv)\n     ++static int daemon__run_server(void)\n      +{\n     -+\tstruct ipc_server_opts opts = {\n     -+\t\t.nr_threads = 5\n     -+\t};\n     ++\tint ret;\n      +\n     -+\tconst char * const daemon_usage[] = {\n     -+\t\tN_(\"test-helper simple-ipc run-daemon [<options>\"),\n     -+\t\tNULL\n     -+\t};\n     -+\tstruct option daemon_options[] = {\n     -+\t\tOPT_INTEGER(0, \"threads\", &opts.nr_threads,\n     -+\t\t\t    N_(\"number of threads in server thread pool\")),\n     -+\t\tOPT_END()\n     ++\tstruct ipc_server_opts opts = {\n     ++\t\t.nr_threads = cl_args.nr_threads,\n      +\t};\n      +\n     -+\targc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);\n     -+\n     -+\tif (opts.nr_threads < 1)\n     -+\t\topts.nr_threads = 1;\n     -+\n      +\t/*\n      +\t * Synchronously run the ipc-server.  We don't need any application\n      +\t * instance data, so pass an arbitrary pointer (that we'll later\n      +\t * verify made the round trip).\n      +\t */\n     -+\treturn ipc_server_run(path, &opts, test_app_cb, (void*)&my_app_data);\n     ++\tret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data);\n     ++\tif (ret == -2)\n     ++\t\terror(_(\"socket/pipe already in use: '%s'\"), cl_args.path);\n     ++\telse if (ret == -1)\n     ++\t\terror_errno(_(\"could not start server on: '%s'\"), cl_args.path);\n     ++\n     ++\treturn ret;\n      +}\n      +\n      +#ifndef GIT_WINDOWS_NATIVE\n     @@ t/helper/test-simple-ipc.c (new)\n      + * This is adapted from `daemonize()`.  Use `fork()` to directly create and\n      + * run the daemon in a child process.\n      + */\n     -+static int spawn_server(const char *path,\n     -+\t\t\tconst struct ipc_server_opts *opts,\n     -+\t\t\tpid_t *pid)\n     ++static int spawn_server(pid_t *pid)\n      +{\n     ++\tstruct ipc_server_opts opts = {\n     ++\t\t.nr_threads = cl_args.nr_threads,\n     ++\t};\n     ++\n      +\t*pid = fork();\n      +\n      +\tswitch (*pid) {\n     @@ t/helper/test-simple-ipc.c (new)\n      +\t\tclose(2);\n      +\t\tsanitize_stdfds();\n      +\n     -+\t\treturn ipc_server_run(path, opts, test_app_cb, (void*)&my_app_data);\n     ++\t\treturn ipc_server_run(cl_args.path, &opts, test_app_cb,\n     ++\t\t\t\t      (void*)&my_app_data);\n      +\n      +\tcase -1:\n      +\t\treturn error_errno(_(\"could not spawn daemon in the background\"));\n     @@ t/helper/test-simple-ipc.c (new)\n      + * have `fork(2)`.  Spawn a normal Windows child process but without the\n      + * limitations of `start_command()` and `finish_command()`.\n      + */\n     -+static int spawn_server(const char *path,\n     -+\t\t\tconst struct ipc_server_opts *opts,\n     -+\t\t\tpid_t *pid)\n     ++static int spawn_server(pid_t *pid)\n      +{\n      +\tchar test_tool_exe[MAX_PATH];\n      +\tstruct strvec args = STRVEC_INIT;\n     @@ t/helper/test-simple-ipc.c (new)\n      +\tstrvec_push(&args, test_tool_exe);\n      +\tstrvec_push(&args, \"simple-ipc\");\n      +\tstrvec_push(&args, \"run-daemon\");\n     -+\tstrvec_pushf(&args, \"--threads=%d\", opts->nr_threads);\n     ++\tstrvec_pushf(&args, \"--name=%s\", cl_args.path);\n     ++\tstrvec_pushf(&args, \"--threads=%d\", cl_args.nr_threads);\n      +\n      +\t*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);\n      +\tclose(in);\n     @@ t/helper/test-simple-ipc.c (new)\n      + * let it get started and begin listening for requests on the socket\n      + * before reporting our success.\n      + */\n     -+static int wait_for_server_startup(const char * path, pid_t pid_child,\n     -+\t\t\t\t   int max_wait_sec)\n     ++static int wait_for_server_startup(pid_t pid_child)\n      +{\n      +\tint status;\n      +\tpid_t pid_seen;\n     @@ t/helper/test-simple-ipc.c (new)\n      +\ttime_t time_limit, now;\n      +\n      +\ttime(&time_limit);\n     -+\ttime_limit += max_wait_sec;\n     ++\ttime_limit += cl_args.max_wait_sec;\n      +\n      +\tfor (;;) {\n      +\t\tpid_seen = waitpid(pid_child, &status, WNOHANG);\n     @@ t/helper/test-simple-ipc.c (new)\n      +\t\t\t * after a timeout on the lock), but we don't\n      +\t\t\t * care (who responds) if the socket is live.\n      +\t\t\t */\n     -+\t\t\ts = ipc_get_active_state(path);\n     ++\t\t\ts = ipc_get_active_state(cl_args.path);\n      +\t\t\tif (s == IPC_STATE__LISTENING)\n      +\t\t\t\treturn 0;\n      +\n     @@ t/helper/test-simple-ipc.c (new)\n      +\t\t\t *\n      +\t\t\t * Again, we don't care who services the socket.\n      +\t\t\t */\n     -+\t\t\ts = ipc_get_active_state(path);\n     ++\t\t\ts = ipc_get_active_state(cl_args.path);\n      +\t\t\tif (s == IPC_STATE__LISTENING)\n      +\t\t\t\treturn 0;\n      +\n     @@ t/helper/test-simple-ipc.c (new)\n      + * more control and better error reporting (and makes it easier to write\n      + * unit tests).\n      + */\n     -+static int daemon__start_server(const char *path, int argc, const char **argv)\n     ++static int daemon__start_server(void)\n      +{\n      +\tpid_t pid_child;\n      +\tint ret;\n     -+\tint max_wait_sec = 60;\n     -+\tstruct ipc_server_opts opts = {\n     -+\t\t.nr_threads = 5\n     -+\t};\n     -+\n     -+\tconst char * const daemon_usage[] = {\n     -+\t\tN_(\"test-helper simple-ipc start-daemon [<options>\"),\n     -+\t\tNULL\n     -+\t};\n     -+\n     -+\tstruct option daemon_options[] = {\n     -+\t\tOPT_INTEGER(0, \"max-wait\", &max_wait_sec,\n     -+\t\t\t    N_(\"seconds to wait for daemon to startup\")),\n     -+\t\tOPT_INTEGER(0, \"threads\", &opts.nr_threads,\n     -+\t\t\t    N_(\"number of threads in server thread pool\")),\n     -+\t\tOPT_END()\n     -+\t};\n     -+\n     -+\targc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);\n     -+\n     -+\tif (max_wait_sec < 0)\n     -+\t\tmax_wait_sec = 0;\n     -+\tif (opts.nr_threads < 1)\n     -+\t\topts.nr_threads = 1;\n      +\n      +\t/*\n      +\t * Run the actual daemon in a background process.\n      +\t */\n     -+\tret = spawn_server(path, &opts, &pid_child);\n     ++\tret = spawn_server(&pid_child);\n      +\tif (pid_child <= 0)\n      +\t\treturn ret;\n      +\n     @@ t/helper/test-simple-ipc.c (new)\n      +\t * Let the parent wait for the child process to get started\n      +\t * and begin listening for requests on the socket.\n      +\t */\n     -+\tret = wait_for_server_startup(path, pid_child, max_wait_sec);\n     ++\tret = wait_for_server_startup(pid_child);\n      +\n      +\treturn ret;\n      +}\n     @@ t/helper/test-simple-ipc.c (new)\n      + *\n      + * Returns 0 if the server is alive.\n      + */\n     -+static int client__probe_server(const char *path)\n     ++static int client__probe_server(void)\n      +{\n      +\tenum ipc_active_state s;\n      +\n     -+\ts = ipc_get_active_state(path);\n     ++\ts = ipc_get_active_state(cl_args.path);\n      +\tswitch (s) {\n      +\tcase IPC_STATE__LISTENING:\n      +\t\treturn 0;\n      +\n      +\tcase IPC_STATE__NOT_LISTENING:\n     -+\t\treturn error(\"no server listening at '%s'\", path);\n     ++\t\treturn error(\"no server listening at '%s'\", cl_args.path);\n      +\n      +\tcase IPC_STATE__PATH_NOT_FOUND:\n     -+\t\treturn error(\"path not found '%s'\", path);\n     ++\t\treturn error(\"path not found '%s'\", cl_args.path);\n      +\n      +\tcase IPC_STATE__INVALID_PATH:\n     -+\t\treturn error(\"invalid pipe/socket name '%s'\", path);\n     ++\t\treturn error(\"invalid pipe/socket name '%s'\", cl_args.path);\n      +\n      +\tcase IPC_STATE__OTHER_ERROR:\n      +\tdefault:\n     -+\t\treturn error(\"other error for '%s'\", path);\n     ++\t\treturn error(\"other error for '%s'\", cl_args.path);\n      +\t}\n      +}\n      +\n      +/*\n     -+ * Send an IPC command to an already-running server daemon and print the\n     -+ * response.\n     ++ * Send an IPC command token to an already-running server daemon and\n     ++ * print the response.\n      + *\n     -+ * argv[2] contains a simple (1 word) command that `test_app_cb()` (in\n     -+ * the daemon process) will understand.\n     ++ * This is a simple 1 word command/token that `test_app_cb()` (in the\n     ++ * daemon process) will understand.\n      + */\n     -+static int client__send_ipc(int argc, const char **argv, const char *path)\n     ++static int client__send_ipc(void)\n      +{\n     -+\tconst char *command = argc > 2 ? argv[2] : \"(no command)\";\n     ++\tconst char *command = \"(no-command)\";\n      +\tstruct strbuf buf = STRBUF_INIT;\n      +\tstruct ipc_client_connect_options options\n      +\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n      +\n     ++\tif (cl_args.token && *cl_args.token)\n     ++\t\tcommand = cl_args.token;\n     ++\n      +\toptions.wait_if_busy = 1;\n      +\toptions.wait_if_not_found = 0;\n      +\n     -+\tif (!ipc_client_send_command(path, &options, command, &buf)) {\n     ++\tif (!ipc_client_send_command(cl_args.path, &options, command, &buf)) {\n      +\t\tif (buf.len) {\n      +\t\t\tprintf(\"%s\\n\", buf.buf);\n      +\t\t\tfflush(stdout);\n     @@ t/helper/test-simple-ipc.c (new)\n      +\t\treturn 0;\n      +\t}\n      +\n     -+\treturn error(\"failed to send '%s' to '%s'\", command, path);\n     ++\treturn error(\"failed to send '%s' to '%s'\", command, cl_args.path);\n      +}\n      +\n      +/*\n     @@ t/helper/test-simple-ipc.c (new)\n      + * event in the server, so we spin and wait here for it to actually\n      + * shutdown to make the unit tests a little easier to write.\n      + */\n     -+static int client__stop_server(int argc, const char **argv, const char *path)\n     ++static int client__stop_server(void)\n      +{\n     -+\tconst char *send_quit[] = { argv[0], \"send\", \"quit\", NULL };\n     -+\tint max_wait_sec = 60;\n      +\tint ret;\n      +\ttime_t time_limit, now;\n      +\tenum ipc_active_state s;\n      +\n     -+\tconst char * const stop_usage[] = {\n     -+\t\tN_(\"test-helper simple-ipc stop-daemon [<options>]\"),\n     -+\t\tNULL\n     -+\t};\n     -+\n     -+\tstruct option stop_options[] = {\n     -+\t\tOPT_INTEGER(0, \"max-wait\", &max_wait_sec,\n     -+\t\t\t    N_(\"seconds to wait for daemon to stop\")),\n     -+\t\tOPT_END()\n     -+\t};\n     -+\n     -+\targc = parse_options(argc, argv, NULL, stop_options, stop_usage, 0);\n     -+\n     -+\tif (max_wait_sec < 0)\n     -+\t\tmax_wait_sec = 0;\n     -+\n      +\ttime(&time_limit);\n     -+\ttime_limit += max_wait_sec;\n     ++\ttime_limit += cl_args.max_wait_sec;\n     ++\n     ++\tcl_args.token = \"quit\";\n      +\n     -+\tret = client__send_ipc(3, send_quit, path);\n     ++\tret = client__send_ipc();\n      +\tif (ret)\n      +\t\treturn ret;\n      +\n      +\tfor (;;) {\n      +\t\tsleep_millisec(100);\n      +\n     -+\t\ts = ipc_get_active_state(path);\n     ++\t\ts = ipc_get_active_state(cl_args.path);\n      +\n      +\t\tif (s != IPC_STATE__LISTENING) {\n      +\t\t\t/*\n     @@ t/helper/test-simple-ipc.c (new)\n      +/*\n      + * Send an IPC command with ballast to an already-running server daemon.\n      + */\n     -+static int client__sendbytes(int argc, const char **argv, const char *path)\n     ++static int client__sendbytes(void)\n      +{\n     -+\tint bytecount = 1024;\n     -+\tchar *string = \"x\";\n     -+\tconst char * const sendbytes_usage[] = {\n     -+\t\tN_(\"test-helper simple-ipc sendbytes [<options>]\"),\n     -+\t\tNULL\n     -+\t};\n     -+\tstruct option sendbytes_options[] = {\n     -+\t\tOPT_INTEGER(0, \"bytecount\", &bytecount, N_(\"number of bytes\")),\n     -+\t\tOPT_STRING(0, \"byte\", &string, N_(\"byte\"), N_(\"ballast\")),\n     -+\t\tOPT_END()\n     -+\t};\n      +\tstruct ipc_client_connect_options options\n      +\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n      +\n     @@ t/helper/test-simple-ipc.c (new)\n      +\toptions.wait_if_not_found = 0;\n      +\toptions.uds_disallow_chdir = 0;\n      +\n     -+\targc = parse_options(argc, argv, NULL, sendbytes_options, sendbytes_usage, 0);\n     -+\n     -+\treturn do_sendbytes(bytecount, string[0], path, &options);\n     ++\treturn do_sendbytes(cl_args.bytecount, cl_args.bytevalue, cl_args.path,\n     ++\t\t\t    &options);\n      +}\n      +\n      +struct multiple_thread_data {\n     @@ t/helper/test-simple-ipc.c (new)\n      + * Start a client-side thread pool.  Each thread sends a series of\n      + * IPC requests.  Each request is on a new connection to the server.\n      + */\n     -+static int client__multiple(int argc, const char **argv, const char *path)\n     ++static int client__multiple(void)\n      +{\n      +\tstruct multiple_thread_data *list = NULL;\n      +\tint k;\n     -+\tint nr_threads = 5;\n     -+\tint bytecount = 1;\n     -+\tint batchsize = 10;\n      +\tint sum_join_errors = 0;\n      +\tint sum_thread_errors = 0;\n      +\tint sum_good = 0;\n      +\n     -+\tconst char * const multiple_usage[] = {\n     -+\t\tN_(\"test-helper simple-ipc multiple [<options>]\"),\n     -+\t\tNULL\n     -+\t};\n     -+\tstruct option multiple_options[] = {\n     -+\t\tOPT_INTEGER(0, \"bytecount\", &bytecount, N_(\"number of bytes\")),\n     -+\t\tOPT_INTEGER(0, \"threads\", &nr_threads, N_(\"number of threads\")),\n     -+\t\tOPT_INTEGER(0, \"batchsize\", &batchsize, N_(\"number of requests per thread\")),\n     -+\t\tOPT_END()\n     -+\t};\n     -+\n     -+\targc = parse_options(argc, argv, NULL, multiple_options, multiple_usage, 0);\n     -+\n     -+\tif (bytecount < 1)\n     -+\t\tbytecount = 1;\n     -+\tif (nr_threads < 1)\n     -+\t\tnr_threads = 1;\n     -+\tif (batchsize < 1)\n     -+\t\tbatchsize = 1;\n     -+\n     -+\tfor (k = 0; k < nr_threads; k++) {\n     ++\tfor (k = 0; k < cl_args.nr_threads; k++) {\n      +\t\tstruct multiple_thread_data *d = xcalloc(1, sizeof(*d));\n      +\t\td->next = list;\n     -+\t\td->path = path;\n     -+\t\td->bytecount = bytecount + batchsize*(k/26);\n     -+\t\td->batchsize = batchsize;\n     ++\t\td->path = cl_args.path;\n     ++\t\td->bytecount = cl_args.bytecount + cl_args.batchsize*(k/26);\n     ++\t\td->batchsize = cl_args.batchsize;\n      +\t\td->sum_errors = 0;\n      +\t\td->sum_good = 0;\n      +\t\td->letter = 'A' + (k % 26);\n     @@ t/helper/test-simple-ipc.c (new)\n      +\n      +int cmd__simple_ipc(int argc, const char **argv)\n      +{\n     -+\tconst char *path = \"ipc-test\";\n     ++\tconst char * const simple_ipc_usage[] = {\n     ++\t\tN_(\"test-helper simple-ipc is-active    [<name>] [<options>]\"),\n     ++\t\tN_(\"test-helper simple-ipc run-daemon   [<name>] [<threads>]\"),\n     ++\t\tN_(\"test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]\"),\n     ++\t\tN_(\"test-helper simple-ipc stop-daemon  [<name>] [<max-wait>]\"),\n     ++\t\tN_(\"test-helper simple-ipc send         [<name>] [<token>]\"),\n     ++\t\tN_(\"test-helper simple-ipc sendbytes    [<name>] [<bytecount>] [<byte>]\"),\n     ++\t\tN_(\"test-helper simple-ipc multiple     [<name>] [<threads>] [<bytecount>] [<batchsize>]\"),\n     ++\t\tNULL\n     ++\t};\n     ++\n     ++\tconst char *bytevalue = NULL;\n     ++\n     ++\tstruct option options[] = {\n     ++#ifndef GIT_WINDOWS_NATIVE\n     ++\t\tOPT_STRING(0, \"name\", &cl_args.path, N_(\"name\"), N_(\"name or pathname of unix domain socket\")),\n     ++#else\n     ++\t\tOPT_STRING(0, \"name\", &cl_args.path, N_(\"name\"), N_(\"named-pipe name\")),\n     ++#endif\n     ++\t\tOPT_INTEGER(0, \"threads\", &cl_args.nr_threads, N_(\"number of threads in server thread pool\")),\n     ++\t\tOPT_INTEGER(0, \"max-wait\", &cl_args.max_wait_sec, N_(\"seconds to wait for daemon to start or stop\")),\n     ++\n     ++\t\tOPT_INTEGER(0, \"bytecount\", &cl_args.bytecount, N_(\"number of bytes\")),\n     ++\t\tOPT_INTEGER(0, \"batchsize\", &cl_args.batchsize, N_(\"number of requests per thread\")),\n     ++\n     ++\t\tOPT_STRING(0, \"byte\", &bytevalue, N_(\"byte\"), N_(\"ballast character\")),\n     ++\t\tOPT_STRING(0, \"token\", &cl_args.token, N_(\"token\"), N_(\"command token to send to the server\")),\n     ++\n     ++\t\tOPT_END()\n     ++\t};\n     ++\n     ++\tif (argc < 2)\n     ++\t\tusage_with_options(simple_ipc_usage, options);\n     ++\n     ++\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n     ++\t\tusage_with_options(simple_ipc_usage, options);\n      +\n      +\tif (argc == 2 && !strcmp(argv[1], \"SUPPORTS_SIMPLE_IPC\"))\n      +\t\treturn 0;\n      +\n     ++\tcl_args.subcommand = argv[1];\n     ++\n     ++\targc--;\n     ++\targv++;\n     ++\n     ++\targc = parse_options(argc, argv, NULL, options, simple_ipc_usage, 0);\n     ++\n     ++\tif (cl_args.nr_threads < 1)\n     ++\t\tcl_args.nr_threads = 1;\n     ++\tif (cl_args.max_wait_sec < 0)\n     ++\t\tcl_args.max_wait_sec = 0;\n     ++\tif (cl_args.bytecount < 1)\n     ++\t\tcl_args.bytecount = 1;\n     ++\tif (cl_args.batchsize < 1)\n     ++\t\tcl_args.batchsize = 1;\n     ++\n     ++\tif (bytevalue && *bytevalue)\n     ++\t\tcl_args.bytevalue = bytevalue[0];\n     ++\n      +\t/*\n      +\t * Use '!!' on all dispatch functions to map from `error()` style\n      +\t * (returns -1) style to `test_must_fail` style (expects 1).  This\n      +\t * makes shell error messages less confusing.\n      +\t */\n      +\n     -+\tif (argc == 2 && !strcmp(argv[1], \"is-active\"))\n     -+\t\treturn !!client__probe_server(path);\n     ++\tif (!strcmp(cl_args.subcommand, \"is-active\"))\n     ++\t\treturn !!client__probe_server();\n      +\n     -+\tif (argc >= 2 && !strcmp(argv[1], \"run-daemon\"))\n     -+\t\treturn !!daemon__run_server(path, argc, argv);\n     ++\tif (!strcmp(cl_args.subcommand, \"run-daemon\"))\n     ++\t\treturn !!daemon__run_server();\n      +\n     -+\tif (argc >= 2 && !strcmp(argv[1], \"start-daemon\"))\n     -+\t\treturn !!daemon__start_server(path, argc, argv);\n     ++\tif (!strcmp(cl_args.subcommand, \"start-daemon\"))\n     ++\t\treturn !!daemon__start_server();\n      +\n      +\t/*\n      +\t * Client commands follow.  Ensure a server is running before\n     -+\t * going any further.\n     ++\t * sending any data.  This might be overkill, but then again\n     ++\t * this is a test harness.\n      +\t */\n     -+\tif (client__probe_server(path))\n     -+\t\treturn 1;\n      +\n     -+\tif (argc >= 2 && !strcmp(argv[1], \"stop-daemon\"))\n     -+\t\treturn !!client__stop_server(argc, argv, path);\n     ++\tif (!strcmp(cl_args.subcommand, \"stop-daemon\")) {\n     ++\t\tif (client__probe_server())\n     ++\t\t\treturn 1;\n     ++\t\treturn !!client__stop_server();\n     ++\t}\n      +\n     -+\tif ((argc == 2 || argc == 3) && !strcmp(argv[1], \"send\"))\n     -+\t\treturn !!client__send_ipc(argc, argv, path);\n     ++\tif (!strcmp(cl_args.subcommand, \"send\")) {\n     ++\t\tif (client__probe_server())\n     ++\t\t\treturn 1;\n     ++\t\treturn !!client__send_ipc();\n     ++\t}\n      +\n     -+\tif (argc >= 2 && !strcmp(argv[1], \"sendbytes\"))\n     -+\t\treturn !!client__sendbytes(argc, argv, path);\n     ++\tif (!strcmp(cl_args.subcommand, \"sendbytes\")) {\n     ++\t\tif (client__probe_server())\n     ++\t\t\treturn 1;\n     ++\t\treturn !!client__sendbytes();\n     ++\t}\n      +\n     -+\tif (argc >= 2 && !strcmp(argv[1], \"multiple\"))\n     -+\t\treturn !!client__multiple(argc, argv, path);\n     ++\tif (!strcmp(cl_args.subcommand, \"multiple\")) {\n     ++\t\tif (client__probe_server())\n     ++\t\t\treturn 1;\n     ++\t\treturn !!client__multiple();\n     ++\t}\n      +\n     -+\tdie(\"Unhandled argv[1]: '%s'\", argv[1]);\n     ++\tdie(\"Unhandled subcommand: '%s'\", cl_args.subcommand);\n      +}\n      +#endif\n      \n     @@ t/t0052-simple-ipc.sh (new)\n      +'\n      +\n      +test_expect_success 'simple command server' '\n     -+\ttest-tool simple-ipc send ping >actual &&\n     ++\ttest-tool simple-ipc send --token=ping >actual &&\n      +\techo pong >expect &&\n      +\ttest_cmp expect actual\n      +'\n     @@ t/t0052-simple-ipc.sh (new)\n      +'\n      +\n      +test_expect_success 'big response' '\n     -+\ttest-tool simple-ipc send big >actual &&\n     ++\ttest-tool simple-ipc send --token=big >actual &&\n      +\ttest_line_count -ge 10000 actual &&\n      +\tgrep -q \"big: [0]*9999\\$\" actual\n      +'\n      +\n      +test_expect_success 'chunk response' '\n     -+\ttest-tool simple-ipc send chunk >actual &&\n     ++\ttest-tool simple-ipc send --token=chunk >actual &&\n      +\ttest_line_count -ge 10000 actual &&\n      +\tgrep -q \"big: [0]*9999\\$\" actual\n      +'\n      +\n      +test_expect_success 'slow response' '\n     -+\ttest-tool simple-ipc send slow >actual &&\n     ++\ttest-tool simple-ipc send --token=slow >actual &&\n      +\ttest_line_count -ge 100 actual &&\n      +\tgrep -q \"big: [0]*99\\$\" actual\n      +'\n     @@ t/t0052-simple-ipc.sh (new)\n      +test_expect_success 'stop-daemon works' '\n      +\ttest-tool simple-ipc stop-daemon &&\n      +\ttest_must_fail test-tool simple-ipc is-active &&\n     -+\ttest_must_fail test-tool simple-ipc send ping\n     ++\ttest_must_fail test-tool simple-ipc send --token=ping\n      +'\n      +\n      +test_done\n\n-- \ngitgitgadget\n"},{"id":"418594","messageId":"1ae99d824a218e43849824a2d3fb39266d391373.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 05/12] simple-ipc: design documentation for new IPC mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:30Z","receivedAt":"2021-03-09T15:03:52Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nBrief design documentation for new IPC mechanism allowing\nforeground Git client to talk with an existing daemon process\nat a known location using a named pipe or unix domain socket.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-simple-ipc.txt | 105 +++++++++++++++++++++\n 1 file changed, 105 insertions(+)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n\ndiff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt\nnew file mode 100644\nindex 000000000000..d79ad323e675\n--- /dev/null\n+++ b/Documentation/technical/api-simple-ipc.txt\n@@ -0,0 +1,105 @@\n+Simple-IPC API\n+==============\n+\n+The Simple-IPC API is a collection of `ipc_` prefixed library routines\n+and a basic communication protocol that allow an IPC-client process to\n+send an application-specific IPC-request message to an IPC-server\n+process and receive an application-specific IPC-response message.\n+\n+Communication occurs over a named pipe on Windows and a Unix domain\n+socket on other platforms.  IPC-clients and IPC-servers rendezvous at\n+a previously agreed-to application-specific pathname (which is outside\n+the scope of this design) that is local to the computer system.\n+\n+The IPC-server routines within the server application process create a\n+thread pool to listen for connections and receive request messages\n+from multiple concurrent IPC-clients.  When received, these messages\n+are dispatched up to the server application callbacks for handling.\n+IPC-server routines then incrementally relay responses back to the\n+IPC-client.\n+\n+The IPC-client routines within a client application process connect\n+to the IPC-server and send a request message and wait for a response.\n+When received, the response is returned back the caller.\n+\n+For example, the `fsmonitor--daemon` feature will be built as a server\n+application on top of the IPC-server library routines.  It will have\n+threads watching for file system events and a thread pool waiting for\n+client connections.  Clients, such as `git status` will request a list\n+of file system events since a point in time and the server will\n+respond with a list of changed files and directories.  The formats of\n+the request and response are application-specific; the IPC-client and\n+IPC-server routines treat them as opaque byte streams.\n+\n+\n+Comparison with sub-process model\n+---------------------------------\n+\n+The Simple-IPC mechanism differs from the existing `sub-process.c`\n+model (Documentation/technical/long-running-process-protocol.txt) and\n+used by applications like Git-LFS.  In the LFS-style sub-process model\n+the helper is started by the foreground process, communication happens\n+via a pair of file descriptors bound to the stdin/stdout of the\n+sub-process, the sub-process only serves the current foreground\n+process, and the sub-process exits when the foreground process\n+terminates.\n+\n+In the Simple-IPC model the server is a very long-running service.  It\n+can service many clients at the same time and has a private socket or\n+named pipe connection to each active client.  It might be started\n+(on-demand) by the current client process or it might have been\n+started by a previous client or by the OS at boot time.  The server\n+process is not associated with a terminal and it persists after\n+clients terminate.  Clients do not have access to the stdin/stdout of\n+the server process and therefore must communicate over sockets or\n+named pipes.\n+\n+\n+Server startup and shutdown\n+---------------------------\n+\n+How an application server based upon IPC-server is started is also\n+outside the scope of the Simple-IPC design and is a property of the\n+application using it.  For example, the server might be started or\n+restarted during routine maintenance operations, or it might be\n+started as a system service during the system boot-up sequence, or it\n+might be started on-demand by a foreground Git command when needed.\n+\n+Similarly, server shutdown is a property of the application using\n+the simple-ipc routines.  For example, the server might decide to\n+shutdown when idle or only upon explicit request.\n+\n+\n+Simple-IPC protocol\n+-------------------\n+\n+The Simple-IPC protocol consists of a single request message from the\n+client and an optional response message from the server.  Both the\n+client and server messages are unlimited in length and are terminated\n+with a flush packet.\n+\n+The pkt-line routines (Documentation/technical/protocol-common.txt)\n+are used to simplify buffer management during message generation,\n+transmission, and reception.  A flush packet is used to mark the end\n+of the message.  This allows the sender to incrementally generate and\n+transmit the message.  It allows the receiver to incrementally receive\n+the message in chunks and to know when they have received the entire\n+message.\n+\n+The actual byte format of the client request and server response\n+messages are application specific.  The IPC layer transmits and\n+receives them as opaque byte buffers without any concern for the\n+content within.  It is the job of the calling application layer to\n+understand the contents of the request and response messages.\n+\n+\n+Summary\n+-------\n+\n+Conceptually, the Simple-IPC protocol is similar to an HTTP REST\n+request.  Clients connect, make an application-specific and\n+stateless request, receive an application-specific\n+response, and disconnect.  It is a one round trip facility for\n+querying the server.  The Simple-IPC routines hide the socket,\n+named pipe, and thread pool details and allow the application\n+layer to focus on the application at hand.\n-- \ngitgitgadget\n\n"},{"id":"418595","messageId":"34df1af98e5b262ddf008da396c4a88ee24f3d42.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 07/12] unix-socket: eliminate static unix_stream_socket() helper function","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:32Z","receivedAt":"2021-03-09T15:03:53Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nThe static helper function `unix_stream_socket()` calls `die()`.  This\nis not appropriate for all callers.  Eliminate the wrapper function\nand make the callers propagate the error.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 27 +++++++++++++--------------\n 1 file changed, 13 insertions(+), 14 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 19ed48be9902..69f81d64e9d5 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,14 +1,6 @@\n #include \"cache.h\"\n #include \"unix-socket.h\"\n \n-static int unix_stream_socket(void)\n-{\n-\tint fd = socket(AF_UNIX, SOCK_STREAM, 0);\n-\tif (fd < 0)\n-\t\tdie_errno(\"unable to create socket\");\n-\treturn fd;\n-}\n-\n static int chdir_len(const char *orig, int len)\n {\n \tchar *path = xmemdupz(orig, len);\n@@ -73,13 +65,16 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \n int unix_stream_connect(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n+\n \tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \tunix_sockaddr_cleanup(&ctx);\n@@ -87,15 +82,16 @@ int unix_stream_connect(const char *path)\n \n fail:\n \tsaved_errno = errno;\n+\tif (fd != -1)\n+\t\tclose(fd);\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n \n int unix_stream_listen(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -103,7 +99,9 @@ int unix_stream_listen(const char *path)\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n \n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n@@ -116,8 +114,9 @@ int unix_stream_listen(const char *path)\n \n fail:\n \tsaved_errno = errno;\n+\tif (fd != -1)\n+\t\tclose(fd);\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n-- \ngitgitgadget\n\n"},{"id":"418596","messageId":"8b3ce40e453898afcdc77823a1b8c47d1d9a84f6.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 06/12] simple-ipc: add win32 implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:31Z","receivedAt":"2021-03-09T15:03:53Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Windows implementation of \"simple-ipc\" using named pipes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   5 +\n compat/simple-ipc/ipc-shared.c      |  28 ++\n compat/simple-ipc/ipc-win32.c       | 751 ++++++++++++++++++++++++++++\n config.mak.uname                    |   2 +\n contrib/buildsystems/CMakeLists.txt |   4 +\n simple-ipc.h                        | 228 +++++++++\n 6 files changed, 1018 insertions(+)\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n\ndiff --git a/Makefile b/Makefile\nindex dd08b4ced01c..d3c42d3f4f9f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1667,6 +1667,11 @@ else\n \tLIB_OBJS += unix-socket.o\n endif\n \n+ifdef USE_WIN32_IPC\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-win32.o\n+endif\n+\n ifdef NO_ICONV\n \tBASIC_CFLAGS += -DNO_ICONV\n endif\ndiff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c\nnew file mode 100644\nindex 000000000000..1edec8159532\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-shared.c\n@@ -0,0 +1,28 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data)\n+{\n+\tstruct ipc_server_data *server_data = NULL;\n+\tint ret;\n+\n+\tret = ipc_server_run_async(&server_data, path, opts,\n+\t\t\t\t   application_cb, application_data);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = ipc_server_await(server_data);\n+\n+\tipc_server_free(server_data);\n+\n+\treturn ret;\n+}\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\ndiff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c\nnew file mode 100644\nindex 000000000000..8f89c02037e3\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-win32.c\n@@ -0,0 +1,751 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+#error This file can only be compiled on Windows\n+#endif\n+\n+static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc)\n+{\n+\tint off = 0;\n+\tstruct strbuf realpath = STRBUF_INIT;\n+\n+\tif (!strbuf_realpath(&realpath, path, 0))\n+\t\treturn -1;\n+\n+\toff = swprintf(wpath, alloc, L\"\\\\\\\\.\\\\pipe\\\\\");\n+\tif (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0)\n+\t\treturn -1;\n+\n+\t/* Handle drive prefix */\n+\tif (wpath[off] && wpath[off + 1] == L':') {\n+\t\twpath[off + 1] = L'_';\n+\t\toff += 2;\n+\t}\n+\n+\tfor (; wpath[off]; off++)\n+\t\tif (wpath[off] == L'/')\n+\t\t\twpath[off] = L'\\\\';\n+\n+\tstrbuf_release(&realpath);\n+\treturn 0;\n+}\n+\n+static enum ipc_active_state get_active_state(wchar_t *pipe_path)\n+{\n+\tif (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT))\n+\t\treturn IPC_STATE__LISTENING;\n+\n+\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\tif (GetLastError() == ERROR_FILE_NOT_FOUND)\n+\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\treturn IPC_STATE__OTHER_ERROR;\n+}\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\twchar_t pipe_path[MAX_PATH];\n+\n+\tif (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\treturn get_active_state(pipe_path);\n+}\n+\n+#define WAIT_STEP_MS (50)\n+\n+static enum ipc_active_state connect_to_server(\n+\tconst wchar_t *wpath,\n+\tDWORD timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tDWORD t_start_ms, t_waited_ms;\n+\tDWORD step_ms;\n+\tHANDLE hPipe = INVALID_HANDLE_VALUE;\n+\tDWORD mode = PIPE_READMODE_BYTE;\n+\tDWORD gle;\n+\n+\t*pfd = -1;\n+\n+\tfor (;;) {\n+\t\thPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE,\n+\t\t\t\t    0, NULL, OPEN_EXISTING, 0, NULL);\n+\t\tif (hPipe != INVALID_HANDLE_VALUE)\n+\t\t\tbreak;\n+\n+\t\tgle = GetLastError();\n+\n+\t\tswitch (gle) {\n+\t\tcase ERROR_FILE_NOT_FOUND:\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tstep_ms = (timeout_ms < WAIT_STEP_MS) ?\n+\t\t\t\ttimeout_ms : WAIT_STEP_MS;\n+\t\t\tsleep_millisec(step_ms);\n+\n+\t\t\ttimeout_ms -= step_ms;\n+\t\t\tbreak; /* try again */\n+\n+\t\tcase ERROR_PIPE_BUSY:\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tt_start_ms = (DWORD)(getnanotime() / 1000000);\n+\n+\t\t\tif (!WaitNamedPipeW(wpath, timeout_ms)) {\n+\t\t\t\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * A pipe server instance became available.\n+\t\t\t * Race other client processes to connect to\n+\t\t\t * it.\n+\t\t\t *\n+\t\t\t * But first decrement our overall timeout so\n+\t\t\t * that we don't starve if we keep losing the\n+\t\t\t * race.  But also guard against special\n+\t\t\t * NPMWAIT_ values (0 and -1).\n+\t\t\t */\n+\t\t\tt_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms;\n+\t\t\tif (t_waited_ms < timeout_ms)\n+\t\t\t\ttimeout_ms -= t_waited_ms;\n+\t\t\telse\n+\t\t\t\ttimeout_ms = 1;\n+\t\t\tbreak; /* try again */\n+\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t}\n+\t}\n+\n+\tif (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t*pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);\n+\tif (*pfd < 0) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t/* fd now owns hPipe */\n+\n+\treturn IPC_STATE__LISTENING;\n+}\n+\n+/*\n+ * The default connection timeout for Windows clients.\n+ *\n+ * This is not currently part of the ipc_ API (nor the config settings)\n+ * because of differences between Windows and other platforms.\n+ *\n+ * This value was chosen at random.\n+ */\n+#define WINDOWS_CONNECTION_TIMEOUT_MS (30000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\twchar_t wpath[MAX_PATH];\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tif (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0)\n+\t\tstate = IPC_STATE__INVALID_PATH;\n+\telse\n+\t\tstate = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n+\t\t\t\t\t       connection->fd) < 0 ||\n+\t    packet_flush_gently(connection->fd) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tFlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));\n+\n+\tif (read_packetized_to_strbuf(\n+\t\t    connection->fd, answer,\n+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *response)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, response);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Duplicate the given pipe handle and wrap it in a file descriptor so\n+ * that we can use pkt-line on it.\n+ */\n+static int dup_fd_from_pipe(const HANDLE pipe)\n+{\n+\tHANDLE process = GetCurrentProcess();\n+\tHANDLE handle;\n+\tint fd;\n+\n+\tif (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE,\n+\t\t\t     DUPLICATE_SAME_ACCESS)) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\treturn -1;\n+\t}\n+\n+\tfd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY);\n+\tif (fd < 0) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\tCloseHandle(handle);\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * `handle` is now owned by `fd` and will be automatically closed\n+\t * when the descriptor is closed.\n+\t */\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_SERVER_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_server_thread_data *server_thread_data;\n+};\n+\n+struct ipc_server_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+\tHANDLE hPipe;\n+};\n+\n+/*\n+ * On Windows, the conceptual \"ipc-server\" is implemented as a pool of\n+ * n idential/peer \"server-thread\" threads.  That is, there is no\n+ * hierarchy of threads; and therefore no controller thread managing\n+ * the pool.  Each thread has an independent handle to the named pipe,\n+ * receives incoming connections, processes the client, and re-uses\n+ * the pipe for the next client connection.\n+ *\n+ * Therefore, the \"ipc-server\" only needs to maintain a list of the\n+ * spawned threads for eventual \"join\" purposes.\n+ *\n+ * A single \"stop-event\" is visible to all of the server threads to\n+ * tell them to shutdown (when idle).\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\twchar_t wpath[MAX_PATH];\n+\n+\tHANDLE hEventStopRequested;\n+\tstruct ipc_server_thread_data *thread_list;\n+\tint is_stopped;\n+};\n+\n+enum connect_result {\n+\tCR_CONNECTED = 0,\n+\tCR_CONNECT_PENDING,\n+\tCR_CONNECT_ERROR,\n+\tCR_WAIT_ERROR,\n+\tCR_SHUTDOWN,\n+};\n+\n+static enum connect_result queue_overlapped_connect(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tif (ConnectNamedPipe(server_thread_data->hPipe, lpo))\n+\t\tgoto failed;\n+\n+\tswitch (GetLastError()) {\n+\tcase ERROR_IO_PENDING:\n+\t\treturn CR_CONNECT_PENDING;\n+\n+\tcase ERROR_PIPE_CONNECTED:\n+\t\tSetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+failed:\n+\terror(_(\"ConnectNamedPipe failed for '%s' (%lu)\"),\n+\t      server_thread_data->server_data->buf_path.buf,\n+\t      GetLastError());\n+\treturn CR_CONNECT_ERROR;\n+}\n+\n+/*\n+ * Use Windows Overlapped IO to wait for a connection or for our event\n+ * to be signalled.\n+ */\n+static enum connect_result wait_for_connection(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tenum connect_result r;\n+\tHANDLE waitHandles[2];\n+\tDWORD dwWaitResult;\n+\n+\tr = queue_overlapped_connect(server_thread_data, lpo);\n+\tif (r != CR_CONNECT_PENDING)\n+\t\treturn r;\n+\n+\twaitHandles[0] = server_thread_data->server_data->hEventStopRequested;\n+\twaitHandles[1] = lpo->hEvent;\n+\n+\tdwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);\n+\tswitch (dwWaitResult) {\n+\tcase WAIT_OBJECT_0 + 0:\n+\t\treturn CR_SHUTDOWN;\n+\n+\tcase WAIT_OBJECT_0 + 1:\n+\t\tResetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\treturn CR_WAIT_ERROR;\n+\t}\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf_no_flush(response, response_len,\n+\t\t\t\t\t\t  reply_data->fd);\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ *\n+ * Simple-IPC only contains one round trip, so we flush and close\n+ * here after the response.\n+ */\n+static int do_io(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.server_thread_data = server_thread_data;\n+\n+\treply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe);\n+\tif (reply_data.fd < 0)\n+\t\treturn error(_(\"could not create fd from pipe for '%s'\"),\n+\t\t\t     server_thread_data->server_data->buf_path.buf);\n+\n+\tret = read_packetized_to_strbuf(\n+\t\treply_data.fd, &buf,\n+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);\n+\tif (ret >= 0) {\n+\t\tret = server_thread_data->server_data->application_cb(\n+\t\t\tserver_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\n+\t\tFlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd)));\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Handle IPC request and response with this connected client.  And reset\n+ * the pipe to prepare for the next client.\n+ */\n+static int use_connection(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tint ret;\n+\n+\tret = do_io(server_thread_data);\n+\n+\tFlushFileBuffers(server_thread_data->hPipe);\n+\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Thread proc for an IPC server worker thread.  It handles a series of\n+ * connections from clients.  It cleans and reuses the hPipe between each\n+ * client.\n+ */\n+static void *server_thread_proc(void *_server_thread_data)\n+{\n+\tstruct ipc_server_thread_data *server_thread_data = _server_thread_data;\n+\tHANDLE hEventConnected = INVALID_HANDLE_VALUE;\n+\tOVERLAPPED oConnect;\n+\tenum connect_result cr;\n+\tint ret;\n+\n+\tassert(server_thread_data->hPipe != INVALID_HANDLE_VALUE);\n+\n+\ttrace2_thread_start(\"ipc-server\");\n+\ttrace2_data_string(\"ipc-server\", NULL, \"pipe\",\n+\t\t\t   server_thread_data->server_data->buf_path.buf);\n+\n+\thEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL);\n+\n+\tmemset(&oConnect, 0, sizeof(oConnect));\n+\toConnect.hEvent = hEventConnected;\n+\n+\tfor (;;) {\n+\t\tcr = wait_for_connection(server_thread_data, &oConnect);\n+\n+\t\tswitch (cr) {\n+\t\tcase CR_SHUTDOWN:\n+\t\t\tgoto finished;\n+\n+\t\tcase CR_CONNECTED:\n+\t\t\tret = use_connection(server_thread_data);\n+\t\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\tserver_thread_data->server_data);\n+\t\t\t\tgoto finished;\n+\t\t\t}\n+\t\t\tif (ret > 0) {\n+\t\t\t\t/*\n+\t\t\t\t * Ignore (transient) IO errors with this\n+\t\t\t\t * client and reset for the next client.\n+\t\t\t\t */\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase CR_CONNECT_PENDING:\n+\t\t\t/* By construction, this should not happen. */\n+\t\t\tBUG(\"ipc-server[%s]: unexpeced CR_CONNECT_PENDING\",\n+\t\t\t    server_thread_data->server_data->buf_path.buf);\n+\n+\t\tcase CR_CONNECT_ERROR:\n+\t\tcase CR_WAIT_ERROR:\n+\t\t\t/*\n+\t\t\t * Ignore these theoretical errors.\n+\t\t\t */\n+\t\t\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tBUG(\"unandled case after wait_for_connection\");\n+\t\t}\n+\t}\n+\n+finished:\n+\tCloseHandle(server_thread_data->hPipe);\n+\tCloseHandle(hEventConnected);\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+static HANDLE create_new_pipe(wchar_t *wpath, int is_first)\n+{\n+\tHANDLE hPipe;\n+\tDWORD dwOpenMode, dwPipeMode;\n+\tLPSECURITY_ATTRIBUTES lpsa = NULL;\n+\n+\tdwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |\n+\t\tFILE_FLAG_OVERLAPPED;\n+\n+\tdwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |\n+\t\tPIPE_REJECT_REMOTE_CLIENTS;\n+\n+\tif (is_first) {\n+\t\tdwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE;\n+\n+\t\t/*\n+\t\t * On Windows, the first server pipe instance gets to\n+\t\t * set the ACL / Security Attributes on the named\n+\t\t * pipe; subsequent instances inherit and cannot\n+\t\t * change them.\n+\t\t *\n+\t\t * TODO Should we allow the application layer to\n+\t\t * specify security attributes, such as `LocalService`\n+\t\t * or `LocalSystem`, when we create the named pipe?\n+\t\t * This question is probably not important when the\n+\t\t * daemon is started by a foreground user process and\n+\t\t * only needs to talk to the current user, but may be\n+\t\t * if the daemon is run via the Control Panel as a\n+\t\t * System Service.\n+\t\t */\n+\t}\n+\n+\thPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,\n+\t\t\t\t PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);\n+\n+\treturn hPipe;\n+}\n+\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct ipc_server_data *server_data;\n+\twchar_t wpath[MAX_PATH];\n+\tHANDLE hPipeFirst = INVALID_HANDLE_VALUE;\n+\tint k;\n+\tint ret = 0;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\tret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));\n+\tif (ret < 0) {\n+\t\terrno = EINVAL;\n+\t\treturn -1;\n+\t}\n+\n+\thPipeFirst = create_new_pipe(wpath, 1);\n+\tif (hPipeFirst == INVALID_HANDLE_VALUE) {\n+\t\terrno = EADDRINUSE;\n+\t\treturn -2;\n+\t}\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tserver_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL);\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\twcscpy(server_data->wpath, wpath);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_server_thread_data *std;\n+\n+\t\tstd = xcalloc(1, sizeof(*std));\n+\t\tstd->magic = MAGIC_SERVER_THREAD_DATA;\n+\t\tstd->server_data = server_data;\n+\t\tstd->hPipe = INVALID_HANDLE_VALUE;\n+\n+\t\tstd->hPipe = (k == 0)\n+\t\t\t? hPipeFirst\n+\t\t\t: create_new_pipe(server_data->wpath, 0);\n+\n+\t\tif (std->hPipe == INVALID_HANDLE_VALUE) {\n+\t\t\t/*\n+\t\t\t * If we've reached a pipe instance limit for\n+\t\t\t * this path, just use fewer threads.\n+\t\t\t */\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (pthread_create(&std->pthread_id, NULL,\n+\t\t\t\t   server_thread_proc, std)) {\n+\t\t\t/*\n+\t\t\t * Likewise, if we're out of threads, just use\n+\t\t\t * fewer threads than requested.\n+\t\t\t *\n+\t\t\t * However, we just give up if we can't even get\n+\t\t\t * one thread.  This should not happen.\n+\t\t\t */\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start thread[0] for '%s'\"),\n+\t\t\t\t    path);\n+\n+\t\t\tCloseHandle(std->hPipe);\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tstd->next_thread = server_data->thread_list;\n+\t\tserver_data->thread_list = std;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Gently tell all of the ipc_server threads to shutdown.\n+\t * This will be seen the next time they are idle (and waiting\n+\t * for a connection).\n+\t *\n+\t * We DO NOT attempt to force them to drop an active connection.\n+\t */\n+\tSetEvent(server_data->hEventStopRequested);\n+\treturn 0;\n+}\n+\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tDWORD dwWaitResult;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\tdwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE);\n+\tif (dwWaitResult != WAIT_OBJECT_0)\n+\t\treturn error(_(\"wait for hEvent failed for '%s'\"),\n+\t\t\t     server_data->buf_path.buf);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tpthread_join(std->pthread_id, NULL);\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tif (server_data->hEventStopRequested != INVALID_HANDLE_VALUE)\n+\t\tCloseHandle(server_data->hEventStopRequested);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tfree(server_data);\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex e22d4b6d67a3..2b3303f34be8 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows)\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \t# USE_NED_ALLOCATOR = YesPlease\n@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \tUSE_NED_ALLOCATOR = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex ac3dbc079af8..40c9e8e3bd9d 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n \tlist(APPEND compat_SOURCES unix-socket.c)\n endif()\n \n+if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+endif()\n+\n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\n \n #header checks\ndiff --git a/simple-ipc.h b/simple-ipc.h\nnew file mode 100644\nindex 000000000000..ab5619e3d76f\n--- /dev/null\n+++ b/simple-ipc.h\n@@ -0,0 +1,228 @@\n+#ifndef GIT_SIMPLE_IPC_H\n+#define GIT_SIMPLE_IPC_H\n+\n+/*\n+ * See Documentation/technical/api-simple-ipc.txt\n+ */\n+\n+#if defined(GIT_WINDOWS_NATIVE)\n+#define SUPPORTS_SIMPLE_IPC\n+#endif\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+#include \"pkt-line.h\"\n+\n+/*\n+ * Simple IPC Client Side API.\n+ */\n+\n+enum ipc_active_state {\n+\t/*\n+\t * The pipe/socket exists and the daemon is waiting for connections.\n+\t */\n+\tIPC_STATE__LISTENING = 0,\n+\n+\t/*\n+\t * The pipe/socket exists, but the daemon is not listening.\n+\t * Perhaps it is very busy.\n+\t * Perhaps the daemon died without deleting the path.\n+\t * Perhaps it is shutting down and draining existing clients.\n+\t * Perhaps it is dead, but other clients are lingering and\n+\t * still holding a reference to the pathname.\n+\t */\n+\tIPC_STATE__NOT_LISTENING,\n+\n+\t/*\n+\t * The requested pathname is bogus and no amount of retries\n+\t * will fix that.\n+\t */\n+\tIPC_STATE__INVALID_PATH,\n+\n+\t/*\n+\t * The requested pathname is not found.  This usually means\n+\t * that there is no daemon present.\n+\t */\n+\tIPC_STATE__PATH_NOT_FOUND,\n+\n+\tIPC_STATE__OTHER_ERROR,\n+};\n+\n+struct ipc_client_connect_options {\n+\t/*\n+\t * Spin under timeout if the server is running but can't\n+\t * accept our connection yet.  This should always be set\n+\t * unless you just want to poke the server and see if it\n+\t * is alive.\n+\t */\n+\tunsigned int wait_if_busy:1;\n+\n+\t/*\n+\t * Spin under timeout if the pipe/socket is not yet present\n+\t * on the file system.  This is useful if we just started\n+\t * the service and need to wait for it to become ready.\n+\t */\n+\tunsigned int wait_if_not_found:1;\n+};\n+\n+#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n+\t.wait_if_busy = 0, \\\n+\t.wait_if_not_found = 0, \\\n+}\n+\n+/*\n+ * Determine if a server is listening on this named pipe or socket using\n+ * platform-specific logic.  This might just probe the filesystem or it\n+ * might make a trivial connection to the server using this pathname.\n+ */\n+enum ipc_active_state ipc_get_active_state(const char *path);\n+\n+struct ipc_client_connection {\n+\tint fd;\n+};\n+\n+/*\n+ * Try to connect to the daemon on the named pipe or socket.\n+ *\n+ * Returns IPC_STATE__LISTENING and a connection handle.\n+ *\n+ * Otherwise, returns info to help decide whether to retry or to\n+ * spawn/respawn the server.\n+ */\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection);\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection);\n+\n+/*\n+ * Used by the client to synchronously send and receive a message with\n+ * the server on the provided client connection.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer);\n+\n+/*\n+ * Used by the client to synchronously connect and send and receive a\n+ * message to the server listening at the given path.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer);\n+\n+/*\n+ * Simple IPC Server Side API.\n+ */\n+\n+struct ipc_server_reply_data;\n+\n+typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *,\n+\t\t\t\t  const char *response,\n+\t\t\t\t  size_t response_len);\n+\n+/*\n+ * Prototype for an application-supplied callback to process incoming\n+ * client IPC messages and compose a reply.  The `application_cb` should\n+ * use the provided `reply_cb` and `reply_data` to send an IPC response\n+ * back to the client.  The `reply_cb` callback can be called multiple\n+ * times for chunking purposes.  A reply message is optional and may be\n+ * omitted if not necessary for the application.\n+ *\n+ * The return value from the application callback is ignored.\n+ * The value `SIMPLE_IPC_QUIT` can be used to shutdown the server.\n+ */\n+typedef int (ipc_server_application_cb)(void *application_data,\n+\t\t\t\t\tconst char *request,\n+\t\t\t\t\tipc_server_reply_cb *reply_cb,\n+\t\t\t\t\tstruct ipc_server_reply_data *reply_data);\n+\n+#define SIMPLE_IPC_QUIT -2\n+\n+/*\n+ * Opaque instance data to represent an IPC server instance.\n+ */\n+struct ipc_server_data;\n+\n+/*\n+ * Control parameters for the IPC server instance.\n+ * Use this to hide platform-specific settings.\n+ */\n+struct ipc_server_opts\n+{\n+\tint nr_threads;\n+};\n+\n+/*\n+ * Start an IPC server instance in one or more background threads\n+ * and return a handle to the pool.\n+ *\n+ * Returns 0 if the asynchronous server pool was started successfully.\n+ * Returns -1 if not.\n+ * Returns -2 if we could not startup because another server is using\n+ * the socket or named pipe.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data);\n+\n+/*\n+ * Gently signal the IPC server pool to shutdown.  No new client\n+ * connections will be accepted, but existing connections will be\n+ * allowed to complete.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data);\n+\n+/*\n+ * Block the calling thread until all threads in the IPC server pool\n+ * have completed and been joined.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data);\n+\n+/*\n+ * Close and free all resource handles associated with the IPC server\n+ * pool.\n+ */\n+void ipc_server_free(struct ipc_server_data *server_data);\n+\n+/*\n+ * Run an IPC server instance and block the calling thread of the\n+ * current process.  It does not return until the IPC server has\n+ * either shutdown or had an unrecoverable error.\n+ *\n+ * The IPC server handles incoming IPC messages from client processes\n+ * and may use one or more background threads as necessary.\n+ *\n+ * Returns 0 after the server has completed successfully.\n+ * Returns -1 if the server cannot be started.\n+ * Returns -2 if we could not startup because another server is using\n+ * the socket or named pipe.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ *\n+ * Note that `ipc_server_run()` is a synchronous wrapper around the\n+ * above asynchronous routines.  It effectively hides all of the\n+ * server state and thread details from the caller and presents a\n+ * simple synchronous interface.\n+ */\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data);\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\n+#endif /* GIT_SIMPLE_IPC_H */\n-- \ngitgitgadget\n\n"},{"id":"418600","messageId":"f2e3b046cc8f8ad5662f65262810c7414cc1569d.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 11/12] simple-ipc: add Unix domain socket implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:36Z","receivedAt":"2021-03-09T15:03:53Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Unix domain socket based implementation of \"simple-ipc\".\n\nA set of `ipc_client` routines implement a client library to connect\nto an `ipc_server` over a Unix domain socket, send a simple request,\nand receive a single response.  Clients use blocking IO on the socket.\n\nA set of `ipc_server` routines implement a thread pool to listen for\nand concurrently service client connections.\n\nThe server creates a new Unix domain socket at a known location.  If a\nsocket already exists with that name, the server tries to determine if\nanother server is already listening on the socket or if the socket is\ndead.  If socket is busy, the server exits with an error rather than\nstealing the socket.  If the socket is dead, the server creates a new\none and starts up.\n\nIf while running, the server detects that its socket has been stolen\nby another server, it automatically exits.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   2 +\n compat/simple-ipc/ipc-unix-socket.c | 986 ++++++++++++++++++++++++++++\n contrib/buildsystems/CMakeLists.txt |   2 +\n simple-ipc.h                        |  13 +-\n 4 files changed, 1002 insertions(+), 1 deletion(-)\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n\ndiff --git a/Makefile b/Makefile\nindex 012694276f6d..20dd65d19658 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1666,6 +1666,8 @@ ifdef NO_UNIX_SOCKETS\n else\n \tLIB_OBJS += unix-socket.o\n \tLIB_OBJS += unix-stream-server.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-unix-socket.o\n endif\n \n ifdef USE_WIN32_IPC\ndiff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c\nnew file mode 100644\nindex 000000000000..6e381a9e030e\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-unix-socket.c\n@@ -0,0 +1,986 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+#include \"unix-socket.h\"\n+#include \"unix-stream-server.h\"\n+\n+#ifdef NO_UNIX_SOCKETS\n+#error compat/simple-ipc/ipc-unix-socket.c requires Unix sockets\n+#endif\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\tstruct stat st;\n+\tstruct ipc_client_connection *connection_test = NULL;\n+\n+\toptions.wait_if_busy = 0;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (lstat(path, &st) == -1) {\n+\t\tswitch (errno) {\n+\t\tcase ENOENT:\n+\t\tcase ENOTDIR:\n+\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__INVALID_PATH;\n+\t\t}\n+\t}\n+\n+\t/* also complain if a plain file is in the way */\n+\tif ((st.st_mode & S_IFMT) != S_IFSOCK)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\t/*\n+\t * Just because the filesystem has a S_IFSOCK type inode\n+\t * at `path`, doesn't mean it that there is a server listening.\n+\t * Ping it to be sure.\n+\t */\n+\tstate = ipc_client_try_connect(path, &options, &connection_test);\n+\tipc_client_close_connection(connection_test);\n+\n+\treturn state;\n+}\n+\n+/*\n+ * This value was chosen at random.\n+ */\n+#define WAIT_STEP_MS (50)\n+\n+/*\n+ * Try to connect to the server.  If the server is just starting up or\n+ * is very busy, we may not get a connection the first time.\n+ */\n+static enum ipc_active_state connect_to_server(\n+\tconst char *path,\n+\tint timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tint wait_ms = 50;\n+\tint k;\n+\n+\t*pfd = -1;\n+\n+\tfor (k = 0; k < timeout_ms; k += wait_ms) {\n+\t\tint fd = unix_stream_connect(path, options->uds_disallow_chdir);\n+\n+\t\tif (fd != -1) {\n+\t\t\t*pfd = fd;\n+\t\t\treturn IPC_STATE__LISTENING;\n+\t\t}\n+\n+\t\tif (errno == ENOENT) {\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ETIMEDOUT) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ECONNREFUSED) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\n+\tsleep_and_try_again:\n+\t\tsleep_millisec(wait_ms);\n+\t}\n+\n+\treturn IPC_STATE__NOT_LISTENING;\n+}\n+\n+/*\n+ * A randomly chosen timeout value.\n+ */\n+#define MY_CONNECTION_TIMEOUT_MS (1000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tstate = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n+\t\t\t\t\t       connection->fd) < 0 ||\n+\t    packet_flush_gently(connection->fd) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tif (read_packetized_to_strbuf(\n+\t\t    connection->fd, answer,\n+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, answer);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+static int set_socket_blocking_flag(int fd, int make_nonblocking)\n+{\n+\tint flags;\n+\n+\tflags = fcntl(fd, F_GETFL, NULL);\n+\n+\tif (flags < 0)\n+\t\treturn -1;\n+\n+\tif (make_nonblocking)\n+\t\tflags |= O_NONBLOCK;\n+\telse\n+\t\tflags &= ~O_NONBLOCK;\n+\n+\treturn fcntl(fd, F_SETFL, flags);\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_WORKER_THREAD_DATA,\n+\tMAGIC_ACCEPT_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_worker_thread_data *worker_thread_data;\n+};\n+\n+struct ipc_worker_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_worker_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+};\n+\n+struct ipc_accept_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_data *server_data;\n+\n+\tstruct unix_stream_server_socket *server_socket;\n+\n+\tint fd_send_shutdown;\n+\tint fd_wait_shutdown;\n+\tpthread_t pthread_id;\n+};\n+\n+/*\n+ * With unix-sockets, the conceptual \"ipc-server\" is implemented as a single\n+ * controller \"accept-thread\" thread and a pool of \"worker-thread\" threads.\n+ * The former does the usual `accept()` loop and dispatches connections\n+ * to an idle worker thread.  The worker threads wait in an idle loop for\n+ * a new connection, communicate with the client and relay data to/from\n+ * the `application_cb` and then wait for another connection from the\n+ * server thread.  This avoids the overhead of constantly creating and\n+ * destroying threads.\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\n+\tstruct ipc_accept_thread_data *accept_thread;\n+\tstruct ipc_worker_thread_data *worker_thread_list;\n+\n+\tpthread_mutex_t work_available_mutex;\n+\tpthread_cond_t work_available_cond;\n+\n+\t/*\n+\t * Accepted but not yet processed client connections are kept\n+\t * in a circular buffer FIFO.  The queue is empty when the\n+\t * positions are equal.\n+\t */\n+\tint *fifo_fds;\n+\tint queue_size;\n+\tint back_pos;\n+\tint front_pos;\n+\n+\tint shutdown_requested;\n+\tint is_stopped;\n+};\n+\n+/*\n+ * Remove and return the oldest queued connection.\n+ *\n+ * Returns -1 if empty.\n+ */\n+static int fifo_dequeue(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint fd;\n+\n+\tif (server_data->back_pos == server_data->front_pos)\n+\t\treturn -1;\n+\n+\tfd = server_data->fifo_fds[server_data->front_pos];\n+\tserver_data->fifo_fds[server_data->front_pos] = -1;\n+\n+\tserver_data->front_pos++;\n+\tif (server_data->front_pos == server_data->queue_size)\n+\t\tserver_data->front_pos = 0;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Push a new fd onto the back of the queue.\n+ *\n+ * Drop it and return -1 if queue is already full.\n+ */\n+static int fifo_enqueue(struct ipc_server_data *server_data, int fd)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint next_back_pos;\n+\n+\tnext_back_pos = server_data->back_pos + 1;\n+\tif (next_back_pos == server_data->queue_size)\n+\t\tnext_back_pos = 0;\n+\n+\tif (next_back_pos == server_data->front_pos) {\n+\t\t/* Queue is full. Just drop it. */\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data->fifo_fds[server_data->back_pos] = fd;\n+\tserver_data->back_pos = next_back_pos;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Wait for a connection to be queued to the FIFO and return it.\n+ *\n+ * Returns -1 if someone has already requested a shutdown.\n+ */\n+static int worker_thread__wait_for_connection(\n+\tstruct ipc_worker_thread_data *worker_thread_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tint fd = -1;\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\tfor (;;) {\n+\t\tif (server_data->shutdown_requested)\n+\t\t\tbreak;\n+\n+\t\tfd = fifo_dequeue(server_data);\n+\t\tif (fd >= 0)\n+\t\t\tbreak;\n+\n+\t\tpthread_cond_wait(&server_data->work_available_cond,\n+\t\t\t\t  &server_data->work_available_mutex);\n+\t}\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf_no_flush(response, response_len,\n+\t\t\t\t\t\t  reply_data->fd);\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_WAIT_POLL_TIMEOUT_MS (10)\n+\n+/*\n+ * If the client hangs up without sending any data on the wire, just\n+ * quietly close the socket and ignore this client.\n+ *\n+ * This worker thread is committed to reading the IPC request data\n+ * from the client at the other end of this fd.  Wait here for the\n+ * client to actually put something on the wire -- because if the\n+ * client just does a ping (connect and hangup without sending any\n+ * data), our use of the pkt-line read routines will spew an error\n+ * message.\n+ *\n+ * Return -1 if the client hung up.\n+ * Return 0 if data (possibly incomplete) is ready.\n+ */\n+static int worker_thread__wait_for_io_start(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tstruct pollfd pollfd[1];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = fd;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\tint in_shutdown;\n+\n+\t\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\t\tin_shutdown = server_data->shutdown_requested;\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\t\t\t/*\n+\t\t\t * If a shutdown is already in progress and this\n+\t\t\t * client has not started talking yet, just drop it.\n+\t\t\t */\n+\t\t\tif (in_shutdown)\n+\t\t\t\tgoto cleanup;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLHUP)\n+\t\t\tgoto cleanup;\n+\n+\t\tif (pollfd[0].revents & POLLIN)\n+\t\t\treturn 0;\n+\n+\t\tgoto cleanup;\n+\t}\n+\n+cleanup:\n+\tclose(fd);\n+\treturn -1;\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ */\n+static int worker_thread__do_io(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\t/* ASSERT NOT holding lock */\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.worker_thread_data = worker_thread_data;\n+\n+\treply_data.fd = fd;\n+\n+\tret = read_packetized_to_strbuf(\n+\t\treply_data.fd, &buf,\n+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);\n+\tif (ret >= 0) {\n+\t\tret = worker_thread_data->server_data->application_cb(\n+\t\t\tworker_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Block SIGPIPE on the current thread (so that we get EPIPE from\n+ * write() rather than an actual signal).\n+ *\n+ * Note that using sigchain_push() and _pop() to control SIGPIPE\n+ * around our IO calls is not thread safe:\n+ * [] It uses a global stack of handler frames.\n+ * [] It uses ALLOC_GROW() to resize it.\n+ * [] Finally, according to the `signal(2)` man-page:\n+ *    \"The effects of `signal()` in a multithreaded process are unspecified.\"\n+ */\n+static void thread_block_sigpipe(sigset_t *old_set)\n+{\n+\tsigset_t new_set;\n+\n+\tsigemptyset(&new_set);\n+\tsigaddset(&new_set, SIGPIPE);\n+\n+\tsigemptyset(old_set);\n+\tpthread_sigmask(SIG_BLOCK, &new_set, old_set);\n+}\n+\n+/*\n+ * Thread proc for an IPC worker thread.  It handles a series of\n+ * connections from clients.  It pulls the next fd from the queue\n+ * processes it, and then waits for the next client.\n+ *\n+ * Block SIGPIPE in this worker thread for the life of the thread.\n+ * This avoids stray (and sometimes delayed) SIGPIPE signals caused\n+ * by client errors and/or when we are under extremely heavy IO load.\n+ *\n+ * This means that the application callback will have SIGPIPE blocked.\n+ * The callback should not change it.\n+ */\n+static void *worker_thread_proc(void *_worker_thread_data)\n+{\n+\tstruct ipc_worker_thread_data *worker_thread_data = _worker_thread_data;\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tsigset_t old_set;\n+\tint fd, io;\n+\tint ret;\n+\n+\ttrace2_thread_start(\"ipc-worker\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tfd = worker_thread__wait_for_connection(worker_thread_data);\n+\t\tif (fd == -1)\n+\t\t\tbreak; /* in shutdown */\n+\n+\t\tio = worker_thread__wait_for_io_start(worker_thread_data, fd);\n+\t\tif (io == -1)\n+\t\t\tcontinue; /* client hung up without sending anything */\n+\n+\t\tret = worker_thread__do_io(worker_thread_data, fd);\n+\n+\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\ttrace2_data_string(\"ipc-worker\", NULL, \"queue_stop_async\",\n+\t\t\t\t\t   \"application_quit\");\n+\t\t\t/*\n+\t\t\t * The application layer is telling the ipc-server\n+\t\t\t * layer to shutdown.\n+\t\t\t *\n+\t\t\t * We DO NOT have a response to send to the client.\n+\t\t\t *\n+\t\t\t * Queue an async stop (to stop the other threads) and\n+\t\t\t * allow this worker thread to exit now (no sense waiting\n+\t\t\t * for the thread-pool shutdown signal).\n+\t\t\t *\n+\t\t\t * Other non-idle worker threads are allowed to finish\n+\t\t\t * responding to their current clients.\n+\t\t\t */\n+\t\t\tipc_server_stop_async(server_data);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)\n+\n+/*\n+ * Accept a new client connection on our socket.  This uses non-blocking\n+ * IO so that we can also wait for shutdown requests on our socket-pair\n+ * without actually spinning on a fast timeout.\n+ */\n+static int accept_thread__wait_for_connection(\n+\tstruct ipc_accept_thread_data *accept_thread_data)\n+{\n+\tstruct pollfd pollfd[2];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = accept_thread_data->fd_wait_shutdown;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tpollfd[1].fd = accept_thread_data->server_socket->fd_socket;\n+\t\tpollfd[1].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\treturn result;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\t/*\n+\t\t\t * If someone deletes or force-creates a new unix\n+\t\t\t * domain socket at our path, all future clients\n+\t\t\t * will be routed elsewhere and we silently starve.\n+\t\t\t * If that happens, just queue a shutdown.\n+\t\t\t */\n+\t\t\tif (unix_stream_server__was_stolen(\n+\t\t\t\t    accept_thread_data->server_socket)) {\n+\t\t\t\ttrace2_data_string(\"ipc-accept\", NULL,\n+\t\t\t\t\t\t   \"queue_stop_async\",\n+\t\t\t\t\t\t   \"socket_stolen\");\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\taccept_thread_data->server_data);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLIN) {\n+\t\t\t/* shutdown message queued to socketpair */\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (pollfd[1].revents & POLLIN) {\n+\t\t\t/* a connection is available on server_socket */\n+\n+\t\t\tint client_fd =\n+\t\t\t\taccept(accept_thread_data->server_socket->fd_socket,\n+\t\t\t\t       NULL, NULL);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\treturn client_fd;\n+\n+\t\t\t/*\n+\t\t\t * An error here is unlikely -- it probably\n+\t\t\t * indicates that the connecting process has\n+\t\t\t * already dropped the connection.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tBUG(\"unandled poll result errno=%d r[0]=%d r[1]=%d\",\n+\t\t    errno, pollfd[0].revents, pollfd[1].revents);\n+\t}\n+}\n+\n+/*\n+ * Thread proc for the IPC server \"accept thread\".  This waits for\n+ * an incoming socket connection, appends it to the queue of available\n+ * connections, and notifies a worker thread to process it.\n+ *\n+ * Block SIGPIPE in this thread for the life of the thread.  This\n+ * avoids any stray SIGPIPE signals when closing pipe fds under\n+ * extremely heavy loads (such as when the fifo queue is full and we\n+ * drop incomming connections).\n+ */\n+static void *accept_thread_proc(void *_accept_thread_data)\n+{\n+\tstruct ipc_accept_thread_data *accept_thread_data = _accept_thread_data;\n+\tstruct ipc_server_data *server_data = accept_thread_data->server_data;\n+\tsigset_t old_set;\n+\n+\ttrace2_thread_start(\"ipc-accept\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tint client_fd = accept_thread__wait_for_connection(\n+\t\t\taccept_thread_data);\n+\n+\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\tif (server_data->shutdown_requested) {\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\tclose(client_fd);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (client_fd < 0) {\n+\t\t\t/* ignore transient accept() errors */\n+\t\t}\n+\t\telse {\n+\t\t\tfifo_enqueue(server_data, client_fd);\n+\t\t\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\t\t}\n+\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * We can't predict the connection arrival rate relative to the worker\n+ * processing rate, therefore we allow the \"accept-thread\" to queue up\n+ * a generous number of connections, since we'd rather have the client\n+ * not unnecessarily timeout if we can avoid it.  (The assumption is\n+ * that this will be used for FSMonitor and a few second wait on a\n+ * connection is better than having the client timeout and do the full\n+ * computation itself.)\n+ *\n+ * The FIFO queue size is set to a multiple of the worker pool size.\n+ * This value chosen at random.\n+ */\n+#define FIFO_SCALE (100)\n+\n+/*\n+ * The backlog value for `listen(2)`.  This doesn't need to huge,\n+ * rather just large enough for our \"accept-thread\" to wake up and\n+ * queue incoming connections onto the FIFO without the kernel\n+ * dropping any.\n+ *\n+ * This value chosen at random.\n+ */\n+#define LISTEN_BACKLOG (50)\n+\n+static int create_listener_socket(\n+\tconst char *path,\n+\tconst struct ipc_server_opts *ipc_opts,\n+\tstruct unix_stream_server_socket **new_server_socket)\n+{\n+\tstruct unix_stream_server_socket *server_socket = NULL;\n+\tstruct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n+\tint ret;\n+\n+\tuslg_opts.listen_backlog_size = LISTEN_BACKLOG;\n+\tuslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;\n+\n+\tret = unix_stream_server__create(path, &uslg_opts, -1, &server_socket);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (set_socket_blocking_flag(server_socket->fd_socket, 1)) {\n+\t\tint saved_errno = errno;\n+\t\tunix_stream_server__free(server_socket);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\t*new_server_socket = server_socket;\n+\n+\ttrace2_data_string(\"ipc-server\", NULL, \"listen-with-lock\", path);\n+\treturn 0;\n+}\n+\n+static int setup_listener_socket(\n+\tconst char *path,\n+\tconst struct ipc_server_opts *ipc_opts,\n+\tstruct unix_stream_server_socket **new_server_socket)\n+{\n+\tint ret, saved_errno;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n+\n+\tret = create_listener_socket(path, ipc_opts, new_server_socket);\n+\n+\tsaved_errno = errno;\n+\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n+\terrno = saved_errno;\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Start IPC server in a pool of background threads.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct unix_stream_server_socket *server_socket = NULL;\n+\tstruct ipc_server_data *server_data;\n+\tint sv[2];\n+\tint k;\n+\tint ret;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\t/*\n+\t * Create a socketpair and set sv[1] to non-blocking.  This\n+\t * will used to send a shutdown message to the accept-thread\n+\t * and allows the accept-thread to wait on EITHER a client\n+\t * connection or a shutdown request without spinning.\n+\t */\n+\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)\n+\t\treturn -1;\n+\n+\tif (set_socket_blocking_flag(sv[1], 1)) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tret = setup_listener_socket(path, opts, &server_socket);\n+\tif (ret) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn ret;\n+\t}\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tpthread_mutex_init(&server_data->work_available_mutex, NULL);\n+\tpthread_cond_init(&server_data->work_available_cond, NULL);\n+\n+\tserver_data->queue_size = nr_threads * FIFO_SCALE;\n+\tserver_data->fifo_fds = xcalloc(server_data->queue_size,\n+\t\t\t\t\tsizeof(*server_data->fifo_fds));\n+\n+\tserver_data->accept_thread =\n+\t\txcalloc(1, sizeof(*server_data->accept_thread));\n+\tserver_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;\n+\tserver_data->accept_thread->server_data = server_data;\n+\tserver_data->accept_thread->server_socket = server_socket;\n+\tserver_data->accept_thread->fd_send_shutdown = sv[0];\n+\tserver_data->accept_thread->fd_wait_shutdown = sv[1];\n+\n+\tif (pthread_create(&server_data->accept_thread->pthread_id, NULL,\n+\t\t\t   accept_thread_proc, server_data->accept_thread))\n+\t\tdie_errno(_(\"could not start accept_thread '%s'\"), path);\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_worker_thread_data *wtd;\n+\n+\t\twtd = xcalloc(1, sizeof(*wtd));\n+\t\twtd->magic = MAGIC_WORKER_THREAD_DATA;\n+\t\twtd->server_data = server_data;\n+\n+\t\tif (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc,\n+\t\t\t\t   wtd)) {\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start worker[0] for '%s'\"),\n+\t\t\t\t    path);\n+\t\t\t/*\n+\t\t\t * Limp along with the thread pool that we have.\n+\t\t\t */\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\twtd->next_thread = server_data->worker_thread_list;\n+\t\tserver_data->worker_thread_list = wtd;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+/*\n+ * Gently tell the IPC server treads to shutdown.\n+ * Can be run on any thread.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tint fd;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\n+\tserver_data->shutdown_requested = 1;\n+\n+\t/*\n+\t * Write a byte to the shutdown socket pair to wake up the\n+\t * accept-thread.\n+\t */\n+\tif (write(server_data->accept_thread->fd_send_shutdown, \"Q\", 1) < 0)\n+\t\terror_errno(\"could not write to fd_send_shutdown\");\n+\n+\t/*\n+\t * Drain the queue of existing connections.\n+\t */\n+\twhile ((fd = fifo_dequeue(server_data)) != -1)\n+\t\tclose(fd);\n+\n+\t/*\n+\t * Gently tell worker threads to stop processing new connections\n+\t * and exit.  (This does not abort in-process conversations.)\n+\t */\n+\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\ttrace2_region_leave(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Wait for all IPC server threads to stop.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tpthread_join(server_data->accept_thread->pthread_id, NULL);\n+\n+\tif (!server_data->shutdown_requested)\n+\t\tBUG(\"ipc-server: accept-thread stopped for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tpthread_join(wtd->pthread_id, NULL);\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tstruct ipc_accept_thread_data * accept_thread_data;\n+\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\taccept_thread_data = server_data->accept_thread;\n+\tif (accept_thread_data) {\n+\t\tunix_stream_server__free(accept_thread_data->server_socket);\n+\n+\t\tif (accept_thread_data->fd_send_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_send_shutdown);\n+\t\tif (accept_thread_data->fd_wait_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_wait_shutdown);\n+\n+\t\tfree(server_data->accept_thread);\n+\t}\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tpthread_cond_destroy(&server_data->work_available_cond);\n+\tpthread_mutex_destroy(&server_data->work_available_mutex);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tfree(server_data->fifo_fds);\n+\tfree(server_data);\n+}\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex c94011269ebb..9897fcc8ea2a 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -248,6 +248,8 @@ endif()\n \n if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+else()\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c)\n endif()\n \n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\ndiff --git a/simple-ipc.h b/simple-ipc.h\nindex ab5619e3d76f..dc3606e30bd6 100644\n--- a/simple-ipc.h\n+++ b/simple-ipc.h\n@@ -5,7 +5,7 @@\n  * See Documentation/technical/api-simple-ipc.txt\n  */\n \n-#if defined(GIT_WINDOWS_NATIVE)\n+#if defined(GIT_WINDOWS_NATIVE) || !defined(NO_UNIX_SOCKETS)\n #define SUPPORTS_SIMPLE_IPC\n #endif\n \n@@ -62,11 +62,17 @@ struct ipc_client_connect_options {\n \t * the service and need to wait for it to become ready.\n \t */\n \tunsigned int wait_if_not_found:1;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n #define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n \t.wait_if_busy = 0, \\\n \t.wait_if_not_found = 0, \\\n+\t.uds_disallow_chdir = 0, \\\n }\n \n /*\n@@ -159,6 +165,11 @@ struct ipc_server_data;\n struct ipc_server_opts\n {\n \tint nr_threads;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"418597","messageId":"af3d13113bc983d78baf10f6d17e54625e13f832.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 03/12] pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:28Z","receivedAt":"2021-03-09T15:03:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIntroduce PACKET_READ_GENTLE_ON_READ_ERROR option to help libify the\npacket readers.\n\nSo far, the (possibly indirect) callers of `get_packet_data()` can ask\nthat function to return an error instead of `die()`ing upon end-of-file.\nHowever, random read errors will still cause the process to die.\n\nSo let's introduce an explicit option to tell the packet reader\nmachinery to please be nice and only return an error on read errors.\n\nThis change prepares pkt-line for use by long-running daemon processes.\nSuch processes should be able to serve multiple concurrent clients and\nand survive random IO errors.  If there is an error on one connection,\na daemon should be able to drop that connection and continue serving\nexisting and future connections.\n\nThis ability will be used by a Git-aware \"Builtin FSMonitor\" feature\nin a later patch series.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n pkt-line.c | 19 +++++++++++++++++--\n pkt-line.h | 11 ++++++++---\n 2 files changed, 25 insertions(+), 5 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex 434da3a0c48d..22775e37a72b 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -305,8 +305,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\t*src_size -= ret;\n \t} else {\n \t\tret = read_in_full(fd, dst, size);\n-\t\tif (ret < 0)\n+\t\tif (ret < 0) {\n+\t\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\t\treturn error_errno(_(\"read error\"));\n \t\t\tdie_errno(_(\"read error\"));\n+\t\t}\n \t}\n \n \t/* And complain if we didn't get enough bytes to satisfy the read. */\n@@ -314,6 +317,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n \t\t\treturn -1;\n \n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"the remote end hung up unexpectedly\"));\n \t\tdie(_(\"the remote end hung up unexpectedly\"));\n \t}\n \n@@ -342,6 +347,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \tlen = packet_length(linelen);\n \n \tif (len < 0) {\n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"protocol error: bad line length \"\n+\t\t\t\t       \"character: %.4s\"), linelen);\n \t\tdie(_(\"protocol error: bad line length character: %.4s\"), linelen);\n \t} else if (!len) {\n \t\tpacket_trace(\"0000\", 4, 0);\n@@ -356,12 +364,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \t\t*pktlen = 0;\n \t\treturn PACKET_READ_RESPONSE_END;\n \t} else if (len < 4) {\n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n \t}\n \n \tlen -= 4;\n-\tif ((unsigned)len >= size)\n+\tif ((unsigned)len >= size) {\n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n+\t}\n \n \tif (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) {\n \t\t*pktlen = -1;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 31012b9943bf..80ce0187e2ea 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -68,10 +68,15 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou\n  *\n  * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n  * ERR packet.\n+ *\n+ * If options contains PACKET_READ_GENTLE_ON_READ_ERROR, we will not die\n+ * on read errors, but instead return -1.  However, we may still die on an\n+ * ERR packet (if requested).\n  */\n-#define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n-#define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n-#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n+#define PACKET_READ_GENTLE_ON_EOF        (1u<<0)\n+#define PACKET_READ_CHOMP_NEWLINE        (1u<<1)\n+#define PACKET_READ_DIE_ON_ERR_PACKET    (1u<<2)\n+#define PACKET_READ_GENTLE_ON_READ_ERROR (1u<<3)\n int packet_read(int fd, char **src_buffer, size_t *src_len, char\n \t\t*buffer, unsigned size, int options);\n \n-- \ngitgitgadget\n\n"},{"id":"418598","messageId":"25157c1f48734eb96026e0c770d24874e4bf0503.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 02/12] pkt-line: do not issue flush packets in write_packetized_*()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:27Z","receivedAt":"2021-03-09T15:03:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nRemove the `packet_flush_gently()` call in `write_packetized_from_buf() and\n`write_packetized_from_fd()` and require the caller to call it if desired.\nRename both functions to `write_packetized_from_*_no_flush()` to prevent\nlater merge accidents.\n\n`write_packetized_from_buf()` currently only has one caller:\n`apply_multi_file_filter()` in `convert.c`.  It always wants a flush packet\nto be written after writing the payload.\n\nHowever, we are about to introduce a caller that wants to write many\npackets before a final flush packet, so let's make the caller responsible\nfor emitting the flush packet.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n convert.c  | 8 ++++++--\n pkt-line.c | 8 ++------\n pkt-line.h | 4 ++--\n 3 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07ce..976d4905cb3a 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -884,9 +884,13 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \t\tgoto done;\n \n \tif (fd >= 0)\n-\t\terr = write_packetized_from_fd(fd, process->in);\n+\t\terr = write_packetized_from_fd_no_flush(fd, process->in);\n \telse\n-\t\terr = write_packetized_from_buf(src, len, process->in);\n+\t\terr = write_packetized_from_buf_no_flush(src, len, process->in);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = packet_flush_gently(process->in);\n \tif (err)\n \t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 8b3512190442..434da3a0c48d 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -250,7 +250,7 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \tpacket_trace(data, len, 1);\n }\n \n-int write_packetized_from_fd(int fd_in, int fd_out)\n+int write_packetized_from_fd_no_flush(int fd_in, int fd_out)\n {\n \tchar *buf = xmalloc(LARGE_PACKET_DATA_MAX);\n \tint err = 0;\n@@ -266,13 +266,11 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n \t\t\tbreak;\n \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \tfree(buf);\n \treturn err;\n }\n \n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out)\n {\n \tint err = 0;\n \tsize_t bytes_written = 0;\n@@ -288,8 +286,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n \t\tbytes_written += bytes_to_write;\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\n \ndiff --git a/pkt-line.h b/pkt-line.h\nindex 8c90daa59ef0..31012b9943bf 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -32,8 +32,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f\n void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n int packet_flush_gently(int fd);\n int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n-int write_packetized_from_fd(int fd_in, int fd_out);\n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n+int write_packetized_from_fd_no_flush(int fd_in, int fd_out);\n+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);\n \n /*\n  * Read a packetized line into the buffer, which must be at least size bytes\n-- \ngitgitgadget\n\n"},{"id":"418599","messageId":"b73e66a69b61d5787511ae20826e58459fabee0c.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 04/12] pkt-line: add options argument to read_packetized_to_strbuf()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:29Z","receivedAt":"2021-03-09T15:03:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nUpdate the calling sequence of `read_packetized_to_strbuf()` to take\nan options argument and not assume a fixed set of options.  Update the\nonly existing caller accordingly to explicitly pass the\nformerly-assumed flags.\n\nThe `read_packetized_to_strbuf()` function calls `packet_read()` with\na fixed set of assumed options (`PACKET_READ_GENTLE_ON_EOF`).  This\nassumption has been fine for the single existing caller\n`apply_multi_file_filter()` in `convert.c`.\n\nIn a later commit we would like to add other callers to\n`read_packetized_to_strbuf()` that need a different set of options.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n convert.c  | 3 ++-\n pkt-line.c | 4 ++--\n pkt-line.h | 2 +-\n 3 files changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 976d4905cb3a..516f1095b06e 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -907,7 +907,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \t\tif (err)\n \t\t\tgoto done;\n \n-\t\terr = read_packetized_to_strbuf(process->out, &nbuf) < 0;\n+\t\terr = read_packetized_to_strbuf(process->out, &nbuf,\n+\t\t\t\t\t\tPACKET_READ_GENTLE_ON_EOF) < 0;\n \t\tif (err)\n \t\t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 22775e37a72b..695ea37b9d30 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -443,7 +443,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)\n \treturn packet_read_line_generic(-1, src, src_len, dst_len);\n }\n \n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options)\n {\n \tint packet_len;\n \n@@ -459,7 +459,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n \t\t\t * that there is already room for the extra byte.\n \t\t\t */\n \t\t\tsb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,\n-\t\t\tPACKET_READ_GENTLE_ON_EOF);\n+\t\t\toptions);\n \t\tif (packet_len <= 0)\n \t\t\tbreak;\n \t\tsb_out->len += packet_len;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 80ce0187e2ea..5af5f4568768 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -136,7 +136,7 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);\n /*\n  * Reads a stream of variable sized packets until a flush packet is detected.\n  */\n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options);\n \n /*\n  * Receive multiplexed output stream over git native protocol.\n-- \ngitgitgadget\n\n"},{"id":"418601","messageId":"d6ff6e0e050acf7efecfad86fcb963b711ee59ff.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 08/12] unix-socket: add backlog size option to unix_stream_listen()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:33Z","receivedAt":"2021-03-09T15:03:54Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nUpdate `unix_stream_listen()` to take an options structure to override\ndefault behaviors.  This commit includes the size of the `listen()` backlog.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache--daemon.c |  3 ++-\n unix-socket.c                      | 11 +++++++++--\n unix-socket.h                      |  9 ++++++++-\n 3 files changed, 19 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c\nindex c61f123a3b81..4c6c89ab0de2 100644\n--- a/builtin/credential-cache--daemon.c\n+++ b/builtin/credential-cache--daemon.c\n@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)\n \n static void serve_cache(const char *socket_path, int debug)\n {\n+\tstruct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n \tint fd;\n \n-\tfd = unix_stream_listen(socket_path);\n+\tfd = unix_stream_listen(socket_path, &opts);\n \tif (fd < 0)\n \t\tdie_errno(\"unable to bind to '%s'\", socket_path);\n \ndiff --git a/unix-socket.c b/unix-socket.c\nindex 69f81d64e9d5..012becd93d57 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,6 +1,8 @@\n #include \"cache.h\"\n #include \"unix-socket.h\"\n \n+#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n+\n static int chdir_len(const char *orig, int len)\n {\n \tchar *path = xmemdupz(orig, len);\n@@ -89,9 +91,11 @@ int unix_stream_connect(const char *path)\n \treturn -1;\n }\n \n-int unix_stream_listen(const char *path)\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts)\n {\n \tint fd = -1, saved_errno;\n+\tint backlog;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -106,7 +110,10 @@ int unix_stream_listen(const char *path)\n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \n-\tif (listen(fd, 5) < 0)\n+\tbacklog = opts->listen_backlog_size;\n+\tif (backlog <= 0)\n+\t\tbacklog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;\n+\tif (listen(fd, backlog) < 0)\n \t\tgoto fail;\n \n \tunix_sockaddr_cleanup(&ctx);\ndiff --git a/unix-socket.h b/unix-socket.h\nindex e271aeec5a07..ec2fb3ea7267 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -1,7 +1,14 @@\n #ifndef UNIX_SOCKET_H\n #define UNIX_SOCKET_H\n \n+struct unix_stream_listen_opts {\n+\tint listen_backlog_size;\n+};\n+\n+#define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }\n+\n int unix_stream_connect(const char *path);\n-int unix_stream_listen(const char *path);\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts);\n \n #endif /* UNIX_SOCKET_H */\n-- \ngitgitgadget\n\n"},{"id":"418602","messageId":"6ccc7472096fb239fdeefe6b396ff747e198abe7.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:37Z","receivedAt":"2021-03-09T15:03:54Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate t0052-simple-ipc.sh with unit tests for the \"simple-ipc\" mechanism.\n\nCreate t/helper/test-simple-ipc test tool to exercise the \"simple-ipc\"\nfunctions.\n\nWhen the tool is invoked with \"run-daemon\", it runs a server to listen\nfor \"simple-ipc\" connections on a test socket or named pipe and\nresponds to a set of commands to exercise/stress the communication\nsetup.\n\nWhen the tool is invoked with \"start-daemon\", it spawns a \"run-daemon\"\ncommand in the background and waits for the server to become ready\nbefore exiting.  (This helps make unit tests in t0052 more predictable\nand avoids the need for arbitrary sleeps in the test script.)\n\nThe tool also has a series of client \"send\" commands to send commands\nand data to a server instance.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                   |   1 +\n t/helper/test-simple-ipc.c | 787 +++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0052-simple-ipc.sh      | 122 ++++++\n 5 files changed, 912 insertions(+)\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\ndiff --git a/Makefile b/Makefile\nindex 20dd65d19658..e556388d28d0 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -734,6 +734,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o\n TEST_BUILTINS_OBJS += test-sha1.o\n TEST_BUILTINS_OBJS += test-sha256.o\n TEST_BUILTINS_OBJS += test-sigchain.o\n+TEST_BUILTINS_OBJS += test-simple-ipc.o\n TEST_BUILTINS_OBJS += test-strcmp-offset.o\n TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\ndiff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c\nnew file mode 100644\nindex 000000000000..42040ef81b1e\n--- /dev/null\n+++ b/t/helper/test-simple-ipc.c\n@@ -0,0 +1,787 @@\n+/*\n+ * test-simple-ipc.c: verify that the Inter-Process Communication works.\n+ */\n+\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"simple-ipc.h\"\n+#include \"parse-options.h\"\n+#include \"thread-utils.h\"\n+#include \"strvec.h\"\n+\n+#ifndef SUPPORTS_SIMPLE_IPC\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tdie(\"simple IPC not available on this platform\");\n+}\n+#else\n+\n+/*\n+ * The test daemon defines an \"application callback\" that supports a\n+ * series of commands (see `test_app_cb()`).\n+ *\n+ * Unknown commands are caught here and we send an error message back\n+ * to the client process.\n+ */\n+static int app__unhandled_command(const char *command,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n+\n+\tstrbuf_addf(&buf, \"unhandled command: %s\", command);\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a single very large buffer.  This is to ensure that\n+ * long response are properly handled -- whether the chunking occurs\n+ * in the kernel or in the (probably pkt-line) layer.\n+ */\n+#define BIG_ROWS (10000)\n+static int app__big_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t    struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < BIG_ROWS; row++)\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a series of lines.  This is to ensure that we can incrementally\n+ * compute the response and chunk it to the client.\n+ */\n+#define CHUNK_ROWS (10000)\n+static int app__chunk_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t      struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < CHUNK_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Slowly reply with a series of lines.  This is to model an expensive to\n+ * compute chunked response (which might happen if this callback is running\n+ * in a thread and is fighting for a lock with other threads).\n+ */\n+#define SLOW_ROWS     (1000)\n+#define SLOW_DELAY_MS (10)\n+static int app__slow_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t     struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < SLOW_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t\tsleep_millisec(SLOW_DELAY_MS);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * The client sent a command followed by a (possibly very) large buffer.\n+ */\n+static int app__sendbytes_command(const char *received,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\tconst char *p = \"?\";\n+\tint len_ballast = 0;\n+\tint k;\n+\tint errs = 0;\n+\tint ret;\n+\n+\tif (skip_prefix(received, \"sendbytes \", &p))\n+\t\tlen_ballast = strlen(p);\n+\n+\t/*\n+\t * Verify that the ballast is n copies of a single letter.\n+\t * And that the multi-threaded IO layer didn't cross the streams.\n+\t */\n+\tfor (k = 1; k < len_ballast; k++)\n+\t\tif (p[k] != p[0])\n+\t\t\terrs++;\n+\n+\tif (errs)\n+\t\tstrbuf_addf(&buf_resp, \"errs:%d\\n\", errs);\n+\telse\n+\t\tstrbuf_addf(&buf_resp, \"rcvd:%c%08d\\n\", p[0], len_ballast);\n+\n+\tret = reply_cb(reply_data, buf_resp.buf, buf_resp.len);\n+\n+\tstrbuf_release(&buf_resp);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * An arbitrary fixed address to verify that the application instance\n+ * data is handled properly.\n+ */\n+static int my_app_data = 42;\n+\n+static ipc_server_application_cb test_app_cb;\n+\n+/*\n+ * This is the \"application callback\" that sits on top of the\n+ * \"ipc-server\".  It completely defines the set of commands supported\n+ * by this application.\n+ */\n+static int test_app_cb(void *application_data,\n+\t\t       const char *command,\n+\t\t       ipc_server_reply_cb *reply_cb,\n+\t\t       struct ipc_server_reply_data *reply_data)\n+{\n+\t/*\n+\t * Verify that we received the application-data that we passed\n+\t * when we started the ipc-server.  (We have several layers of\n+\t * callbacks calling callbacks and it's easy to get things mixed\n+\t * up (especially when some are \"void*\").)\n+\t */\n+\tif (application_data != (void*)&my_app_data)\n+\t\tBUG(\"application_cb: application_data pointer wrong\");\n+\n+\tif (!strcmp(command, \"quit\")) {\n+\t\t/*\n+\t\t * The client sent a \"quit\" command.  This is an async\n+\t\t * request for the server to shutdown.\n+\t\t *\n+\t\t * We DO NOT send the client a response message\n+\t\t * (because we have nothing to say and the other\n+\t\t * server threads have not yet stopped).\n+\t\t *\n+\t\t * Tell the ipc-server layer to start shutting down.\n+\t\t * This includes: stop listening for new connections\n+\t\t * on the socket/pipe and telling all worker threads\n+\t\t * to finish/drain their outgoing responses to other\n+\t\t * clients.\n+\t\t *\n+\t\t * This DOES NOT force an immediate sync shutdown.\n+\t\t */\n+\t\treturn SIMPLE_IPC_QUIT;\n+\t}\n+\n+\tif (!strcmp(command, \"ping\")) {\n+\t\tconst char *answer = \"pong\";\n+\t\treturn reply_cb(reply_data, answer, strlen(answer));\n+\t}\n+\n+\tif (!strcmp(command, \"big\"))\n+\t\treturn app__big_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"chunk\"))\n+\t\treturn app__chunk_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"slow\"))\n+\t\treturn app__slow_command(reply_cb, reply_data);\n+\n+\tif (starts_with(command, \"sendbytes \"))\n+\t\treturn app__sendbytes_command(command, reply_cb, reply_data);\n+\n+\treturn app__unhandled_command(command, reply_cb, reply_data);\n+}\n+\n+struct cl_args\n+{\n+\tconst char *subcommand;\n+\tconst char *path;\n+\tconst char *token;\n+\n+\tint nr_threads;\n+\tint max_wait_sec;\n+\tint bytecount;\n+\tint batchsize;\n+\n+\tchar bytevalue;\n+};\n+\n+static struct cl_args cl_args = {\n+\t.subcommand = NULL,\n+\t.path = \"ipc-test\",\n+\t.token = NULL,\n+\n+\t.nr_threads = 5,\n+\t.max_wait_sec = 60,\n+\t.bytecount = 1024,\n+\t.batchsize = 10,\n+\n+\t.bytevalue = 'x',\n+};\n+\n+/*\n+ * This process will run as a simple-ipc server and listen for IPC commands\n+ * from client processes.\n+ */\n+static int daemon__run_server(void)\n+{\n+\tint ret;\n+\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = cl_args.nr_threads,\n+\t};\n+\n+\t/*\n+\t * Synchronously run the ipc-server.  We don't need any application\n+\t * instance data, so pass an arbitrary pointer (that we'll later\n+\t * verify made the round trip).\n+\t */\n+\tret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data);\n+\tif (ret == -2)\n+\t\terror(_(\"socket/pipe already in use: '%s'\"), cl_args.path);\n+\telse if (ret == -1)\n+\t\terror_errno(_(\"could not start server on: '%s'\"), cl_args.path);\n+\n+\treturn ret;\n+}\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+/*\n+ * This is adapted from `daemonize()`.  Use `fork()` to directly create and\n+ * run the daemon in a child process.\n+ */\n+static int spawn_server(pid_t *pid)\n+{\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = cl_args.nr_threads,\n+\t};\n+\n+\t*pid = fork();\n+\n+\tswitch (*pid) {\n+\tcase 0:\n+\t\tif (setsid() == -1)\n+\t\t\terror_errno(_(\"setsid failed\"));\n+\t\tclose(0);\n+\t\tclose(1);\n+\t\tclose(2);\n+\t\tsanitize_stdfds();\n+\n+\t\treturn ipc_server_run(cl_args.path, &opts, test_app_cb,\n+\t\t\t\t      (void*)&my_app_data);\n+\n+\tcase -1:\n+\t\treturn error_errno(_(\"could not spawn daemon in the background\"));\n+\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+}\n+#else\n+/*\n+ * Conceptually like `daemonize()` but different because Windows does not\n+ * have `fork(2)`.  Spawn a normal Windows child process but without the\n+ * limitations of `start_command()` and `finish_command()`.\n+ */\n+static int spawn_server(pid_t *pid)\n+{\n+\tchar test_tool_exe[MAX_PATH];\n+\tstruct strvec args = STRVEC_INIT;\n+\tint in, out;\n+\n+\tGetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);\n+\n+\tin = open(\"/dev/null\", O_RDONLY);\n+\tout = open(\"/dev/null\", O_WRONLY);\n+\n+\tstrvec_push(&args, test_tool_exe);\n+\tstrvec_push(&args, \"simple-ipc\");\n+\tstrvec_push(&args, \"run-daemon\");\n+\tstrvec_pushf(&args, \"--name=%s\", cl_args.path);\n+\tstrvec_pushf(&args, \"--threads=%d\", cl_args.nr_threads);\n+\n+\t*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);\n+\tclose(in);\n+\tclose(out);\n+\n+\tstrvec_clear(&args);\n+\n+\tif (*pid < 0)\n+\t\treturn error(_(\"could not spawn daemon in the background\"));\n+\n+\treturn 0;\n+}\n+#endif\n+\n+/*\n+ * This is adapted from `wait_or_whine()`.  Watch the child process and\n+ * let it get started and begin listening for requests on the socket\n+ * before reporting our success.\n+ */\n+static int wait_for_server_startup(pid_t pid_child)\n+{\n+\tint status;\n+\tpid_t pid_seen;\n+\tenum ipc_active_state s;\n+\ttime_t time_limit, now;\n+\n+\ttime(&time_limit);\n+\ttime_limit += cl_args.max_wait_sec;\n+\n+\tfor (;;) {\n+\t\tpid_seen = waitpid(pid_child, &status, WNOHANG);\n+\n+\t\tif (pid_seen == -1)\n+\t\t\treturn error_errno(_(\"waitpid failed\"));\n+\n+\t\telse if (pid_seen == 0) {\n+\t\t\t/*\n+\t\t\t * The child is still running (this should be\n+\t\t\t * the normal case).  Try to connect to it on\n+\t\t\t * the socket and see if it is ready for\n+\t\t\t * business.\n+\t\t\t *\n+\t\t\t * If there is another daemon already running,\n+\t\t\t * our child will fail to start (possibly\n+\t\t\t * after a timeout on the lock), but we don't\n+\t\t\t * care (who responds) if the socket is live.\n+\t\t\t */\n+\t\t\ts = ipc_get_active_state(cl_args.path);\n+\t\t\tif (s == IPC_STATE__LISTENING)\n+\t\t\t\treturn 0;\n+\n+\t\t\ttime(&now);\n+\t\t\tif (now > time_limit)\n+\t\t\t\treturn error(_(\"daemon not online yet\"));\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\telse if (pid_seen == pid_child) {\n+\t\t\t/*\n+\t\t\t * The new child daemon process shutdown while\n+\t\t\t * it was starting up, so it is not listening\n+\t\t\t * on the socket.\n+\t\t\t *\n+\t\t\t * Try to ping the socket in the odd chance\n+\t\t\t * that another daemon started (or was already\n+\t\t\t * running) while our child was starting.\n+\t\t\t *\n+\t\t\t * Again, we don't care who services the socket.\n+\t\t\t */\n+\t\t\ts = ipc_get_active_state(cl_args.path);\n+\t\t\tif (s == IPC_STATE__LISTENING)\n+\t\t\t\treturn 0;\n+\n+\t\t\t/*\n+\t\t\t * We don't care about the WEXITSTATUS() nor\n+\t\t\t * any of the WIF*(status) values because\n+\t\t\t * `cmd__simple_ipc()` does the `!!result`\n+\t\t\t * trick on all function return values.\n+\t\t\t *\n+\t\t\t * So it is sufficient to just report the\n+\t\t\t * early shutdown as an error.\n+\t\t\t */\n+\t\t\treturn error(_(\"daemon failed to start\"));\n+\t\t}\n+\n+\t\telse\n+\t\t\treturn error(_(\"waitpid is confused\"));\n+\t}\n+}\n+\n+/*\n+ * This process will start a simple-ipc server in a background process and\n+ * wait for it to become ready.  This is like `daemonize()` but gives us\n+ * more control and better error reporting (and makes it easier to write\n+ * unit tests).\n+ */\n+static int daemon__start_server(void)\n+{\n+\tpid_t pid_child;\n+\tint ret;\n+\n+\t/*\n+\t * Run the actual daemon in a background process.\n+\t */\n+\tret = spawn_server(&pid_child);\n+\tif (pid_child <= 0)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Let the parent wait for the child process to get started\n+\t * and begin listening for requests on the socket.\n+\t */\n+\tret = wait_for_server_startup(pid_child);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * This process will run a quick probe to see if a simple-ipc server\n+ * is active on this path.\n+ *\n+ * Returns 0 if the server is alive.\n+ */\n+static int client__probe_server(void)\n+{\n+\tenum ipc_active_state s;\n+\n+\ts = ipc_get_active_state(cl_args.path);\n+\tswitch (s) {\n+\tcase IPC_STATE__LISTENING:\n+\t\treturn 0;\n+\n+\tcase IPC_STATE__NOT_LISTENING:\n+\t\treturn error(\"no server listening at '%s'\", cl_args.path);\n+\n+\tcase IPC_STATE__PATH_NOT_FOUND:\n+\t\treturn error(\"path not found '%s'\", cl_args.path);\n+\n+\tcase IPC_STATE__INVALID_PATH:\n+\t\treturn error(\"invalid pipe/socket name '%s'\", cl_args.path);\n+\n+\tcase IPC_STATE__OTHER_ERROR:\n+\tdefault:\n+\t\treturn error(\"other error for '%s'\", cl_args.path);\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command token to an already-running server daemon and\n+ * print the response.\n+ *\n+ * This is a simple 1 word command/token that `test_app_cb()` (in the\n+ * daemon process) will understand.\n+ */\n+static int client__send_ipc(void)\n+{\n+\tconst char *command = \"(no-command)\";\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\tif (cl_args.token && *cl_args.token)\n+\t\tcommand = cl_args.token;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (!ipc_client_send_command(cl_args.path, &options, command, &buf)) {\n+\t\tif (buf.len) {\n+\t\t\tprintf(\"%s\\n\", buf.buf);\n+\t\t\tfflush(stdout);\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"failed to send '%s' to '%s'\", command, cl_args.path);\n+}\n+\n+/*\n+ * Send an IPC command to an already-running server and ask it to\n+ * shutdown.  \"send quit\" is an async request and queues a shutdown\n+ * event in the server, so we spin and wait here for it to actually\n+ * shutdown to make the unit tests a little easier to write.\n+ */\n+static int client__stop_server(void)\n+{\n+\tint ret;\n+\ttime_t time_limit, now;\n+\tenum ipc_active_state s;\n+\n+\ttime(&time_limit);\n+\ttime_limit += cl_args.max_wait_sec;\n+\n+\tcl_args.token = \"quit\";\n+\n+\tret = client__send_ipc();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tfor (;;) {\n+\t\tsleep_millisec(100);\n+\n+\t\ts = ipc_get_active_state(cl_args.path);\n+\n+\t\tif (s != IPC_STATE__LISTENING) {\n+\t\t\t/*\n+\t\t\t * The socket/pipe is gone and/or has stopped\n+\t\t\t * responding.  Lets assume that the daemon\n+\t\t\t * process has exited too.\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\ttime(&now);\n+\t\tif (now > time_limit)\n+\t\t\treturn error(_(\"daemon has not shutdown yet\"));\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command followed by ballast to confirm that a large\n+ * message can be sent and that the kernel or pkt-line layers will\n+ * properly chunk it and that the daemon receives the entire message.\n+ */\n+static int do_sendbytes(int bytecount, char byte, const char *path,\n+\t\t\tconst struct ipc_client_connect_options *options)\n+{\n+\tstruct strbuf buf_send = STRBUF_INIT;\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_send, \"sendbytes \");\n+\tstrbuf_addchars(&buf_send, byte, bytecount);\n+\n+\tif (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {\n+\t\tstrbuf_rtrim(&buf_resp);\n+\t\tprintf(\"sent:%c%08d %s\\n\", byte, bytecount, buf_resp.buf);\n+\t\tfflush(stdout);\n+\t\tstrbuf_release(&buf_send);\n+\t\tstrbuf_release(&buf_resp);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"client failed to sendbytes(%d, '%c') to '%s'\",\n+\t\t     bytecount, byte, path);\n+}\n+\n+/*\n+ * Send an IPC command with ballast to an already-running server daemon.\n+ */\n+static int client__sendbytes(void)\n+{\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\toptions.uds_disallow_chdir = 0;\n+\n+\treturn do_sendbytes(cl_args.bytecount, cl_args.bytevalue, cl_args.path,\n+\t\t\t    &options);\n+}\n+\n+struct multiple_thread_data {\n+\tpthread_t pthread_id;\n+\tstruct multiple_thread_data *next;\n+\tconst char *path;\n+\tint bytecount;\n+\tint batchsize;\n+\tint sum_errors;\n+\tint sum_good;\n+\tchar letter;\n+};\n+\n+static void *multiple_thread_proc(void *_multiple_thread_data)\n+{\n+\tstruct multiple_thread_data *d = _multiple_thread_data;\n+\tint k;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\t/*\n+\t * A multi-threaded client should not be randomly calling chdir().\n+\t * The test will pass without this restriction because the test is\n+\t * not otherwise accessing the filesystem, but it makes us honest.\n+\t */\n+\toptions.uds_disallow_chdir = 1;\n+\n+\ttrace2_thread_start(\"multiple\");\n+\n+\tfor (k = 0; k < d->batchsize; k++) {\n+\t\tif (do_sendbytes(d->bytecount + k, d->letter, d->path, &options))\n+\t\t\td->sum_errors++;\n+\t\telse\n+\t\t\td->sum_good++;\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * Start a client-side thread pool.  Each thread sends a series of\n+ * IPC requests.  Each request is on a new connection to the server.\n+ */\n+static int client__multiple(void)\n+{\n+\tstruct multiple_thread_data *list = NULL;\n+\tint k;\n+\tint sum_join_errors = 0;\n+\tint sum_thread_errors = 0;\n+\tint sum_good = 0;\n+\n+\tfor (k = 0; k < cl_args.nr_threads; k++) {\n+\t\tstruct multiple_thread_data *d = xcalloc(1, sizeof(*d));\n+\t\td->next = list;\n+\t\td->path = cl_args.path;\n+\t\td->bytecount = cl_args.bytecount + cl_args.batchsize*(k/26);\n+\t\td->batchsize = cl_args.batchsize;\n+\t\td->sum_errors = 0;\n+\t\td->sum_good = 0;\n+\t\td->letter = 'A' + (k % 26);\n+\n+\t\tif (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) {\n+\t\t\twarning(\"failed to create thread[%d] skipping remainder\", k);\n+\t\t\tfree(d);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tlist = d;\n+\t}\n+\n+\twhile (list) {\n+\t\tstruct multiple_thread_data *d = list;\n+\n+\t\tif (pthread_join(d->pthread_id, NULL))\n+\t\t\tsum_join_errors++;\n+\n+\t\tsum_thread_errors += d->sum_errors;\n+\t\tsum_good += d->sum_good;\n+\n+\t\tlist = d->next;\n+\t\tfree(d);\n+\t}\n+\n+\tprintf(\"client (good %d) (join %d), (errors %d)\\n\",\n+\t       sum_good, sum_join_errors, sum_thread_errors);\n+\n+\treturn (sum_join_errors + sum_thread_errors) ? 1 : 0;\n+}\n+\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tconst char * const simple_ipc_usage[] = {\n+\t\tN_(\"test-helper simple-ipc is-active    [<name>] [<options>]\"),\n+\t\tN_(\"test-helper simple-ipc run-daemon   [<name>] [<threads>]\"),\n+\t\tN_(\"test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]\"),\n+\t\tN_(\"test-helper simple-ipc stop-daemon  [<name>] [<max-wait>]\"),\n+\t\tN_(\"test-helper simple-ipc send         [<name>] [<token>]\"),\n+\t\tN_(\"test-helper simple-ipc sendbytes    [<name>] [<bytecount>] [<byte>]\"),\n+\t\tN_(\"test-helper simple-ipc multiple     [<name>] [<threads>] [<bytecount>] [<batchsize>]\"),\n+\t\tNULL\n+\t};\n+\n+\tconst char *bytevalue = NULL;\n+\n+\tstruct option options[] = {\n+#ifndef GIT_WINDOWS_NATIVE\n+\t\tOPT_STRING(0, \"name\", &cl_args.path, N_(\"name\"), N_(\"name or pathname of unix domain socket\")),\n+#else\n+\t\tOPT_STRING(0, \"name\", &cl_args.path, N_(\"name\"), N_(\"named-pipe name\")),\n+#endif\n+\t\tOPT_INTEGER(0, \"threads\", &cl_args.nr_threads, N_(\"number of threads in server thread pool\")),\n+\t\tOPT_INTEGER(0, \"max-wait\", &cl_args.max_wait_sec, N_(\"seconds to wait for daemon to start or stop\")),\n+\n+\t\tOPT_INTEGER(0, \"bytecount\", &cl_args.bytecount, N_(\"number of bytes\")),\n+\t\tOPT_INTEGER(0, \"batchsize\", &cl_args.batchsize, N_(\"number of requests per thread\")),\n+\n+\t\tOPT_STRING(0, \"byte\", &bytevalue, N_(\"byte\"), N_(\"ballast character\")),\n+\t\tOPT_STRING(0, \"token\", &cl_args.token, N_(\"token\"), N_(\"command token to send to the server\")),\n+\n+\t\tOPT_END()\n+\t};\n+\n+\tif (argc < 2)\n+\t\tusage_with_options(simple_ipc_usage, options);\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage_with_options(simple_ipc_usage, options);\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"SUPPORTS_SIMPLE_IPC\"))\n+\t\treturn 0;\n+\n+\tcl_args.subcommand = argv[1];\n+\n+\targc--;\n+\targv++;\n+\n+\targc = parse_options(argc, argv, NULL, options, simple_ipc_usage, 0);\n+\n+\tif (cl_args.nr_threads < 1)\n+\t\tcl_args.nr_threads = 1;\n+\tif (cl_args.max_wait_sec < 0)\n+\t\tcl_args.max_wait_sec = 0;\n+\tif (cl_args.bytecount < 1)\n+\t\tcl_args.bytecount = 1;\n+\tif (cl_args.batchsize < 1)\n+\t\tcl_args.batchsize = 1;\n+\n+\tif (bytevalue && *bytevalue)\n+\t\tcl_args.bytevalue = bytevalue[0];\n+\n+\t/*\n+\t * Use '!!' on all dispatch functions to map from `error()` style\n+\t * (returns -1) style to `test_must_fail` style (expects 1).  This\n+\t * makes shell error messages less confusing.\n+\t */\n+\n+\tif (!strcmp(cl_args.subcommand, \"is-active\"))\n+\t\treturn !!client__probe_server();\n+\n+\tif (!strcmp(cl_args.subcommand, \"run-daemon\"))\n+\t\treturn !!daemon__run_server();\n+\n+\tif (!strcmp(cl_args.subcommand, \"start-daemon\"))\n+\t\treturn !!daemon__start_server();\n+\n+\t/*\n+\t * Client commands follow.  Ensure a server is running before\n+\t * sending any data.  This might be overkill, but then again\n+\t * this is a test harness.\n+\t */\n+\n+\tif (!strcmp(cl_args.subcommand, \"stop-daemon\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__stop_server();\n+\t}\n+\n+\tif (!strcmp(cl_args.subcommand, \"send\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__send_ipc();\n+\t}\n+\n+\tif (!strcmp(cl_args.subcommand, \"sendbytes\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__sendbytes();\n+\t}\n+\n+\tif (!strcmp(cl_args.subcommand, \"multiple\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__multiple();\n+\t}\n+\n+\tdie(\"Unhandled subcommand: '%s'\", cl_args.subcommand);\n+}\n+#endif\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f97cd9f48a69..287aa6002307 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -65,6 +65,7 @@ static struct test_cmd cmds[] = {\n \t{ \"sha1\", cmd__sha1 },\n \t{ \"sha256\", cmd__sha256 },\n \t{ \"sigchain\", cmd__sigchain },\n+\t{ \"simple-ipc\", cmd__simple_ipc },\n \t{ \"strcmp-offset\", cmd__strcmp_offset },\n \t{ \"string-list\", cmd__string_list },\n \t{ \"submodule-config\", cmd__submodule_config },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 28072c0ad5ab..9ea4b31011dd 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -55,6 +55,7 @@ int cmd__sha1(int argc, const char **argv);\n int cmd__oid_array(int argc, const char **argv);\n int cmd__sha256(int argc, const char **argv);\n int cmd__sigchain(int argc, const char **argv);\n+int cmd__simple_ipc(int argc, const char **argv);\n int cmd__strcmp_offset(int argc, const char **argv);\n int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\ndiff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\nnew file mode 100755\nindex 000000000000..ff98be31a51b\n--- /dev/null\n+++ b/t/t0052-simple-ipc.sh\n@@ -0,0 +1,122 @@\n+#!/bin/sh\n+\n+test_description='simple command server'\n+\n+. ./test-lib.sh\n+\n+test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {\n+\tskip_all='simple IPC not supported on this platform'\n+\ttest_done\n+}\n+\n+stop_simple_IPC_server () {\n+\ttest-tool simple-ipc stop-daemon\n+}\n+\n+test_expect_success 'start simple command server' '\n+\ttest_atexit stop_simple_IPC_server &&\n+\ttest-tool simple-ipc start-daemon --threads=8 &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'simple command server' '\n+\ttest-tool simple-ipc send --token=ping >actual &&\n+\techo pong >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'servers cannot share the same path' '\n+\ttest_must_fail test-tool simple-ipc run-daemon &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'big response' '\n+\ttest-tool simple-ipc send --token=big >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'chunk response' '\n+\ttest-tool simple-ipc send --token=chunk >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'slow response' '\n+\ttest-tool simple-ipc send --token=slow >actual &&\n+\ttest_line_count -ge 100 actual &&\n+\tgrep -q \"big: [0]*99\\$\" actual\n+'\n+\n+# Send an IPC with n=100,000 bytes of ballast.  This should be large enough\n+# to force both the kernel and the pkt-line layer to chunk the message to the\n+# daemon and for the daemon to receive it in chunks.\n+#\n+test_expect_success 'sendbytes' '\n+\ttest-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual &&\n+\tgrep \"sent:A00100000 rcvd:A00100000\" actual\n+'\n+\n+# Start a series of <threads> client threads that each make <batchsize>\n+# IPC requests to the server.  Each (<threads> * <batchsize>) request\n+# will open a new connection to the server and randomly bind to a server\n+# thread.  Each client thread exits after completing its batch.  So the\n+# total number of live client threads will be smaller than the total.\n+# Each request will send a message containing at least <bytecount> bytes\n+# of ballast.  (Responses are small.)\n+#\n+# The purpose here is to test threading in the server and responding to\n+# many concurrent client requests (regardless of whether they come from\n+# 1 client process or many).  And to test that the server side of the\n+# named pipe/socket is stable.  (On Windows this means that the server\n+# pipe is properly recycled.)\n+#\n+# On Windows it also lets us adjust the connection timeout in the\n+# `ipc_client_send_command()`.\n+#\n+# Note it is easy to drive the system into failure by requesting an\n+# insane number of threads on client or server and/or increasing the\n+# per-thread batchsize or the per-request bytecount (ballast).\n+# On Windows these failures look like \"pipe is busy\" errors.\n+# So I've chosen fairly conservative values for now.\n+#\n+# We expect output of the form \"sent:<letter><length> ...\"\n+# With terms (7, 19, 13) we expect:\n+#   <letter> in [A-G]\n+#   <length> in [19+0 .. 19+(13-1)]\n+# and (7 * 13) successful responses.\n+#\n+test_expect_success 'stress test threads' '\n+\ttest-tool simple-ipc multiple \\\n+\t\t--threads=7 \\\n+\t\t--bytecount=19 \\\n+\t\t--batchsize=13 \\\n+\t\t>actual &&\n+\ttest_line_count = 92 actual &&\n+\tgrep \"good 91\" actual &&\n+\tgrep \"sent:A\" <actual >actual_a &&\n+\tcat >expect_a <<-EOF &&\n+\t\tsent:A00000019 rcvd:A00000019\n+\t\tsent:A00000020 rcvd:A00000020\n+\t\tsent:A00000021 rcvd:A00000021\n+\t\tsent:A00000022 rcvd:A00000022\n+\t\tsent:A00000023 rcvd:A00000023\n+\t\tsent:A00000024 rcvd:A00000024\n+\t\tsent:A00000025 rcvd:A00000025\n+\t\tsent:A00000026 rcvd:A00000026\n+\t\tsent:A00000027 rcvd:A00000027\n+\t\tsent:A00000028 rcvd:A00000028\n+\t\tsent:A00000029 rcvd:A00000029\n+\t\tsent:A00000030 rcvd:A00000030\n+\t\tsent:A00000031 rcvd:A00000031\n+\tEOF\n+\ttest_cmp expect_a actual_a\n+'\n+\n+test_expect_success 'stop-daemon works' '\n+\ttest-tool simple-ipc stop-daemon &&\n+\ttest_must_fail test-tool simple-ipc is-active &&\n+\ttest_must_fail test-tool simple-ipc send --token=ping\n+'\n+\n+test_done\n-- \ngitgitgadget\n"},{"id":"418603","messageId":"1ee9de55a106e46dab6126fe8ca2a0aeace57b1a.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 10/12] unix-stream-server: create unix domain socket under lock","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:35Z","receivedAt":"2021-03-09T15:03:54Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a wrapper class for `unix_stream_listen()` that uses a \".lock\"\nlockfile to create the unix domain socket in a race-free manner.\n\nUnix domain sockets have a fundamental problem on Unix systems because\nthey persist in the filesystem until they are deleted.  This is\nindependent of whether a server is actually listening for connections.\nWell-behaved servers are expected to delete the socket when they\nshutdown.  A new server cannot easily tell if a found socket is\nattached to an active server or is leftover cruft from a dead server.\nThe traditional solution used by `unix_stream_listen()` is to force\ndelete the socket pathname and then create a new socket.  This solves\nthe latter (cruft) problem, but in the case of the former, it orphans\nthe existing server (by stealing the pathname associated with the\nsocket it is listening on).\n\nWe cannot directly use a .lock lockfile to create the socket because\nthe socket is created by `bind(2)` rather than the `open(2)` mechanism\nused by `tempfile.c`.\n\nAs an alternative, we hold a plain lockfile (\"<path>.lock\") as a\nmutual exclusion device.  Under the lock, we test if an existing\nsocket (\"<path>\") is has an active server.  If not, we create a new\nsocket and begin listening.  Then we use \"rollback\" to delete the\nlockfile in all cases.\n\nThis wrapper code conceptually exists at a higher-level than the core\nunix_stream_connect() and unix_stream_listen() routines that it\nconsumes.  It is isolated in a wrapper class for clarity.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   1 +\n contrib/buildsystems/CMakeLists.txt |   2 +-\n unix-stream-server.c                | 128 ++++++++++++++++++++++++++++\n unix-stream-server.h                |  36 ++++++++\n 4 files changed, 166 insertions(+), 1 deletion(-)\n create mode 100644 unix-stream-server.c\n create mode 100644 unix-stream-server.h\n\ndiff --git a/Makefile b/Makefile\nindex d3c42d3f4f9f..012694276f6d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1665,6 +1665,7 @@ ifdef NO_UNIX_SOCKETS\n \tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n else\n \tLIB_OBJS += unix-socket.o\n+\tLIB_OBJS += unix-stream-server.o\n endif\n \n ifdef USE_WIN32_IPC\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 40c9e8e3bd9d..c94011269ebb 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -243,7 +243,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \n elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n \tadd_compile_definitions(PROCFS_EXECUTABLE_PATH=\"/proc/self/exe\" HAVE_DEV_TTY )\n-\tlist(APPEND compat_SOURCES unix-socket.c)\n+\tlist(APPEND compat_SOURCES unix-socket.c unix-stream-server.c)\n endif()\n \n if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\ndiff --git a/unix-stream-server.c b/unix-stream-server.c\nnew file mode 100644\nindex 000000000000..5dfe2a9ac2c0\n--- /dev/null\n+++ b/unix-stream-server.c\n@@ -0,0 +1,128 @@\n+#include \"cache.h\"\n+#include \"lockfile.h\"\n+#include \"unix-socket.h\"\n+#include \"unix-stream-server.h\"\n+\n+#define DEFAULT_LOCK_TIMEOUT (100)\n+\n+/*\n+ * Try to connect to a unix domain socket at `path` (if it exists) and\n+ * see if there is a server listening.\n+ *\n+ * We don't know if the socket exists, whether a server died and\n+ * failed to cleanup, or whether we have a live server listening, so\n+ * we \"poke\" it.\n+ *\n+ * We immediately hangup without sending/receiving any data because we\n+ * don't know anything about the protocol spoken and don't want to\n+ * block while writing/reading data.  It is sufficient to just know\n+ * that someone is listening.\n+ */\n+static int is_another_server_alive(const char *path,\n+\t\t\t\t   const struct unix_stream_listen_opts *opts)\n+{\n+\tint fd = unix_stream_connect(path, opts->disallow_chdir);\n+\tif (fd >= 0) {\n+\t\tclose(fd);\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int unix_stream_server__create(\n+\tconst char *path,\n+\tconst struct unix_stream_listen_opts *opts,\n+\tlong timeout_ms,\n+\tstruct unix_stream_server_socket **new_server_socket)\n+{\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd_socket;\n+\tstruct unix_stream_server_socket *server_socket;\n+\n+\t*new_server_socket = NULL;\n+\n+\tif (timeout_ms < 0)\n+\t\ttimeout_ms = DEFAULT_LOCK_TIMEOUT;\n+\n+\t/*\n+\t * Create a lock at \"<path>.lock\" if we can.\n+\t */\n+\tif (hold_lock_file_for_update_timeout(&lock, path, 0, timeout_ms) < 0)\n+\t\treturn -1;\n+\n+\t/*\n+\t * If another server is listening on \"<path>\" give up.  We do not\n+\t * want to create a socket and steal future connections from them.\n+\t */\n+\tif (is_another_server_alive(path, opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\terrno = EADDRINUSE;\n+\t\treturn -2;\n+\t}\n+\n+\t/*\n+\t * Create and bind to a Unix domain socket at \"<path>\".\n+\t */\n+\tfd_socket = unix_stream_listen(path, opts);\n+\tif (fd_socket < 0) {\n+\t\tint saved_errno = errno;\n+\t\trollback_lock_file(&lock);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tserver_socket = xcalloc(1, sizeof(*server_socket));\n+\tserver_socket->path_socket = strdup(path);\n+\tserver_socket->fd_socket = fd_socket;\n+\tlstat(path, &server_socket->st_socket);\n+\n+\t*new_server_socket = server_socket;\n+\n+\t/*\n+\t * Always rollback (just delete) \"<path>.lock\" because we already created\n+\t * \"<path>\" as a socket and do not want to commit_lock to do the atomic\n+\t * rename trick.\n+\t */\n+\trollback_lock_file(&lock);\n+\n+\treturn 0;\n+}\n+\n+void unix_stream_server__free(\n+\tstruct unix_stream_server_socket *server_socket)\n+{\n+\tif (!server_socket)\n+\t\treturn;\n+\n+\tif (server_socket->fd_socket >= 0) {\n+\t\tif (!unix_stream_server__was_stolen(server_socket))\n+\t\t\tunlink(server_socket->path_socket);\n+\t\tclose(server_socket->fd_socket);\n+\t}\n+\n+\tfree(server_socket->path_socket);\n+\tfree(server_socket);\n+}\n+\n+int unix_stream_server__was_stolen(\n+\tstruct unix_stream_server_socket *server_socket)\n+{\n+\tstruct stat st_now;\n+\n+\tif (!server_socket)\n+\t\treturn 0;\n+\n+\tif (lstat(server_socket->path_socket, &st_now) == -1)\n+\t\treturn 1;\n+\n+\tif (st_now.st_ino != server_socket->st_socket.st_ino)\n+\t\treturn 1;\n+\tif (st_now.st_dev != server_socket->st_socket.st_dev)\n+\t\treturn 1;\n+\n+\tif (!S_ISSOCK(st_now.st_mode))\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\ndiff --git a/unix-stream-server.h b/unix-stream-server.h\nnew file mode 100644\nindex 000000000000..ef9241d0ef70\n--- /dev/null\n+++ b/unix-stream-server.h\n@@ -0,0 +1,36 @@\n+#ifndef UNIX_STREAM_SERVER_H\n+#define UNIX_STREAM_SERVER_H\n+\n+#include \"unix-socket.h\"\n+\n+struct unix_stream_server_socket {\n+\tchar *path_socket;\n+\tstruct stat st_socket;\n+\tint fd_socket;\n+};\n+\n+/*\n+ * Create a Unix Domain Socket at the given path under the protection\n+ * of a '.lock' lockfile.\n+ *\n+ * Returns 0 on success, -1 on error, -2 if socket is in use.\n+ */\n+int unix_stream_server__create(\n+\tconst char *path,\n+\tconst struct unix_stream_listen_opts *opts,\n+\tlong timeout_ms,\n+\tstruct unix_stream_server_socket **server_socket);\n+\n+/*\n+ * Close and delete the socket.\n+ */\n+void unix_stream_server__free(\n+\tstruct unix_stream_server_socket *server_socket);\n+\n+/*\n+ * Return 1 if the inode of the pathname to our socket changes.\n+ */\n+int unix_stream_server__was_stolen(\n+\tstruct unix_stream_server_socket *server_socket);\n+\n+#endif /* UNIX_STREAM_SERVER_H */\n-- \ngitgitgadget\n\n"},{"id":"418604","messageId":"21b8d3c63dbf3d1e5a05274b9693612ac4a14a36.1615302157.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v5 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-09T15:02:34Z","receivedAt":"2021-03-09T15:03:54Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCalls to `chdir()` are dangerous in a multi-threaded context.  If\n`unix_stream_listen()` or `unix_stream_connect()` is given a socket\npathname that is too long to fit in a `sockaddr_un` structure, it will\n`chdir()` to the parent directory of the requested socket pathname,\ncreate the socket using a relative pathname, and then `chdir()` back.\nThis is not thread-safe.\n\nTeach `unix_sockaddr_init()` to not allow calls to `chdir()` when this\nflag is set.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache.c |  2 +-\n unix-socket.c              | 17 ++++++++++++-----\n unix-socket.h              |  3 ++-\n 3 files changed, 15 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/credential-cache.c b/builtin/credential-cache.c\nindex 9b3f70990597..76a6ba37223f 100644\n--- a/builtin/credential-cache.c\n+++ b/builtin/credential-cache.c\n@@ -14,7 +14,7 @@\n static int send_request(const char *socket, const struct strbuf *out)\n {\n \tint got_data = 0;\n-\tint fd = unix_stream_connect(socket);\n+\tint fd = unix_stream_connect(socket, 0);\n \n \tif (fd < 0)\n \t\treturn -1;\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 012becd93d57..e0be1badb58d 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -30,16 +30,23 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n }\n \n static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n-\t\t\t      struct unix_sockaddr_context *ctx)\n+\t\t\t      struct unix_sockaddr_context *ctx,\n+\t\t\t      int disallow_chdir)\n {\n \tint size = strlen(path) + 1;\n \n \tctx->orig_dir = NULL;\n \tif (size > sizeof(sa->sun_path)) {\n-\t\tconst char *slash = find_last_dir_sep(path);\n+\t\tconst char *slash;\n \t\tconst char *dir;\n \t\tstruct strbuf cwd = STRBUF_INIT;\n \n+\t\tif (disallow_chdir) {\n+\t\t\terrno = ENAMETOOLONG;\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tslash = find_last_dir_sep(path);\n \t\tif (!slash) {\n \t\t\terrno = ENAMETOOLONG;\n \t\t\treturn -1;\n@@ -65,13 +72,13 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \treturn 0;\n }\n \n-int unix_stream_connect(const char *path)\n+int unix_stream_connect(const char *path, int disallow_chdir)\n {\n \tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\tif (unix_sockaddr_init(&sa, path, &ctx, disallow_chdir) < 0)\n \t\treturn -1;\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\n@@ -101,7 +108,7 @@ int unix_stream_listen(const char *path,\n \n \tunlink(path);\n \n-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\tif (unix_sockaddr_init(&sa, path, &ctx, opts->disallow_chdir) < 0)\n \t\treturn -1;\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\ndiff --git a/unix-socket.h b/unix-socket.h\nindex ec2fb3ea7267..8542cdd7995d 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -3,11 +3,12 @@\n \n struct unix_stream_listen_opts {\n \tint listen_backlog_size;\n+\tunsigned int disallow_chdir:1;\n };\n \n #define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }\n \n-int unix_stream_connect(const char *path);\n+int unix_stream_connect(const char *path, int disallow_chdir);\n int unix_stream_listen(const char *path,\n \t\t       const struct unix_stream_listen_opts *opts);\n \n-- \ngitgitgadget\n\n"},{"id":"418666","messageId":"xmqqk0qfzz3b.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/12] Simple IPC Mechanism","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-09T23:28:24Z","receivedAt":"2021-03-09T23:35:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> .... I think the combined\n> result is better long term than preserving them as two sequential series.\n\nYup, I think that is a sensible thing to do, too.  Just kick the one\nin 'next' out by reverting them, and queue a cleaned-up series to be\nmerged to 'next' once the upcoming release is out.\n\nThanks.\n"},{"id":"418667","messageId":"xmqqblbrzy5j.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"311ea4a5cd71c5dd2407348ad4608d2f7dd77ce5.1615302157.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-09T23:48:40Z","receivedAt":"2021-03-09T23:49:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +\t/*\n> +\t * Write the header and the buffer in 2 parts so that we do not need\n> +\t * to allocate a buffer or rely on a static buffer.  This avoids perf\n> +\t * and multi-threading issues.\n> +\t */\n\nI understand \"multi-threading issues\" (i.e. let's not have too much\nstuff on the stack), but what issue around \"perf\" are we worried\nabout?\n\nEven though we eliminate memcpy() from the original buffer to our\ntemporary, this doubles the number of write(2) system calls used to\nwrite out packetised data, by the way.  I do not know if this results\nin measurable performance degradation, but hopefully we can fix it\nlocally if it turns out to be a real problem later.\n\n> +\tif (write_in_full(fd_out, header, 4) < 0 ||\n> +\t    write_in_full(fd_out, buf, size) < 0)\n>  \t\treturn error(_(\"packet write failed\"));\n>  \treturn 0;\n>  }\n> @@ -244,20 +252,23 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n>  \n>  int write_packetized_from_fd(int fd_in, int fd_out)\n>  {\n> -\tstatic char buf[LARGE_PACKET_DATA_MAX];\n> +\tchar *buf = xmalloc(LARGE_PACKET_DATA_MAX);\n>  \tint err = 0;\n>  \tssize_t bytes_to_write;\n>  \n>  \twhile (!err) {\n> -\t\tbytes_to_write = xread(fd_in, buf, sizeof(buf));\n> -\t\tif (bytes_to_write < 0)\n> +\t\tbytes_to_write = xread(fd_in, buf, LARGE_PACKET_DATA_MAX);\n> +\t\tif (bytes_to_write < 0) {\n> +\t\t\tfree(buf);\n>  \t\t\treturn COPY_READ_ERROR;\n> +\t\t}\n>  \t\tif (bytes_to_write == 0)\n>  \t\t\tbreak;\n>  \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n>  \t}\n>  \tif (!err)\n>  \t\terr = packet_flush_gently(fd_out);\n> +\tfree(buf);\n>  \treturn err;\n>  }\n"},{"id":"418668","messageId":"xmqq7dmfzx8q.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"f2e3b046cc8f8ad5662f65262810c7414cc1569d.1615302157.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 11/12] simple-ipc: add Unix domain socket implementation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-10T00:08:21Z","receivedAt":"2021-03-10T00:09:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +/*\n> + * This value was chosen at random.\n> + */\n> +#define WAIT_STEP_MS (50)\n\n... and never used.  Is this supposed to be used as the hardcoded\nvalue 50 below ...\n\n> +\n> +/*\n> + * Try to connect to the server.  If the server is just starting up or\n> + * is very busy, we may not get a connection the first time.\n> + */\n> +static enum ipc_active_state connect_to_server(\n> +\tconst char *path,\n> +\tint timeout_ms,\n> +\tconst struct ipc_client_connect_options *options,\n> +\tint *pfd)\n> +{\n> +\tint wait_ms = 50;\n\n... here?\n\n> +\tint k;\n> +\n> +\t*pfd = -1;\n> +\n> +\tfor (k = 0; k < timeout_ms; k += wait_ms) {\n> +\t\tint fd = unix_stream_connect(path, options->uds_disallow_chdir);\n> +\n> +\t\tif (fd != -1) {\n> +\t\t\t*pfd = fd;\n> +\t\t\treturn IPC_STATE__LISTENING;\n> +\t\t}\n> +\n> +\t\tif (errno == ENOENT) {\n> +\t\t\tif (!options->wait_if_not_found)\n> +\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n> +\n> +\t\t\tgoto sleep_and_try_again;\n> +\t\t}\n> + ...\n> +\t\treturn IPC_STATE__OTHER_ERROR;\n> +\n> +\tsleep_and_try_again:\n> +\t\tsleep_millisec(wait_ms);\n\nOr, since there is nothing like exponential back-off implemented\nhere which may want to modify wait_ms variable, perhaps use the\nconstant directly here and where k is incremented?\n\n> +/*\n> + * A randomly chosen timeout value.\n> + */\n> +#define MY_CONNECTION_TIMEOUT_MS (1000)\n\nEven if it may have been \"randomly chosen\", there should be some\ncriteria to judge if the value is sensible, right?  IOW, I have a\nsuspicion that I would regret if I randomly chose 5 (or 3600000)\ninstead of 1000.  How would we figure that 1000 acceptable but not\n5?\n\nPerhaps explain that criterion here, e.g. \"... value that ought to\nbe long enough to establish connection locally as long as the box is\nnot loaded unusably heavily\" or something?\n\n"},{"id":"418669","messageId":"xmqq35x3zwr0.fsf@gitster.c.googlers.com","threadId":"54978","inReplyTo":"1ee9de55a106e46dab6126fe8ca2a0aeace57b1a.1615302157.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 10/12] unix-stream-server: create unix domain socket under lock","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-10T00:18:59Z","receivedAt":"2021-03-10T00:19:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +struct unix_stream_server_socket {\n> +int unix_stream_server__create(\n> +void unix_stream_server__free(\n> +int unix_stream_server__was_stolen(\n\nI think we reserve __ in our API for names of symbols that normal\ncallers never have to write (both data like git_attr__true[] and\nfunctions like cmd_bisect__helper()).\n\nIt seems that list-objects-filter.h may have introduced the\n\"name_space\" followed by \"__\" followed by \"name\" convention,\nbut I am not sure if that is a desirable convention to spread\nthroughout our codebase.\n\nAlso \"unix_stream_server\" is quite a mouthful.  Perhaps abbreviate\nit to uss_ or something?  I dunno if that is too short and invite\nconfusion with other kinds of uss.\n\n\n\n"},{"id":"418900","messageId":"YEpvfztZWhAvSDTL@coredump.intra.peff.net","threadId":"54978","inReplyTo":"xmqqblbrzy5j.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-03-11T19:29:03Z","receivedAt":"2021-03-11T19:30:13Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Mar 09, 2021 at 03:48:40PM -0800, Junio C Hamano wrote:\n\n> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n> > +\t/*\n> > +\t * Write the header and the buffer in 2 parts so that we do not need\n> > +\t * to allocate a buffer or rely on a static buffer.  This avoids perf\n> > +\t * and multi-threading issues.\n> > +\t */\n> \n> I understand \"multi-threading issues\" (i.e. let's not have too much\n> stuff on the stack), but what issue around \"perf\" are we worried\n> about?\n> \n> Even though we eliminate memcpy() from the original buffer to our\n> temporary, this doubles the number of write(2) system calls used to\n> write out packetised data, by the way.  I do not know if this results\n> in measurable performance degradation, but hopefully we can fix it\n> locally if it turns out to be a real problem later.\n\nYeah, this came from my suggestion. My gut feeling is that it isn't\nlikely to matter, but I'd much rather solve any performance problem we\nfind using writev(), which would be pretty easy to emulate with a\nwrapper for systems that lack it.\n\n-Peff\n"},{"id":"418904","messageId":"xmqq4khhctya.fsf@gitster.g","threadId":"54978","inReplyTo":"YEpvfztZWhAvSDTL@coredump.intra.peff.net","subject":"Re: [PATCH v5 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-11T20:32:29Z","receivedAt":"2021-03-11T20:33:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Tue, Mar 09, 2021 at 03:48:40PM -0800, Junio C Hamano wrote:\n>\n>> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> \n>> > +\t/*\n>> > +\t * Write the header and the buffer in 2 parts so that we do not need\n>> > +\t * to allocate a buffer or rely on a static buffer.  This avoids perf\n>> > +\t * and multi-threading issues.\n>> > +\t */\n>> \n>> I understand \"multi-threading issues\" (i.e. let's not have too much\n>> stuff on the stack), but what issue around \"perf\" are we worried\n>> about?\n>>  ...\n> Yeah, this came from my suggestion. My gut feeling is that it isn't\n> likely to matter, but I'd much rather solve any performance problem we\n> find using writev(), which would be pretty easy to emulate with a\n> wrapper for systems that lack it.\n\nI too had writev() in mind when I said \"can fix it locally\", so we\nare on the same page, which is good.\n\nSo \"this avoid multi-threading issues\" without mentioning \"perf and\"\nwould be more appropriate?\n\nThanks.\n"},{"id":"418906","messageId":"YEqDZP/Ea+qf0Qrp@coredump.intra.peff.net","threadId":"54978","inReplyTo":"xmqq4khhctya.fsf@gitster.g","subject":"Re: [PATCH v5 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-03-11T20:53:56Z","receivedAt":"2021-03-11T20:54:33Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Mar 11, 2021 at 12:32:29PM -0800, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > On Tue, Mar 09, 2021 at 03:48:40PM -0800, Junio C Hamano wrote:\n> >\n> >> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >> \n> >> > +\t/*\n> >> > +\t * Write the header and the buffer in 2 parts so that we do not need\n> >> > +\t * to allocate a buffer or rely on a static buffer.  This avoids perf\n> >> > +\t * and multi-threading issues.\n> >> > +\t */\n> >> \n> >> I understand \"multi-threading issues\" (i.e. let's not have too much\n> >> stuff on the stack), but what issue around \"perf\" are we worried\n> >> about?\n> >>  ...\n> > Yeah, this came from my suggestion. My gut feeling is that it isn't\n> > likely to matter, but I'd much rather solve any performance problem we\n> > find using writev(), which would be pretty easy to emulate with a\n> > wrapper for systems that lack it.\n> \n> I too had writev() in mind when I said \"can fix it locally\", so we\n> are on the same page, which is good.\n> \n> So \"this avoid multi-threading issues\" without mentioning \"perf and\"\n> would be more appropriate?\n\nIMHO yes. I think \"avoid perf issues\" is probably answering the \"why not\njust heap-allocate the buffer\" question. But that makes sense in the\ncommit message, not in a comment.\n\n-Peff\n"},{"id":"419182","messageId":"9e6a041c-0581-73ab-0139-8c6d507075b3@jeffhostetler.com","threadId":"54978","inReplyTo":"xmqq7dmfzx8q.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 11/12] simple-ipc: add Unix domain socket implementation","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2021-03-15T19:56:49Z","receivedAt":"2021-03-15T19:57:45Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 3/9/21 7:08 PM, Junio C Hamano wrote:\n> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> +/*\n>> + * This value was chosen at random.\n>> + */\n>> +#define WAIT_STEP_MS (50)\n> \n> ... and never used.  Is this supposed to be used as the hardcoded\n> value 50 below ...\n> \n\nLet me fix that before you move this to \"next\".\nLooks like I just missed that one.\n\n\n>> +\n>> +/*\n>> + * Try to connect to the server.  If the server is just starting up or\n>> + * is very busy, we may not get a connection the first time.\n>> + */\n>> +static enum ipc_active_state connect_to_server(\n>> +\tconst char *path,\n>> +\tint timeout_ms,\n>> +\tconst struct ipc_client_connect_options *options,\n>> +\tint *pfd)\n>> +{\n>> +\tint wait_ms = 50;\n> \n> ... here?\n> \n>> +\tint k;\n>> +\n>> +\t*pfd = -1;\n>> +\n>> +\tfor (k = 0; k < timeout_ms; k += wait_ms) {\n>> +\t\tint fd = unix_stream_connect(path, options->uds_disallow_chdir);\n>> +\n>> +\t\tif (fd != -1) {\n>> +\t\t\t*pfd = fd;\n>> +\t\t\treturn IPC_STATE__LISTENING;\n>> +\t\t}\n>> +\n>> +\t\tif (errno == ENOENT) {\n>> +\t\t\tif (!options->wait_if_not_found)\n>> +\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n>> +\n>> +\t\t\tgoto sleep_and_try_again;\n>> +\t\t}\n>> + ...\n>> +\t\treturn IPC_STATE__OTHER_ERROR;\n>> +\n>> +\tsleep_and_try_again:\n>> +\t\tsleep_millisec(wait_ms);\n> \n> Or, since there is nothing like exponential back-off implemented\n> here which may want to modify wait_ms variable, perhaps use the\n> constant directly here and where k is incremented?\n> \n>> +/*\n>> + * A randomly chosen timeout value.\n>> + */\n>> +#define MY_CONNECTION_TIMEOUT_MS (1000)\n> \n> Even if it may have been \"randomly chosen\", there should be some\n> criteria to judge if the value is sensible, right?  IOW, I have a\n> suspicion that I would regret if I randomly chose 5 (or 3600000)\n> instead of 1000.  How would we figure that 1000 acceptable but not\n> 5?\n> \n> Perhaps explain that criterion here, e.g. \"... value that ought to\n> be long enough to establish connection locally as long as the box is\n> not loaded unusably heavily\" or something?\n> \n\nWill do.  Thanks!\nJeff\n"},{"id":"419184","messageId":"fe35dc3d292d0f13802f5ec646a3d4c5071920f2.1615842509.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:18Z","receivedAt":"2021-03-15T21:09:20Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nTeach `packet_write_gently()` to write the pkt-line header and the actual\nbuffer in 2 separate calls to `write_in_full()` and avoid the need for a\nstatic buffer, thread-safe scratch space, or an excessively large stack\nbuffer.\n\nChange `write_packetized_from_fd()` to allocate a temporary buffer rather\nthan using a static buffer to avoid similar issues here.\n\nThese changes are intended to make it easier to use pkt-line routines in\na multi-threaded context with multiple concurrent writers writing to\ndifferent streams.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n pkt-line.c | 28 ++++++++++++++++++++--------\n 1 file changed, 20 insertions(+), 8 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex d633005ef746..66bd0ddfd1d0 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -196,17 +196,26 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)\n \n static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n {\n-\tstatic char packet_write_buffer[LARGE_PACKET_MAX];\n+\tchar header[4];\n \tsize_t packet_size;\n \n-\tif (size > sizeof(packet_write_buffer) - 4)\n+\tif (size > LARGE_PACKET_DATA_MAX)\n \t\treturn error(_(\"packet write failed - data exceeds max packet size\"));\n \n \tpacket_trace(buf, size, 1);\n \tpacket_size = size + 4;\n-\tset_packet_header(packet_write_buffer, packet_size);\n-\tmemcpy(packet_write_buffer + 4, buf, size);\n-\tif (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)\n+\n+\tset_packet_header(header, packet_size);\n+\n+\t/*\n+\t * Write the header and the buffer in 2 parts so that we do\n+\t * not need to allocate a buffer or rely on a static buffer.\n+\t * This also avoids putting a large buffer on the stack which\n+\t * might have multi-threading issues.\n+\t */\n+\n+\tif (write_in_full(fd_out, header, 4) < 0 ||\n+\t    write_in_full(fd_out, buf, size) < 0)\n \t\treturn error(_(\"packet write failed\"));\n \treturn 0;\n }\n@@ -244,20 +253,23 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \n int write_packetized_from_fd(int fd_in, int fd_out)\n {\n-\tstatic char buf[LARGE_PACKET_DATA_MAX];\n+\tchar *buf = xmalloc(LARGE_PACKET_DATA_MAX);\n \tint err = 0;\n \tssize_t bytes_to_write;\n \n \twhile (!err) {\n-\t\tbytes_to_write = xread(fd_in, buf, sizeof(buf));\n-\t\tif (bytes_to_write < 0)\n+\t\tbytes_to_write = xread(fd_in, buf, LARGE_PACKET_DATA_MAX);\n+\t\tif (bytes_to_write < 0) {\n+\t\t\tfree(buf);\n \t\t\treturn COPY_READ_ERROR;\n+\t\t}\n \t\tif (bytes_to_write == 0)\n \t\t\tbreak;\n \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n \t}\n \tif (!err)\n \t\terr = packet_flush_gently(fd_out);\n+\tfree(buf);\n \treturn err;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"419186","messageId":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v5.git.1615302157.gitgitgadget@gmail.com","subject":"[PATCH v6 00/12] Simple IPC Mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:17Z","receivedAt":"2021-03-15T21:09:21Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"Here is V6 of my \"Simple IPC\" series. This version addresses comments from\nlast week on V5. This includes:\n\n 1. Removing \"perf and\" in pkt-line.c\n 2. Better comments to describe the various timeout #define's.\n 3. Remove the double-underscore and shorten the \"unix_stream_server\"\n    prefix.\n\nThanks Jeff\n\nJeff Hostetler (9):\n  pkt-line: eliminate the need for static buffer in\n    packet_write_gently()\n  simple-ipc: design documentation for new IPC mechanism\n  simple-ipc: add win32 implementation\n  unix-socket: eliminate static unix_stream_socket() helper function\n  unix-socket: add backlog size option to unix_stream_listen()\n  unix-socket: disallow chdir() when creating unix domain sockets\n  unix-stream-server: create unix domain socket under lock\n  simple-ipc: add Unix domain socket implementation\n  t0052: add simple-ipc tests and t/helper/test-simple-ipc tool\n\nJohannes Schindelin (3):\n  pkt-line: do not issue flush packets in write_packetized_*()\n  pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option\n  pkt-line: add options argument to read_packetized_to_strbuf()\n\n Documentation/technical/api-simple-ipc.txt |  105 ++\n Makefile                                   |    9 +\n builtin/credential-cache--daemon.c         |    3 +-\n builtin/credential-cache.c                 |    2 +-\n compat/simple-ipc/ipc-shared.c             |   28 +\n compat/simple-ipc/ipc-unix-socket.c        | 1000 ++++++++++++++++++++\n compat/simple-ipc/ipc-win32.c              |  751 +++++++++++++++\n config.mak.uname                           |    2 +\n contrib/buildsystems/CMakeLists.txt        |    8 +-\n convert.c                                  |   11 +-\n pkt-line.c                                 |   59 +-\n pkt-line.h                                 |   17 +-\n simple-ipc.h                               |  239 +++++\n t/helper/test-simple-ipc.c                 |  787 +++++++++++++++\n t/helper/test-tool.c                       |    1 +\n t/helper/test-tool.h                       |    1 +\n t/t0052-simple-ipc.sh                      |  122 +++\n unix-socket.c                              |   53 +-\n unix-socket.h                              |   12 +-\n unix-stream-server.c                       |  125 +++\n unix-stream-server.h                       |   33 +\n 21 files changed, 3316 insertions(+), 52 deletions(-)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n create mode 100644 unix-stream-server.c\n create mode 100644 unix-stream-server.h\n\n\nbase-commit: f01623b2c9d14207e497b21ebc6b3ec4afaf4b46\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/766\n\nRange-diff vs v5:\n\n  1:  311ea4a5cd71 !  1:  fe35dc3d292d pkt-line: eliminate the need for static buffer in packet_write_gently()\n     @@ pkt-line.c: int packet_write_fmt_gently(int fd, const char *fmt, ...)\n      +\tset_packet_header(header, packet_size);\n      +\n      +\t/*\n     -+\t * Write the header and the buffer in 2 parts so that we do not need\n     -+\t * to allocate a buffer or rely on a static buffer.  This avoids perf\n     -+\t * and multi-threading issues.\n     ++\t * Write the header and the buffer in 2 parts so that we do\n     ++\t * not need to allocate a buffer or rely on a static buffer.\n     ++\t * This also avoids putting a large buffer on the stack which\n     ++\t * might have multi-threading issues.\n      +\t */\n      +\n      +\tif (write_in_full(fd_out, header, 4) < 0 ||\n  2:  25157c1f4873 =  2:  de11b3036148 pkt-line: do not issue flush packets in write_packetized_*()\n  3:  af3d13113bc9 =  3:  3718da39da30 pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option\n  4:  b73e66a69b61 =  4:  b43df7ad0b7a pkt-line: add options argument to read_packetized_to_strbuf()\n  5:  1ae99d824a21 =  5:  f829feb2aa93 simple-ipc: design documentation for new IPC mechanism\n  6:  8b3ce40e4538 =  6:  58c3fb7cd776 simple-ipc: add win32 implementation\n  7:  34df1af98e5b =  7:  4e8c352fb366 unix-socket: eliminate static unix_stream_socket() helper function\n  8:  d6ff6e0e050a =  8:  3b71f52d8628 unix-socket: add backlog size option to unix_stream_listen()\n  9:  21b8d3c63dbf =  9:  5972a198361c unix-socket: disallow chdir() when creating unix domain sockets\n 10:  1ee9de55a106 ! 10:  02c885fd623d unix-stream-server: create unix domain socket under lock\n     @@ unix-stream-server.c (new)\n      +\treturn 0;\n      +}\n      +\n     -+int unix_stream_server__create(\n     -+\tconst char *path,\n     -+\tconst struct unix_stream_listen_opts *opts,\n     -+\tlong timeout_ms,\n     -+\tstruct unix_stream_server_socket **new_server_socket)\n     ++int unix_ss_create(const char *path,\n     ++\t\t   const struct unix_stream_listen_opts *opts,\n     ++\t\t   long timeout_ms,\n     ++\t\t   struct unix_ss_socket **new_server_socket)\n      +{\n      +\tstruct lock_file lock = LOCK_INIT;\n      +\tint fd_socket;\n     -+\tstruct unix_stream_server_socket *server_socket;\n     ++\tstruct unix_ss_socket *server_socket;\n      +\n      +\t*new_server_socket = NULL;\n      +\n     @@ unix-stream-server.c (new)\n      +\treturn 0;\n      +}\n      +\n     -+void unix_stream_server__free(\n     -+\tstruct unix_stream_server_socket *server_socket)\n     ++void unix_ss_free(struct unix_ss_socket *server_socket)\n      +{\n      +\tif (!server_socket)\n      +\t\treturn;\n      +\n      +\tif (server_socket->fd_socket >= 0) {\n     -+\t\tif (!unix_stream_server__was_stolen(server_socket))\n     ++\t\tif (!unix_ss_was_stolen(server_socket))\n      +\t\t\tunlink(server_socket->path_socket);\n      +\t\tclose(server_socket->fd_socket);\n      +\t}\n     @@ unix-stream-server.c (new)\n      +\tfree(server_socket);\n      +}\n      +\n     -+int unix_stream_server__was_stolen(\n     -+\tstruct unix_stream_server_socket *server_socket)\n     ++int unix_ss_was_stolen(struct unix_ss_socket *server_socket)\n      +{\n      +\tstruct stat st_now;\n      +\n     @@ unix-stream-server.h (new)\n      +\n      +#include \"unix-socket.h\"\n      +\n     -+struct unix_stream_server_socket {\n     ++struct unix_ss_socket {\n      +\tchar *path_socket;\n      +\tstruct stat st_socket;\n      +\tint fd_socket;\n     @@ unix-stream-server.h (new)\n      + *\n      + * Returns 0 on success, -1 on error, -2 if socket is in use.\n      + */\n     -+int unix_stream_server__create(\n     -+\tconst char *path,\n     -+\tconst struct unix_stream_listen_opts *opts,\n     -+\tlong timeout_ms,\n     -+\tstruct unix_stream_server_socket **server_socket);\n     ++int unix_ss_create(const char *path,\n     ++\t\t   const struct unix_stream_listen_opts *opts,\n     ++\t\t   long timeout_ms,\n     ++\t\t   struct unix_ss_socket **server_socket);\n      +\n      +/*\n      + * Close and delete the socket.\n      + */\n     -+void unix_stream_server__free(\n     -+\tstruct unix_stream_server_socket *server_socket);\n     ++void unix_ss_free(struct unix_ss_socket *server_socket);\n      +\n      +/*\n      + * Return 1 if the inode of the pathname to our socket changes.\n      + */\n     -+int unix_stream_server__was_stolen(\n     -+\tstruct unix_stream_server_socket *server_socket);\n     ++int unix_ss_was_stolen(struct unix_ss_socket *server_socket);\n      +\n      +#endif /* UNIX_STREAM_SERVER_H */\n 11:  f2e3b046cc8f ! 11:  4c2199231d05 simple-ipc: add Unix domain socket implementation\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +}\n      +\n      +/*\n     -+ * This value was chosen at random.\n     ++ * Retry frequency when trying to connect to a server.\n     ++ *\n     ++ * This value should be short enough that we don't seriously delay our\n     ++ * caller, but not fast enough that our spinning puts pressure on the\n     ++ * system.\n      + */\n      +#define WAIT_STEP_MS (50)\n      +\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\tconst struct ipc_client_connect_options *options,\n      +\tint *pfd)\n      +{\n     -+\tint wait_ms = 50;\n      +\tint k;\n      +\n      +\t*pfd = -1;\n      +\n     -+\tfor (k = 0; k < timeout_ms; k += wait_ms) {\n     ++\tfor (k = 0; k < timeout_ms; k += WAIT_STEP_MS) {\n      +\t\tint fd = unix_stream_connect(path, options->uds_disallow_chdir);\n      +\n      +\t\tif (fd != -1) {\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\treturn IPC_STATE__OTHER_ERROR;\n      +\n      +\tsleep_and_try_again:\n     -+\t\tsleep_millisec(wait_ms);\n     ++\t\tsleep_millisec(WAIT_STEP_MS);\n      +\t}\n      +\n      +\treturn IPC_STATE__NOT_LISTENING;\n      +}\n      +\n      +/*\n     -+ * A randomly chosen timeout value.\n     ++ * The total amount of time that we are willing to wait when trying to\n     ++ * connect to a server.\n     ++ *\n     ++ * When the server is first started, it might take a little while for\n     ++ * it to become ready to service requests.  Likewise, the server may\n     ++ * be very (temporarily) busy and not respond to our connections.\n     ++ *\n     ++ * We should gracefully and silently handle those conditions and try\n     ++ * again for a reasonable time period.\n     ++ *\n     ++ * The value chosen here should be long enough for the server\n     ++ * to reliably heal from the above conditions.\n      + */\n      +#define MY_CONNECTION_TIMEOUT_MS (1000)\n      +\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\tenum magic magic;\n      +\tstruct ipc_server_data *server_data;\n      +\n     -+\tstruct unix_stream_server_socket *server_socket;\n     ++\tstruct unix_ss_socket *server_socket;\n      +\n      +\tint fd_send_shutdown;\n      +\tint fd_wait_shutdown;\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\t\t * will be routed elsewhere and we silently starve.\n      +\t\t\t * If that happens, just queue a shutdown.\n      +\t\t\t */\n     -+\t\t\tif (unix_stream_server__was_stolen(\n     ++\t\t\tif (unix_ss_was_stolen(\n      +\t\t\t\t    accept_thread_data->server_socket)) {\n      +\t\t\t\ttrace2_data_string(\"ipc-accept\", NULL,\n      +\t\t\t\t\t\t   \"queue_stop_async\",\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +static int create_listener_socket(\n      +\tconst char *path,\n      +\tconst struct ipc_server_opts *ipc_opts,\n     -+\tstruct unix_stream_server_socket **new_server_socket)\n     ++\tstruct unix_ss_socket **new_server_socket)\n      +{\n     -+\tstruct unix_stream_server_socket *server_socket = NULL;\n     ++\tstruct unix_ss_socket *server_socket = NULL;\n      +\tstruct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n      +\tint ret;\n      +\n      +\tuslg_opts.listen_backlog_size = LISTEN_BACKLOG;\n      +\tuslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;\n      +\n     -+\tret = unix_stream_server__create(path, &uslg_opts, -1, &server_socket);\n     ++\tret = unix_ss_create(path, &uslg_opts, -1, &server_socket);\n      +\tif (ret)\n      +\t\treturn ret;\n      +\n      +\tif (set_socket_blocking_flag(server_socket->fd_socket, 1)) {\n      +\t\tint saved_errno = errno;\n     -+\t\tunix_stream_server__free(server_socket);\n     ++\t\tunix_ss_free(server_socket);\n      +\t\terrno = saved_errno;\n      +\t\treturn -1;\n      +\t}\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +static int setup_listener_socket(\n      +\tconst char *path,\n      +\tconst struct ipc_server_opts *ipc_opts,\n     -+\tstruct unix_stream_server_socket **new_server_socket)\n     ++\tstruct unix_ss_socket **new_server_socket)\n      +{\n      +\tint ret, saved_errno;\n      +\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\t\t\t ipc_server_application_cb *application_cb,\n      +\t\t\t void *application_data)\n      +{\n     -+\tstruct unix_stream_server_socket *server_socket = NULL;\n     ++\tstruct unix_ss_socket *server_socket = NULL;\n      +\tstruct ipc_server_data *server_data;\n      +\tint sv[2];\n      +\tint k;\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\n      +\taccept_thread_data = server_data->accept_thread;\n      +\tif (accept_thread_data) {\n     -+\t\tunix_stream_server__free(accept_thread_data->server_socket);\n     ++\t\tunix_ss_free(accept_thread_data->server_socket);\n      +\n      +\t\tif (accept_thread_data->fd_send_shutdown != -1)\n      +\t\t\tclose(accept_thread_data->fd_send_shutdown);\n 12:  6ccc7472096f = 12:  132b6f3271be t0052: add simple-ipc tests and t/helper/test-simple-ipc tool\n\n-- \ngitgitgadget\n"},{"id":"419185","messageId":"3718da39da30ffc283e74eb94c942d0110eb9676.1615842509.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 03/12] pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:20Z","receivedAt":"2021-03-15T21:09:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIntroduce PACKET_READ_GENTLE_ON_READ_ERROR option to help libify the\npacket readers.\n\nSo far, the (possibly indirect) callers of `get_packet_data()` can ask\nthat function to return an error instead of `die()`ing upon end-of-file.\nHowever, random read errors will still cause the process to die.\n\nSo let's introduce an explicit option to tell the packet reader\nmachinery to please be nice and only return an error on read errors.\n\nThis change prepares pkt-line for use by long-running daemon processes.\nSuch processes should be able to serve multiple concurrent clients and\nand survive random IO errors.  If there is an error on one connection,\na daemon should be able to drop that connection and continue serving\nexisting and future connections.\n\nThis ability will be used by a Git-aware \"Builtin FSMonitor\" feature\nin a later patch series.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n pkt-line.c | 19 +++++++++++++++++--\n pkt-line.h | 11 ++++++++---\n 2 files changed, 25 insertions(+), 5 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex bb0fb0c3802c..457ac4e151bb 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -306,8 +306,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\t*src_size -= ret;\n \t} else {\n \t\tret = read_in_full(fd, dst, size);\n-\t\tif (ret < 0)\n+\t\tif (ret < 0) {\n+\t\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\t\treturn error_errno(_(\"read error\"));\n \t\t\tdie_errno(_(\"read error\"));\n+\t\t}\n \t}\n \n \t/* And complain if we didn't get enough bytes to satisfy the read. */\n@@ -315,6 +318,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n \t\t\treturn -1;\n \n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"the remote end hung up unexpectedly\"));\n \t\tdie(_(\"the remote end hung up unexpectedly\"));\n \t}\n \n@@ -343,6 +348,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \tlen = packet_length(linelen);\n \n \tif (len < 0) {\n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"protocol error: bad line length \"\n+\t\t\t\t       \"character: %.4s\"), linelen);\n \t\tdie(_(\"protocol error: bad line length character: %.4s\"), linelen);\n \t} else if (!len) {\n \t\tpacket_trace(\"0000\", 4, 0);\n@@ -357,12 +365,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \t\t*pktlen = 0;\n \t\treturn PACKET_READ_RESPONSE_END;\n \t} else if (len < 4) {\n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n \t}\n \n \tlen -= 4;\n-\tif ((unsigned)len >= size)\n+\tif ((unsigned)len >= size) {\n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n+\t}\n \n \tif (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) {\n \t\t*pktlen = -1;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 31012b9943bf..80ce0187e2ea 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -68,10 +68,15 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou\n  *\n  * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n  * ERR packet.\n+ *\n+ * If options contains PACKET_READ_GENTLE_ON_READ_ERROR, we will not die\n+ * on read errors, but instead return -1.  However, we may still die on an\n+ * ERR packet (if requested).\n  */\n-#define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n-#define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n-#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n+#define PACKET_READ_GENTLE_ON_EOF        (1u<<0)\n+#define PACKET_READ_CHOMP_NEWLINE        (1u<<1)\n+#define PACKET_READ_DIE_ON_ERR_PACKET    (1u<<2)\n+#define PACKET_READ_GENTLE_ON_READ_ERROR (1u<<3)\n int packet_read(int fd, char **src_buffer, size_t *src_len, char\n \t\t*buffer, unsigned size, int options);\n \n-- \ngitgitgadget\n\n"},{"id":"419187","messageId":"de11b3036148104308b22a1af39fbdaa5f54b296.1615842509.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 02/12] pkt-line: do not issue flush packets in write_packetized_*()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:19Z","receivedAt":"2021-03-15T21:09:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nRemove the `packet_flush_gently()` call in `write_packetized_from_buf() and\n`write_packetized_from_fd()` and require the caller to call it if desired.\nRename both functions to `write_packetized_from_*_no_flush()` to prevent\nlater merge accidents.\n\n`write_packetized_from_buf()` currently only has one caller:\n`apply_multi_file_filter()` in `convert.c`.  It always wants a flush packet\nto be written after writing the payload.\n\nHowever, we are about to introduce a caller that wants to write many\npackets before a final flush packet, so let's make the caller responsible\nfor emitting the flush packet.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n convert.c  | 8 ++++++--\n pkt-line.c | 8 ++------\n pkt-line.h | 4 ++--\n 3 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07ce..976d4905cb3a 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -884,9 +884,13 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \t\tgoto done;\n \n \tif (fd >= 0)\n-\t\terr = write_packetized_from_fd(fd, process->in);\n+\t\terr = write_packetized_from_fd_no_flush(fd, process->in);\n \telse\n-\t\terr = write_packetized_from_buf(src, len, process->in);\n+\t\terr = write_packetized_from_buf_no_flush(src, len, process->in);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = packet_flush_gently(process->in);\n \tif (err)\n \t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 66bd0ddfd1d0..bb0fb0c3802c 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -251,7 +251,7 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \tpacket_trace(data, len, 1);\n }\n \n-int write_packetized_from_fd(int fd_in, int fd_out)\n+int write_packetized_from_fd_no_flush(int fd_in, int fd_out)\n {\n \tchar *buf = xmalloc(LARGE_PACKET_DATA_MAX);\n \tint err = 0;\n@@ -267,13 +267,11 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n \t\t\tbreak;\n \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \tfree(buf);\n \treturn err;\n }\n \n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out)\n {\n \tint err = 0;\n \tsize_t bytes_written = 0;\n@@ -289,8 +287,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n \t\tbytes_written += bytes_to_write;\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\n \ndiff --git a/pkt-line.h b/pkt-line.h\nindex 8c90daa59ef0..31012b9943bf 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -32,8 +32,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f\n void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n int packet_flush_gently(int fd);\n int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n-int write_packetized_from_fd(int fd_in, int fd_out);\n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n+int write_packetized_from_fd_no_flush(int fd_in, int fd_out);\n+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);\n \n /*\n  * Read a packetized line into the buffer, which must be at least size bytes\n-- \ngitgitgadget\n\n"},{"id":"419188","messageId":"f829feb2aa93937b2e2fc493e8ea647051960658.1615842510.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 05/12] simple-ipc: design documentation for new IPC mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:22Z","receivedAt":"2021-03-15T21:09:22Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nBrief design documentation for new IPC mechanism allowing\nforeground Git client to talk with an existing daemon process\nat a known location using a named pipe or unix domain socket.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-simple-ipc.txt | 105 +++++++++++++++++++++\n 1 file changed, 105 insertions(+)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n\ndiff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt\nnew file mode 100644\nindex 000000000000..d79ad323e675\n--- /dev/null\n+++ b/Documentation/technical/api-simple-ipc.txt\n@@ -0,0 +1,105 @@\n+Simple-IPC API\n+==============\n+\n+The Simple-IPC API is a collection of `ipc_` prefixed library routines\n+and a basic communication protocol that allow an IPC-client process to\n+send an application-specific IPC-request message to an IPC-server\n+process and receive an application-specific IPC-response message.\n+\n+Communication occurs over a named pipe on Windows and a Unix domain\n+socket on other platforms.  IPC-clients and IPC-servers rendezvous at\n+a previously agreed-to application-specific pathname (which is outside\n+the scope of this design) that is local to the computer system.\n+\n+The IPC-server routines within the server application process create a\n+thread pool to listen for connections and receive request messages\n+from multiple concurrent IPC-clients.  When received, these messages\n+are dispatched up to the server application callbacks for handling.\n+IPC-server routines then incrementally relay responses back to the\n+IPC-client.\n+\n+The IPC-client routines within a client application process connect\n+to the IPC-server and send a request message and wait for a response.\n+When received, the response is returned back the caller.\n+\n+For example, the `fsmonitor--daemon` feature will be built as a server\n+application on top of the IPC-server library routines.  It will have\n+threads watching for file system events and a thread pool waiting for\n+client connections.  Clients, such as `git status` will request a list\n+of file system events since a point in time and the server will\n+respond with a list of changed files and directories.  The formats of\n+the request and response are application-specific; the IPC-client and\n+IPC-server routines treat them as opaque byte streams.\n+\n+\n+Comparison with sub-process model\n+---------------------------------\n+\n+The Simple-IPC mechanism differs from the existing `sub-process.c`\n+model (Documentation/technical/long-running-process-protocol.txt) and\n+used by applications like Git-LFS.  In the LFS-style sub-process model\n+the helper is started by the foreground process, communication happens\n+via a pair of file descriptors bound to the stdin/stdout of the\n+sub-process, the sub-process only serves the current foreground\n+process, and the sub-process exits when the foreground process\n+terminates.\n+\n+In the Simple-IPC model the server is a very long-running service.  It\n+can service many clients at the same time and has a private socket or\n+named pipe connection to each active client.  It might be started\n+(on-demand) by the current client process or it might have been\n+started by a previous client or by the OS at boot time.  The server\n+process is not associated with a terminal and it persists after\n+clients terminate.  Clients do not have access to the stdin/stdout of\n+the server process and therefore must communicate over sockets or\n+named pipes.\n+\n+\n+Server startup and shutdown\n+---------------------------\n+\n+How an application server based upon IPC-server is started is also\n+outside the scope of the Simple-IPC design and is a property of the\n+application using it.  For example, the server might be started or\n+restarted during routine maintenance operations, or it might be\n+started as a system service during the system boot-up sequence, or it\n+might be started on-demand by a foreground Git command when needed.\n+\n+Similarly, server shutdown is a property of the application using\n+the simple-ipc routines.  For example, the server might decide to\n+shutdown when idle or only upon explicit request.\n+\n+\n+Simple-IPC protocol\n+-------------------\n+\n+The Simple-IPC protocol consists of a single request message from the\n+client and an optional response message from the server.  Both the\n+client and server messages are unlimited in length and are terminated\n+with a flush packet.\n+\n+The pkt-line routines (Documentation/technical/protocol-common.txt)\n+are used to simplify buffer management during message generation,\n+transmission, and reception.  A flush packet is used to mark the end\n+of the message.  This allows the sender to incrementally generate and\n+transmit the message.  It allows the receiver to incrementally receive\n+the message in chunks and to know when they have received the entire\n+message.\n+\n+The actual byte format of the client request and server response\n+messages are application specific.  The IPC layer transmits and\n+receives them as opaque byte buffers without any concern for the\n+content within.  It is the job of the calling application layer to\n+understand the contents of the request and response messages.\n+\n+\n+Summary\n+-------\n+\n+Conceptually, the Simple-IPC protocol is similar to an HTTP REST\n+request.  Clients connect, make an application-specific and\n+stateless request, receive an application-specific\n+response, and disconnect.  It is a one round trip facility for\n+querying the server.  The Simple-IPC routines hide the socket,\n+named pipe, and thread pool details and allow the application\n+layer to focus on the application at hand.\n-- \ngitgitgadget\n\n"},{"id":"419190","messageId":"3b71f52d862832f3eb65d4041baac4c3a9f3e153.1615842510.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 08/12] unix-socket: add backlog size option to unix_stream_listen()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:25Z","receivedAt":"2021-03-15T21:09:22Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nUpdate `unix_stream_listen()` to take an options structure to override\ndefault behaviors.  This commit includes the size of the `listen()` backlog.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache--daemon.c |  3 ++-\n unix-socket.c                      | 11 +++++++++--\n unix-socket.h                      |  9 ++++++++-\n 3 files changed, 19 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c\nindex c61f123a3b81..4c6c89ab0de2 100644\n--- a/builtin/credential-cache--daemon.c\n+++ b/builtin/credential-cache--daemon.c\n@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)\n \n static void serve_cache(const char *socket_path, int debug)\n {\n+\tstruct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n \tint fd;\n \n-\tfd = unix_stream_listen(socket_path);\n+\tfd = unix_stream_listen(socket_path, &opts);\n \tif (fd < 0)\n \t\tdie_errno(\"unable to bind to '%s'\", socket_path);\n \ndiff --git a/unix-socket.c b/unix-socket.c\nindex 69f81d64e9d5..012becd93d57 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,6 +1,8 @@\n #include \"cache.h\"\n #include \"unix-socket.h\"\n \n+#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n+\n static int chdir_len(const char *orig, int len)\n {\n \tchar *path = xmemdupz(orig, len);\n@@ -89,9 +91,11 @@ int unix_stream_connect(const char *path)\n \treturn -1;\n }\n \n-int unix_stream_listen(const char *path)\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts)\n {\n \tint fd = -1, saved_errno;\n+\tint backlog;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -106,7 +110,10 @@ int unix_stream_listen(const char *path)\n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \n-\tif (listen(fd, 5) < 0)\n+\tbacklog = opts->listen_backlog_size;\n+\tif (backlog <= 0)\n+\t\tbacklog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;\n+\tif (listen(fd, backlog) < 0)\n \t\tgoto fail;\n \n \tunix_sockaddr_cleanup(&ctx);\ndiff --git a/unix-socket.h b/unix-socket.h\nindex e271aeec5a07..ec2fb3ea7267 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -1,7 +1,14 @@\n #ifndef UNIX_SOCKET_H\n #define UNIX_SOCKET_H\n \n+struct unix_stream_listen_opts {\n+\tint listen_backlog_size;\n+};\n+\n+#define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }\n+\n int unix_stream_connect(const char *path);\n-int unix_stream_listen(const char *path);\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts);\n \n #endif /* UNIX_SOCKET_H */\n-- \ngitgitgadget\n\n"},{"id":"419192","messageId":"b43df7ad0b7a8afb686baf166a118432305154ba.1615842510.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 04/12] pkt-line: add options argument to read_packetized_to_strbuf()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:21Z","receivedAt":"2021-03-15T21:09:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nUpdate the calling sequence of `read_packetized_to_strbuf()` to take\nan options argument and not assume a fixed set of options.  Update the\nonly existing caller accordingly to explicitly pass the\nformerly-assumed flags.\n\nThe `read_packetized_to_strbuf()` function calls `packet_read()` with\na fixed set of assumed options (`PACKET_READ_GENTLE_ON_EOF`).  This\nassumption has been fine for the single existing caller\n`apply_multi_file_filter()` in `convert.c`.\n\nIn a later commit we would like to add other callers to\n`read_packetized_to_strbuf()` that need a different set of options.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n convert.c  | 3 ++-\n pkt-line.c | 4 ++--\n pkt-line.h | 2 +-\n 3 files changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 976d4905cb3a..516f1095b06e 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -907,7 +907,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \t\tif (err)\n \t\t\tgoto done;\n \n-\t\terr = read_packetized_to_strbuf(process->out, &nbuf) < 0;\n+\t\terr = read_packetized_to_strbuf(process->out, &nbuf,\n+\t\t\t\t\t\tPACKET_READ_GENTLE_ON_EOF) < 0;\n \t\tif (err)\n \t\t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 457ac4e151bb..0194137528c3 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -444,7 +444,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)\n \treturn packet_read_line_generic(-1, src, src_len, dst_len);\n }\n \n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options)\n {\n \tint packet_len;\n \n@@ -460,7 +460,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n \t\t\t * that there is already room for the extra byte.\n \t\t\t */\n \t\t\tsb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,\n-\t\t\tPACKET_READ_GENTLE_ON_EOF);\n+\t\t\toptions);\n \t\tif (packet_len <= 0)\n \t\t\tbreak;\n \t\tsb_out->len += packet_len;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 80ce0187e2ea..5af5f4568768 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -136,7 +136,7 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);\n /*\n  * Reads a stream of variable sized packets until a flush packet is detected.\n  */\n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options);\n \n /*\n  * Receive multiplexed output stream over git native protocol.\n-- \ngitgitgadget\n\n"},{"id":"419189","messageId":"02c885fd623df3551c46aa270c23f87e7ef79af2.1615842510.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 10/12] unix-stream-server: create unix domain socket under lock","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:27Z","receivedAt":"2021-03-15T21:09:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a wrapper class for `unix_stream_listen()` that uses a \".lock\"\nlockfile to create the unix domain socket in a race-free manner.\n\nUnix domain sockets have a fundamental problem on Unix systems because\nthey persist in the filesystem until they are deleted.  This is\nindependent of whether a server is actually listening for connections.\nWell-behaved servers are expected to delete the socket when they\nshutdown.  A new server cannot easily tell if a found socket is\nattached to an active server or is leftover cruft from a dead server.\nThe traditional solution used by `unix_stream_listen()` is to force\ndelete the socket pathname and then create a new socket.  This solves\nthe latter (cruft) problem, but in the case of the former, it orphans\nthe existing server (by stealing the pathname associated with the\nsocket it is listening on).\n\nWe cannot directly use a .lock lockfile to create the socket because\nthe socket is created by `bind(2)` rather than the `open(2)` mechanism\nused by `tempfile.c`.\n\nAs an alternative, we hold a plain lockfile (\"<path>.lock\") as a\nmutual exclusion device.  Under the lock, we test if an existing\nsocket (\"<path>\") is has an active server.  If not, we create a new\nsocket and begin listening.  Then we use \"rollback\" to delete the\nlockfile in all cases.\n\nThis wrapper code conceptually exists at a higher-level than the core\nunix_stream_connect() and unix_stream_listen() routines that it\nconsumes.  It is isolated in a wrapper class for clarity.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   1 +\n contrib/buildsystems/CMakeLists.txt |   2 +-\n unix-stream-server.c                | 125 ++++++++++++++++++++++++++++\n unix-stream-server.h                |  33 ++++++++\n 4 files changed, 160 insertions(+), 1 deletion(-)\n create mode 100644 unix-stream-server.c\n create mode 100644 unix-stream-server.h\n\ndiff --git a/Makefile b/Makefile\nindex d3c42d3f4f9f..012694276f6d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1665,6 +1665,7 @@ ifdef NO_UNIX_SOCKETS\n \tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n else\n \tLIB_OBJS += unix-socket.o\n+\tLIB_OBJS += unix-stream-server.o\n endif\n \n ifdef USE_WIN32_IPC\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 40c9e8e3bd9d..c94011269ebb 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -243,7 +243,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \n elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n \tadd_compile_definitions(PROCFS_EXECUTABLE_PATH=\"/proc/self/exe\" HAVE_DEV_TTY )\n-\tlist(APPEND compat_SOURCES unix-socket.c)\n+\tlist(APPEND compat_SOURCES unix-socket.c unix-stream-server.c)\n endif()\n \n if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\ndiff --git a/unix-stream-server.c b/unix-stream-server.c\nnew file mode 100644\nindex 000000000000..efa2a207abcd\n--- /dev/null\n+++ b/unix-stream-server.c\n@@ -0,0 +1,125 @@\n+#include \"cache.h\"\n+#include \"lockfile.h\"\n+#include \"unix-socket.h\"\n+#include \"unix-stream-server.h\"\n+\n+#define DEFAULT_LOCK_TIMEOUT (100)\n+\n+/*\n+ * Try to connect to a unix domain socket at `path` (if it exists) and\n+ * see if there is a server listening.\n+ *\n+ * We don't know if the socket exists, whether a server died and\n+ * failed to cleanup, or whether we have a live server listening, so\n+ * we \"poke\" it.\n+ *\n+ * We immediately hangup without sending/receiving any data because we\n+ * don't know anything about the protocol spoken and don't want to\n+ * block while writing/reading data.  It is sufficient to just know\n+ * that someone is listening.\n+ */\n+static int is_another_server_alive(const char *path,\n+\t\t\t\t   const struct unix_stream_listen_opts *opts)\n+{\n+\tint fd = unix_stream_connect(path, opts->disallow_chdir);\n+\tif (fd >= 0) {\n+\t\tclose(fd);\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int unix_ss_create(const char *path,\n+\t\t   const struct unix_stream_listen_opts *opts,\n+\t\t   long timeout_ms,\n+\t\t   struct unix_ss_socket **new_server_socket)\n+{\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd_socket;\n+\tstruct unix_ss_socket *server_socket;\n+\n+\t*new_server_socket = NULL;\n+\n+\tif (timeout_ms < 0)\n+\t\ttimeout_ms = DEFAULT_LOCK_TIMEOUT;\n+\n+\t/*\n+\t * Create a lock at \"<path>.lock\" if we can.\n+\t */\n+\tif (hold_lock_file_for_update_timeout(&lock, path, 0, timeout_ms) < 0)\n+\t\treturn -1;\n+\n+\t/*\n+\t * If another server is listening on \"<path>\" give up.  We do not\n+\t * want to create a socket and steal future connections from them.\n+\t */\n+\tif (is_another_server_alive(path, opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\terrno = EADDRINUSE;\n+\t\treturn -2;\n+\t}\n+\n+\t/*\n+\t * Create and bind to a Unix domain socket at \"<path>\".\n+\t */\n+\tfd_socket = unix_stream_listen(path, opts);\n+\tif (fd_socket < 0) {\n+\t\tint saved_errno = errno;\n+\t\trollback_lock_file(&lock);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tserver_socket = xcalloc(1, sizeof(*server_socket));\n+\tserver_socket->path_socket = strdup(path);\n+\tserver_socket->fd_socket = fd_socket;\n+\tlstat(path, &server_socket->st_socket);\n+\n+\t*new_server_socket = server_socket;\n+\n+\t/*\n+\t * Always rollback (just delete) \"<path>.lock\" because we already created\n+\t * \"<path>\" as a socket and do not want to commit_lock to do the atomic\n+\t * rename trick.\n+\t */\n+\trollback_lock_file(&lock);\n+\n+\treturn 0;\n+}\n+\n+void unix_ss_free(struct unix_ss_socket *server_socket)\n+{\n+\tif (!server_socket)\n+\t\treturn;\n+\n+\tif (server_socket->fd_socket >= 0) {\n+\t\tif (!unix_ss_was_stolen(server_socket))\n+\t\t\tunlink(server_socket->path_socket);\n+\t\tclose(server_socket->fd_socket);\n+\t}\n+\n+\tfree(server_socket->path_socket);\n+\tfree(server_socket);\n+}\n+\n+int unix_ss_was_stolen(struct unix_ss_socket *server_socket)\n+{\n+\tstruct stat st_now;\n+\n+\tif (!server_socket)\n+\t\treturn 0;\n+\n+\tif (lstat(server_socket->path_socket, &st_now) == -1)\n+\t\treturn 1;\n+\n+\tif (st_now.st_ino != server_socket->st_socket.st_ino)\n+\t\treturn 1;\n+\tif (st_now.st_dev != server_socket->st_socket.st_dev)\n+\t\treturn 1;\n+\n+\tif (!S_ISSOCK(st_now.st_mode))\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\ndiff --git a/unix-stream-server.h b/unix-stream-server.h\nnew file mode 100644\nindex 000000000000..ae2712ba39b1\n--- /dev/null\n+++ b/unix-stream-server.h\n@@ -0,0 +1,33 @@\n+#ifndef UNIX_STREAM_SERVER_H\n+#define UNIX_STREAM_SERVER_H\n+\n+#include \"unix-socket.h\"\n+\n+struct unix_ss_socket {\n+\tchar *path_socket;\n+\tstruct stat st_socket;\n+\tint fd_socket;\n+};\n+\n+/*\n+ * Create a Unix Domain Socket at the given path under the protection\n+ * of a '.lock' lockfile.\n+ *\n+ * Returns 0 on success, -1 on error, -2 if socket is in use.\n+ */\n+int unix_ss_create(const char *path,\n+\t\t   const struct unix_stream_listen_opts *opts,\n+\t\t   long timeout_ms,\n+\t\t   struct unix_ss_socket **server_socket);\n+\n+/*\n+ * Close and delete the socket.\n+ */\n+void unix_ss_free(struct unix_ss_socket *server_socket);\n+\n+/*\n+ * Return 1 if the inode of the pathname to our socket changes.\n+ */\n+int unix_ss_was_stolen(struct unix_ss_socket *server_socket);\n+\n+#endif /* UNIX_STREAM_SERVER_H */\n-- \ngitgitgadget\n\n"},{"id":"419191","messageId":"5972a198361c153e000a9d11c05bec480cb9f4ce.1615842510.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:26Z","receivedAt":"2021-03-15T21:09:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCalls to `chdir()` are dangerous in a multi-threaded context.  If\n`unix_stream_listen()` or `unix_stream_connect()` is given a socket\npathname that is too long to fit in a `sockaddr_un` structure, it will\n`chdir()` to the parent directory of the requested socket pathname,\ncreate the socket using a relative pathname, and then `chdir()` back.\nThis is not thread-safe.\n\nTeach `unix_sockaddr_init()` to not allow calls to `chdir()` when this\nflag is set.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache.c |  2 +-\n unix-socket.c              | 17 ++++++++++++-----\n unix-socket.h              |  3 ++-\n 3 files changed, 15 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/credential-cache.c b/builtin/credential-cache.c\nindex 9b3f70990597..76a6ba37223f 100644\n--- a/builtin/credential-cache.c\n+++ b/builtin/credential-cache.c\n@@ -14,7 +14,7 @@\n static int send_request(const char *socket, const struct strbuf *out)\n {\n \tint got_data = 0;\n-\tint fd = unix_stream_connect(socket);\n+\tint fd = unix_stream_connect(socket, 0);\n \n \tif (fd < 0)\n \t\treturn -1;\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 012becd93d57..e0be1badb58d 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -30,16 +30,23 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n }\n \n static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n-\t\t\t      struct unix_sockaddr_context *ctx)\n+\t\t\t      struct unix_sockaddr_context *ctx,\n+\t\t\t      int disallow_chdir)\n {\n \tint size = strlen(path) + 1;\n \n \tctx->orig_dir = NULL;\n \tif (size > sizeof(sa->sun_path)) {\n-\t\tconst char *slash = find_last_dir_sep(path);\n+\t\tconst char *slash;\n \t\tconst char *dir;\n \t\tstruct strbuf cwd = STRBUF_INIT;\n \n+\t\tif (disallow_chdir) {\n+\t\t\terrno = ENAMETOOLONG;\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tslash = find_last_dir_sep(path);\n \t\tif (!slash) {\n \t\t\terrno = ENAMETOOLONG;\n \t\t\treturn -1;\n@@ -65,13 +72,13 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \treturn 0;\n }\n \n-int unix_stream_connect(const char *path)\n+int unix_stream_connect(const char *path, int disallow_chdir)\n {\n \tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\tif (unix_sockaddr_init(&sa, path, &ctx, disallow_chdir) < 0)\n \t\treturn -1;\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\n@@ -101,7 +108,7 @@ int unix_stream_listen(const char *path,\n \n \tunlink(path);\n \n-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\tif (unix_sockaddr_init(&sa, path, &ctx, opts->disallow_chdir) < 0)\n \t\treturn -1;\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\ndiff --git a/unix-socket.h b/unix-socket.h\nindex ec2fb3ea7267..8542cdd7995d 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -3,11 +3,12 @@\n \n struct unix_stream_listen_opts {\n \tint listen_backlog_size;\n+\tunsigned int disallow_chdir:1;\n };\n \n #define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }\n \n-int unix_stream_connect(const char *path);\n+int unix_stream_connect(const char *path, int disallow_chdir);\n int unix_stream_listen(const char *path,\n \t\t       const struct unix_stream_listen_opts *opts);\n \n-- \ngitgitgadget\n\n"},{"id":"419193","messageId":"58c3fb7cd776600c2e859a7db23dcfe00b52c6f0.1615842510.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 06/12] simple-ipc: add win32 implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:23Z","receivedAt":"2021-03-15T21:09:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Windows implementation of \"simple-ipc\" using named pipes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   5 +\n compat/simple-ipc/ipc-shared.c      |  28 ++\n compat/simple-ipc/ipc-win32.c       | 751 ++++++++++++++++++++++++++++\n config.mak.uname                    |   2 +\n contrib/buildsystems/CMakeLists.txt |   4 +\n simple-ipc.h                        | 228 +++++++++\n 6 files changed, 1018 insertions(+)\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n\ndiff --git a/Makefile b/Makefile\nindex dd08b4ced01c..d3c42d3f4f9f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1667,6 +1667,11 @@ else\n \tLIB_OBJS += unix-socket.o\n endif\n \n+ifdef USE_WIN32_IPC\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-win32.o\n+endif\n+\n ifdef NO_ICONV\n \tBASIC_CFLAGS += -DNO_ICONV\n endif\ndiff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c\nnew file mode 100644\nindex 000000000000..1edec8159532\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-shared.c\n@@ -0,0 +1,28 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data)\n+{\n+\tstruct ipc_server_data *server_data = NULL;\n+\tint ret;\n+\n+\tret = ipc_server_run_async(&server_data, path, opts,\n+\t\t\t\t   application_cb, application_data);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = ipc_server_await(server_data);\n+\n+\tipc_server_free(server_data);\n+\n+\treturn ret;\n+}\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\ndiff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c\nnew file mode 100644\nindex 000000000000..8f89c02037e3\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-win32.c\n@@ -0,0 +1,751 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+#error This file can only be compiled on Windows\n+#endif\n+\n+static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc)\n+{\n+\tint off = 0;\n+\tstruct strbuf realpath = STRBUF_INIT;\n+\n+\tif (!strbuf_realpath(&realpath, path, 0))\n+\t\treturn -1;\n+\n+\toff = swprintf(wpath, alloc, L\"\\\\\\\\.\\\\pipe\\\\\");\n+\tif (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0)\n+\t\treturn -1;\n+\n+\t/* Handle drive prefix */\n+\tif (wpath[off] && wpath[off + 1] == L':') {\n+\t\twpath[off + 1] = L'_';\n+\t\toff += 2;\n+\t}\n+\n+\tfor (; wpath[off]; off++)\n+\t\tif (wpath[off] == L'/')\n+\t\t\twpath[off] = L'\\\\';\n+\n+\tstrbuf_release(&realpath);\n+\treturn 0;\n+}\n+\n+static enum ipc_active_state get_active_state(wchar_t *pipe_path)\n+{\n+\tif (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT))\n+\t\treturn IPC_STATE__LISTENING;\n+\n+\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\tif (GetLastError() == ERROR_FILE_NOT_FOUND)\n+\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\treturn IPC_STATE__OTHER_ERROR;\n+}\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\twchar_t pipe_path[MAX_PATH];\n+\n+\tif (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\treturn get_active_state(pipe_path);\n+}\n+\n+#define WAIT_STEP_MS (50)\n+\n+static enum ipc_active_state connect_to_server(\n+\tconst wchar_t *wpath,\n+\tDWORD timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tDWORD t_start_ms, t_waited_ms;\n+\tDWORD step_ms;\n+\tHANDLE hPipe = INVALID_HANDLE_VALUE;\n+\tDWORD mode = PIPE_READMODE_BYTE;\n+\tDWORD gle;\n+\n+\t*pfd = -1;\n+\n+\tfor (;;) {\n+\t\thPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE,\n+\t\t\t\t    0, NULL, OPEN_EXISTING, 0, NULL);\n+\t\tif (hPipe != INVALID_HANDLE_VALUE)\n+\t\t\tbreak;\n+\n+\t\tgle = GetLastError();\n+\n+\t\tswitch (gle) {\n+\t\tcase ERROR_FILE_NOT_FOUND:\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tstep_ms = (timeout_ms < WAIT_STEP_MS) ?\n+\t\t\t\ttimeout_ms : WAIT_STEP_MS;\n+\t\t\tsleep_millisec(step_ms);\n+\n+\t\t\ttimeout_ms -= step_ms;\n+\t\t\tbreak; /* try again */\n+\n+\t\tcase ERROR_PIPE_BUSY:\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tt_start_ms = (DWORD)(getnanotime() / 1000000);\n+\n+\t\t\tif (!WaitNamedPipeW(wpath, timeout_ms)) {\n+\t\t\t\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * A pipe server instance became available.\n+\t\t\t * Race other client processes to connect to\n+\t\t\t * it.\n+\t\t\t *\n+\t\t\t * But first decrement our overall timeout so\n+\t\t\t * that we don't starve if we keep losing the\n+\t\t\t * race.  But also guard against special\n+\t\t\t * NPMWAIT_ values (0 and -1).\n+\t\t\t */\n+\t\t\tt_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms;\n+\t\t\tif (t_waited_ms < timeout_ms)\n+\t\t\t\ttimeout_ms -= t_waited_ms;\n+\t\t\telse\n+\t\t\t\ttimeout_ms = 1;\n+\t\t\tbreak; /* try again */\n+\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t}\n+\t}\n+\n+\tif (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t*pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);\n+\tif (*pfd < 0) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t/* fd now owns hPipe */\n+\n+\treturn IPC_STATE__LISTENING;\n+}\n+\n+/*\n+ * The default connection timeout for Windows clients.\n+ *\n+ * This is not currently part of the ipc_ API (nor the config settings)\n+ * because of differences between Windows and other platforms.\n+ *\n+ * This value was chosen at random.\n+ */\n+#define WINDOWS_CONNECTION_TIMEOUT_MS (30000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\twchar_t wpath[MAX_PATH];\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tif (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0)\n+\t\tstate = IPC_STATE__INVALID_PATH;\n+\telse\n+\t\tstate = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n+\t\t\t\t\t       connection->fd) < 0 ||\n+\t    packet_flush_gently(connection->fd) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tFlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));\n+\n+\tif (read_packetized_to_strbuf(\n+\t\t    connection->fd, answer,\n+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *response)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, response);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Duplicate the given pipe handle and wrap it in a file descriptor so\n+ * that we can use pkt-line on it.\n+ */\n+static int dup_fd_from_pipe(const HANDLE pipe)\n+{\n+\tHANDLE process = GetCurrentProcess();\n+\tHANDLE handle;\n+\tint fd;\n+\n+\tif (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE,\n+\t\t\t     DUPLICATE_SAME_ACCESS)) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\treturn -1;\n+\t}\n+\n+\tfd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY);\n+\tif (fd < 0) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\tCloseHandle(handle);\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * `handle` is now owned by `fd` and will be automatically closed\n+\t * when the descriptor is closed.\n+\t */\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_SERVER_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_server_thread_data *server_thread_data;\n+};\n+\n+struct ipc_server_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+\tHANDLE hPipe;\n+};\n+\n+/*\n+ * On Windows, the conceptual \"ipc-server\" is implemented as a pool of\n+ * n idential/peer \"server-thread\" threads.  That is, there is no\n+ * hierarchy of threads; and therefore no controller thread managing\n+ * the pool.  Each thread has an independent handle to the named pipe,\n+ * receives incoming connections, processes the client, and re-uses\n+ * the pipe for the next client connection.\n+ *\n+ * Therefore, the \"ipc-server\" only needs to maintain a list of the\n+ * spawned threads for eventual \"join\" purposes.\n+ *\n+ * A single \"stop-event\" is visible to all of the server threads to\n+ * tell them to shutdown (when idle).\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\twchar_t wpath[MAX_PATH];\n+\n+\tHANDLE hEventStopRequested;\n+\tstruct ipc_server_thread_data *thread_list;\n+\tint is_stopped;\n+};\n+\n+enum connect_result {\n+\tCR_CONNECTED = 0,\n+\tCR_CONNECT_PENDING,\n+\tCR_CONNECT_ERROR,\n+\tCR_WAIT_ERROR,\n+\tCR_SHUTDOWN,\n+};\n+\n+static enum connect_result queue_overlapped_connect(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tif (ConnectNamedPipe(server_thread_data->hPipe, lpo))\n+\t\tgoto failed;\n+\n+\tswitch (GetLastError()) {\n+\tcase ERROR_IO_PENDING:\n+\t\treturn CR_CONNECT_PENDING;\n+\n+\tcase ERROR_PIPE_CONNECTED:\n+\t\tSetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+failed:\n+\terror(_(\"ConnectNamedPipe failed for '%s' (%lu)\"),\n+\t      server_thread_data->server_data->buf_path.buf,\n+\t      GetLastError());\n+\treturn CR_CONNECT_ERROR;\n+}\n+\n+/*\n+ * Use Windows Overlapped IO to wait for a connection or for our event\n+ * to be signalled.\n+ */\n+static enum connect_result wait_for_connection(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tenum connect_result r;\n+\tHANDLE waitHandles[2];\n+\tDWORD dwWaitResult;\n+\n+\tr = queue_overlapped_connect(server_thread_data, lpo);\n+\tif (r != CR_CONNECT_PENDING)\n+\t\treturn r;\n+\n+\twaitHandles[0] = server_thread_data->server_data->hEventStopRequested;\n+\twaitHandles[1] = lpo->hEvent;\n+\n+\tdwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);\n+\tswitch (dwWaitResult) {\n+\tcase WAIT_OBJECT_0 + 0:\n+\t\treturn CR_SHUTDOWN;\n+\n+\tcase WAIT_OBJECT_0 + 1:\n+\t\tResetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\treturn CR_WAIT_ERROR;\n+\t}\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf_no_flush(response, response_len,\n+\t\t\t\t\t\t  reply_data->fd);\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ *\n+ * Simple-IPC only contains one round trip, so we flush and close\n+ * here after the response.\n+ */\n+static int do_io(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.server_thread_data = server_thread_data;\n+\n+\treply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe);\n+\tif (reply_data.fd < 0)\n+\t\treturn error(_(\"could not create fd from pipe for '%s'\"),\n+\t\t\t     server_thread_data->server_data->buf_path.buf);\n+\n+\tret = read_packetized_to_strbuf(\n+\t\treply_data.fd, &buf,\n+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);\n+\tif (ret >= 0) {\n+\t\tret = server_thread_data->server_data->application_cb(\n+\t\t\tserver_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\n+\t\tFlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd)));\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Handle IPC request and response with this connected client.  And reset\n+ * the pipe to prepare for the next client.\n+ */\n+static int use_connection(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tint ret;\n+\n+\tret = do_io(server_thread_data);\n+\n+\tFlushFileBuffers(server_thread_data->hPipe);\n+\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Thread proc for an IPC server worker thread.  It handles a series of\n+ * connections from clients.  It cleans and reuses the hPipe between each\n+ * client.\n+ */\n+static void *server_thread_proc(void *_server_thread_data)\n+{\n+\tstruct ipc_server_thread_data *server_thread_data = _server_thread_data;\n+\tHANDLE hEventConnected = INVALID_HANDLE_VALUE;\n+\tOVERLAPPED oConnect;\n+\tenum connect_result cr;\n+\tint ret;\n+\n+\tassert(server_thread_data->hPipe != INVALID_HANDLE_VALUE);\n+\n+\ttrace2_thread_start(\"ipc-server\");\n+\ttrace2_data_string(\"ipc-server\", NULL, \"pipe\",\n+\t\t\t   server_thread_data->server_data->buf_path.buf);\n+\n+\thEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL);\n+\n+\tmemset(&oConnect, 0, sizeof(oConnect));\n+\toConnect.hEvent = hEventConnected;\n+\n+\tfor (;;) {\n+\t\tcr = wait_for_connection(server_thread_data, &oConnect);\n+\n+\t\tswitch (cr) {\n+\t\tcase CR_SHUTDOWN:\n+\t\t\tgoto finished;\n+\n+\t\tcase CR_CONNECTED:\n+\t\t\tret = use_connection(server_thread_data);\n+\t\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\tserver_thread_data->server_data);\n+\t\t\t\tgoto finished;\n+\t\t\t}\n+\t\t\tif (ret > 0) {\n+\t\t\t\t/*\n+\t\t\t\t * Ignore (transient) IO errors with this\n+\t\t\t\t * client and reset for the next client.\n+\t\t\t\t */\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase CR_CONNECT_PENDING:\n+\t\t\t/* By construction, this should not happen. */\n+\t\t\tBUG(\"ipc-server[%s]: unexpeced CR_CONNECT_PENDING\",\n+\t\t\t    server_thread_data->server_data->buf_path.buf);\n+\n+\t\tcase CR_CONNECT_ERROR:\n+\t\tcase CR_WAIT_ERROR:\n+\t\t\t/*\n+\t\t\t * Ignore these theoretical errors.\n+\t\t\t */\n+\t\t\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tBUG(\"unandled case after wait_for_connection\");\n+\t\t}\n+\t}\n+\n+finished:\n+\tCloseHandle(server_thread_data->hPipe);\n+\tCloseHandle(hEventConnected);\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+static HANDLE create_new_pipe(wchar_t *wpath, int is_first)\n+{\n+\tHANDLE hPipe;\n+\tDWORD dwOpenMode, dwPipeMode;\n+\tLPSECURITY_ATTRIBUTES lpsa = NULL;\n+\n+\tdwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |\n+\t\tFILE_FLAG_OVERLAPPED;\n+\n+\tdwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |\n+\t\tPIPE_REJECT_REMOTE_CLIENTS;\n+\n+\tif (is_first) {\n+\t\tdwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE;\n+\n+\t\t/*\n+\t\t * On Windows, the first server pipe instance gets to\n+\t\t * set the ACL / Security Attributes on the named\n+\t\t * pipe; subsequent instances inherit and cannot\n+\t\t * change them.\n+\t\t *\n+\t\t * TODO Should we allow the application layer to\n+\t\t * specify security attributes, such as `LocalService`\n+\t\t * or `LocalSystem`, when we create the named pipe?\n+\t\t * This question is probably not important when the\n+\t\t * daemon is started by a foreground user process and\n+\t\t * only needs to talk to the current user, but may be\n+\t\t * if the daemon is run via the Control Panel as a\n+\t\t * System Service.\n+\t\t */\n+\t}\n+\n+\thPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,\n+\t\t\t\t PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);\n+\n+\treturn hPipe;\n+}\n+\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct ipc_server_data *server_data;\n+\twchar_t wpath[MAX_PATH];\n+\tHANDLE hPipeFirst = INVALID_HANDLE_VALUE;\n+\tint k;\n+\tint ret = 0;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\tret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));\n+\tif (ret < 0) {\n+\t\terrno = EINVAL;\n+\t\treturn -1;\n+\t}\n+\n+\thPipeFirst = create_new_pipe(wpath, 1);\n+\tif (hPipeFirst == INVALID_HANDLE_VALUE) {\n+\t\terrno = EADDRINUSE;\n+\t\treturn -2;\n+\t}\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tserver_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL);\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\twcscpy(server_data->wpath, wpath);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_server_thread_data *std;\n+\n+\t\tstd = xcalloc(1, sizeof(*std));\n+\t\tstd->magic = MAGIC_SERVER_THREAD_DATA;\n+\t\tstd->server_data = server_data;\n+\t\tstd->hPipe = INVALID_HANDLE_VALUE;\n+\n+\t\tstd->hPipe = (k == 0)\n+\t\t\t? hPipeFirst\n+\t\t\t: create_new_pipe(server_data->wpath, 0);\n+\n+\t\tif (std->hPipe == INVALID_HANDLE_VALUE) {\n+\t\t\t/*\n+\t\t\t * If we've reached a pipe instance limit for\n+\t\t\t * this path, just use fewer threads.\n+\t\t\t */\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (pthread_create(&std->pthread_id, NULL,\n+\t\t\t\t   server_thread_proc, std)) {\n+\t\t\t/*\n+\t\t\t * Likewise, if we're out of threads, just use\n+\t\t\t * fewer threads than requested.\n+\t\t\t *\n+\t\t\t * However, we just give up if we can't even get\n+\t\t\t * one thread.  This should not happen.\n+\t\t\t */\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start thread[0] for '%s'\"),\n+\t\t\t\t    path);\n+\n+\t\t\tCloseHandle(std->hPipe);\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tstd->next_thread = server_data->thread_list;\n+\t\tserver_data->thread_list = std;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Gently tell all of the ipc_server threads to shutdown.\n+\t * This will be seen the next time they are idle (and waiting\n+\t * for a connection).\n+\t *\n+\t * We DO NOT attempt to force them to drop an active connection.\n+\t */\n+\tSetEvent(server_data->hEventStopRequested);\n+\treturn 0;\n+}\n+\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tDWORD dwWaitResult;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\tdwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE);\n+\tif (dwWaitResult != WAIT_OBJECT_0)\n+\t\treturn error(_(\"wait for hEvent failed for '%s'\"),\n+\t\t\t     server_data->buf_path.buf);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tpthread_join(std->pthread_id, NULL);\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tif (server_data->hEventStopRequested != INVALID_HANDLE_VALUE)\n+\t\tCloseHandle(server_data->hEventStopRequested);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tfree(server_data);\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex e22d4b6d67a3..2b3303f34be8 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows)\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \t# USE_NED_ALLOCATOR = YesPlease\n@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \tUSE_NED_ALLOCATOR = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex ac3dbc079af8..40c9e8e3bd9d 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n \tlist(APPEND compat_SOURCES unix-socket.c)\n endif()\n \n+if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+endif()\n+\n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\n \n #header checks\ndiff --git a/simple-ipc.h b/simple-ipc.h\nnew file mode 100644\nindex 000000000000..ab5619e3d76f\n--- /dev/null\n+++ b/simple-ipc.h\n@@ -0,0 +1,228 @@\n+#ifndef GIT_SIMPLE_IPC_H\n+#define GIT_SIMPLE_IPC_H\n+\n+/*\n+ * See Documentation/technical/api-simple-ipc.txt\n+ */\n+\n+#if defined(GIT_WINDOWS_NATIVE)\n+#define SUPPORTS_SIMPLE_IPC\n+#endif\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+#include \"pkt-line.h\"\n+\n+/*\n+ * Simple IPC Client Side API.\n+ */\n+\n+enum ipc_active_state {\n+\t/*\n+\t * The pipe/socket exists and the daemon is waiting for connections.\n+\t */\n+\tIPC_STATE__LISTENING = 0,\n+\n+\t/*\n+\t * The pipe/socket exists, but the daemon is not listening.\n+\t * Perhaps it is very busy.\n+\t * Perhaps the daemon died without deleting the path.\n+\t * Perhaps it is shutting down and draining existing clients.\n+\t * Perhaps it is dead, but other clients are lingering and\n+\t * still holding a reference to the pathname.\n+\t */\n+\tIPC_STATE__NOT_LISTENING,\n+\n+\t/*\n+\t * The requested pathname is bogus and no amount of retries\n+\t * will fix that.\n+\t */\n+\tIPC_STATE__INVALID_PATH,\n+\n+\t/*\n+\t * The requested pathname is not found.  This usually means\n+\t * that there is no daemon present.\n+\t */\n+\tIPC_STATE__PATH_NOT_FOUND,\n+\n+\tIPC_STATE__OTHER_ERROR,\n+};\n+\n+struct ipc_client_connect_options {\n+\t/*\n+\t * Spin under timeout if the server is running but can't\n+\t * accept our connection yet.  This should always be set\n+\t * unless you just want to poke the server and see if it\n+\t * is alive.\n+\t */\n+\tunsigned int wait_if_busy:1;\n+\n+\t/*\n+\t * Spin under timeout if the pipe/socket is not yet present\n+\t * on the file system.  This is useful if we just started\n+\t * the service and need to wait for it to become ready.\n+\t */\n+\tunsigned int wait_if_not_found:1;\n+};\n+\n+#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n+\t.wait_if_busy = 0, \\\n+\t.wait_if_not_found = 0, \\\n+}\n+\n+/*\n+ * Determine if a server is listening on this named pipe or socket using\n+ * platform-specific logic.  This might just probe the filesystem or it\n+ * might make a trivial connection to the server using this pathname.\n+ */\n+enum ipc_active_state ipc_get_active_state(const char *path);\n+\n+struct ipc_client_connection {\n+\tint fd;\n+};\n+\n+/*\n+ * Try to connect to the daemon on the named pipe or socket.\n+ *\n+ * Returns IPC_STATE__LISTENING and a connection handle.\n+ *\n+ * Otherwise, returns info to help decide whether to retry or to\n+ * spawn/respawn the server.\n+ */\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection);\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection);\n+\n+/*\n+ * Used by the client to synchronously send and receive a message with\n+ * the server on the provided client connection.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer);\n+\n+/*\n+ * Used by the client to synchronously connect and send and receive a\n+ * message to the server listening at the given path.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer);\n+\n+/*\n+ * Simple IPC Server Side API.\n+ */\n+\n+struct ipc_server_reply_data;\n+\n+typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *,\n+\t\t\t\t  const char *response,\n+\t\t\t\t  size_t response_len);\n+\n+/*\n+ * Prototype for an application-supplied callback to process incoming\n+ * client IPC messages and compose a reply.  The `application_cb` should\n+ * use the provided `reply_cb` and `reply_data` to send an IPC response\n+ * back to the client.  The `reply_cb` callback can be called multiple\n+ * times for chunking purposes.  A reply message is optional and may be\n+ * omitted if not necessary for the application.\n+ *\n+ * The return value from the application callback is ignored.\n+ * The value `SIMPLE_IPC_QUIT` can be used to shutdown the server.\n+ */\n+typedef int (ipc_server_application_cb)(void *application_data,\n+\t\t\t\t\tconst char *request,\n+\t\t\t\t\tipc_server_reply_cb *reply_cb,\n+\t\t\t\t\tstruct ipc_server_reply_data *reply_data);\n+\n+#define SIMPLE_IPC_QUIT -2\n+\n+/*\n+ * Opaque instance data to represent an IPC server instance.\n+ */\n+struct ipc_server_data;\n+\n+/*\n+ * Control parameters for the IPC server instance.\n+ * Use this to hide platform-specific settings.\n+ */\n+struct ipc_server_opts\n+{\n+\tint nr_threads;\n+};\n+\n+/*\n+ * Start an IPC server instance in one or more background threads\n+ * and return a handle to the pool.\n+ *\n+ * Returns 0 if the asynchronous server pool was started successfully.\n+ * Returns -1 if not.\n+ * Returns -2 if we could not startup because another server is using\n+ * the socket or named pipe.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data);\n+\n+/*\n+ * Gently signal the IPC server pool to shutdown.  No new client\n+ * connections will be accepted, but existing connections will be\n+ * allowed to complete.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data);\n+\n+/*\n+ * Block the calling thread until all threads in the IPC server pool\n+ * have completed and been joined.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data);\n+\n+/*\n+ * Close and free all resource handles associated with the IPC server\n+ * pool.\n+ */\n+void ipc_server_free(struct ipc_server_data *server_data);\n+\n+/*\n+ * Run an IPC server instance and block the calling thread of the\n+ * current process.  It does not return until the IPC server has\n+ * either shutdown or had an unrecoverable error.\n+ *\n+ * The IPC server handles incoming IPC messages from client processes\n+ * and may use one or more background threads as necessary.\n+ *\n+ * Returns 0 after the server has completed successfully.\n+ * Returns -1 if the server cannot be started.\n+ * Returns -2 if we could not startup because another server is using\n+ * the socket or named pipe.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ *\n+ * Note that `ipc_server_run()` is a synchronous wrapper around the\n+ * above asynchronous routines.  It effectively hides all of the\n+ * server state and thread details from the caller and presents a\n+ * simple synchronous interface.\n+ */\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data);\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\n+#endif /* GIT_SIMPLE_IPC_H */\n-- \ngitgitgadget\n\n"},{"id":"419194","messageId":"4e8c352fb366471c02d1cdf605d37e017eb3f507.1615842510.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 07/12] unix-socket: eliminate static unix_stream_socket() helper function","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:24Z","receivedAt":"2021-03-15T21:09:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nThe static helper function `unix_stream_socket()` calls `die()`.  This\nis not appropriate for all callers.  Eliminate the wrapper function\nand make the callers propagate the error.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 27 +++++++++++++--------------\n 1 file changed, 13 insertions(+), 14 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 19ed48be9902..69f81d64e9d5 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,14 +1,6 @@\n #include \"cache.h\"\n #include \"unix-socket.h\"\n \n-static int unix_stream_socket(void)\n-{\n-\tint fd = socket(AF_UNIX, SOCK_STREAM, 0);\n-\tif (fd < 0)\n-\t\tdie_errno(\"unable to create socket\");\n-\treturn fd;\n-}\n-\n static int chdir_len(const char *orig, int len)\n {\n \tchar *path = xmemdupz(orig, len);\n@@ -73,13 +65,16 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \n int unix_stream_connect(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n+\n \tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \tunix_sockaddr_cleanup(&ctx);\n@@ -87,15 +82,16 @@ int unix_stream_connect(const char *path)\n \n fail:\n \tsaved_errno = errno;\n+\tif (fd != -1)\n+\t\tclose(fd);\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n \n int unix_stream_listen(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -103,7 +99,9 @@ int unix_stream_listen(const char *path)\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n \n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n@@ -116,8 +114,9 @@ int unix_stream_listen(const char *path)\n \n fail:\n \tsaved_errno = errno;\n+\tif (fd != -1)\n+\t\tclose(fd);\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n-- \ngitgitgadget\n\n"},{"id":"419195","messageId":"4c2199231d050d27742c5a9519d48b2950a971eb.1615842510.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 11/12] simple-ipc: add Unix domain socket implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:28Z","receivedAt":"2021-03-15T21:09:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Unix domain socket based implementation of \"simple-ipc\".\n\nA set of `ipc_client` routines implement a client library to connect\nto an `ipc_server` over a Unix domain socket, send a simple request,\nand receive a single response.  Clients use blocking IO on the socket.\n\nA set of `ipc_server` routines implement a thread pool to listen for\nand concurrently service client connections.\n\nThe server creates a new Unix domain socket at a known location.  If a\nsocket already exists with that name, the server tries to determine if\nanother server is already listening on the socket or if the socket is\ndead.  If socket is busy, the server exits with an error rather than\nstealing the socket.  If the socket is dead, the server creates a new\none and starts up.\n\nIf while running, the server detects that its socket has been stolen\nby another server, it automatically exits.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |    2 +\n compat/simple-ipc/ipc-unix-socket.c | 1000 +++++++++++++++++++++++++++\n contrib/buildsystems/CMakeLists.txt |    2 +\n simple-ipc.h                        |   13 +-\n 4 files changed, 1016 insertions(+), 1 deletion(-)\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n\ndiff --git a/Makefile b/Makefile\nindex 012694276f6d..20dd65d19658 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1666,6 +1666,8 @@ ifdef NO_UNIX_SOCKETS\n else\n \tLIB_OBJS += unix-socket.o\n \tLIB_OBJS += unix-stream-server.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-unix-socket.o\n endif\n \n ifdef USE_WIN32_IPC\ndiff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c\nnew file mode 100644\nindex 000000000000..5e2e82a523a1\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-unix-socket.c\n@@ -0,0 +1,1000 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+#include \"unix-socket.h\"\n+#include \"unix-stream-server.h\"\n+\n+#ifdef NO_UNIX_SOCKETS\n+#error compat/simple-ipc/ipc-unix-socket.c requires Unix sockets\n+#endif\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\tstruct stat st;\n+\tstruct ipc_client_connection *connection_test = NULL;\n+\n+\toptions.wait_if_busy = 0;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (lstat(path, &st) == -1) {\n+\t\tswitch (errno) {\n+\t\tcase ENOENT:\n+\t\tcase ENOTDIR:\n+\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__INVALID_PATH;\n+\t\t}\n+\t}\n+\n+\t/* also complain if a plain file is in the way */\n+\tif ((st.st_mode & S_IFMT) != S_IFSOCK)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\t/*\n+\t * Just because the filesystem has a S_IFSOCK type inode\n+\t * at `path`, doesn't mean it that there is a server listening.\n+\t * Ping it to be sure.\n+\t */\n+\tstate = ipc_client_try_connect(path, &options, &connection_test);\n+\tipc_client_close_connection(connection_test);\n+\n+\treturn state;\n+}\n+\n+/*\n+ * Retry frequency when trying to connect to a server.\n+ *\n+ * This value should be short enough that we don't seriously delay our\n+ * caller, but not fast enough that our spinning puts pressure on the\n+ * system.\n+ */\n+#define WAIT_STEP_MS (50)\n+\n+/*\n+ * Try to connect to the server.  If the server is just starting up or\n+ * is very busy, we may not get a connection the first time.\n+ */\n+static enum ipc_active_state connect_to_server(\n+\tconst char *path,\n+\tint timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tint k;\n+\n+\t*pfd = -1;\n+\n+\tfor (k = 0; k < timeout_ms; k += WAIT_STEP_MS) {\n+\t\tint fd = unix_stream_connect(path, options->uds_disallow_chdir);\n+\n+\t\tif (fd != -1) {\n+\t\t\t*pfd = fd;\n+\t\t\treturn IPC_STATE__LISTENING;\n+\t\t}\n+\n+\t\tif (errno == ENOENT) {\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ETIMEDOUT) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ECONNREFUSED) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\n+\tsleep_and_try_again:\n+\t\tsleep_millisec(WAIT_STEP_MS);\n+\t}\n+\n+\treturn IPC_STATE__NOT_LISTENING;\n+}\n+\n+/*\n+ * The total amount of time that we are willing to wait when trying to\n+ * connect to a server.\n+ *\n+ * When the server is first started, it might take a little while for\n+ * it to become ready to service requests.  Likewise, the server may\n+ * be very (temporarily) busy and not respond to our connections.\n+ *\n+ * We should gracefully and silently handle those conditions and try\n+ * again for a reasonable time period.\n+ *\n+ * The value chosen here should be long enough for the server\n+ * to reliably heal from the above conditions.\n+ */\n+#define MY_CONNECTION_TIMEOUT_MS (1000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tstate = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n+\t\t\t\t\t       connection->fd) < 0 ||\n+\t    packet_flush_gently(connection->fd) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tif (read_packetized_to_strbuf(\n+\t\t    connection->fd, answer,\n+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, answer);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+static int set_socket_blocking_flag(int fd, int make_nonblocking)\n+{\n+\tint flags;\n+\n+\tflags = fcntl(fd, F_GETFL, NULL);\n+\n+\tif (flags < 0)\n+\t\treturn -1;\n+\n+\tif (make_nonblocking)\n+\t\tflags |= O_NONBLOCK;\n+\telse\n+\t\tflags &= ~O_NONBLOCK;\n+\n+\treturn fcntl(fd, F_SETFL, flags);\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_WORKER_THREAD_DATA,\n+\tMAGIC_ACCEPT_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_worker_thread_data *worker_thread_data;\n+};\n+\n+struct ipc_worker_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_worker_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+};\n+\n+struct ipc_accept_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_data *server_data;\n+\n+\tstruct unix_ss_socket *server_socket;\n+\n+\tint fd_send_shutdown;\n+\tint fd_wait_shutdown;\n+\tpthread_t pthread_id;\n+};\n+\n+/*\n+ * With unix-sockets, the conceptual \"ipc-server\" is implemented as a single\n+ * controller \"accept-thread\" thread and a pool of \"worker-thread\" threads.\n+ * The former does the usual `accept()` loop and dispatches connections\n+ * to an idle worker thread.  The worker threads wait in an idle loop for\n+ * a new connection, communicate with the client and relay data to/from\n+ * the `application_cb` and then wait for another connection from the\n+ * server thread.  This avoids the overhead of constantly creating and\n+ * destroying threads.\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\n+\tstruct ipc_accept_thread_data *accept_thread;\n+\tstruct ipc_worker_thread_data *worker_thread_list;\n+\n+\tpthread_mutex_t work_available_mutex;\n+\tpthread_cond_t work_available_cond;\n+\n+\t/*\n+\t * Accepted but not yet processed client connections are kept\n+\t * in a circular buffer FIFO.  The queue is empty when the\n+\t * positions are equal.\n+\t */\n+\tint *fifo_fds;\n+\tint queue_size;\n+\tint back_pos;\n+\tint front_pos;\n+\n+\tint shutdown_requested;\n+\tint is_stopped;\n+};\n+\n+/*\n+ * Remove and return the oldest queued connection.\n+ *\n+ * Returns -1 if empty.\n+ */\n+static int fifo_dequeue(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint fd;\n+\n+\tif (server_data->back_pos == server_data->front_pos)\n+\t\treturn -1;\n+\n+\tfd = server_data->fifo_fds[server_data->front_pos];\n+\tserver_data->fifo_fds[server_data->front_pos] = -1;\n+\n+\tserver_data->front_pos++;\n+\tif (server_data->front_pos == server_data->queue_size)\n+\t\tserver_data->front_pos = 0;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Push a new fd onto the back of the queue.\n+ *\n+ * Drop it and return -1 if queue is already full.\n+ */\n+static int fifo_enqueue(struct ipc_server_data *server_data, int fd)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint next_back_pos;\n+\n+\tnext_back_pos = server_data->back_pos + 1;\n+\tif (next_back_pos == server_data->queue_size)\n+\t\tnext_back_pos = 0;\n+\n+\tif (next_back_pos == server_data->front_pos) {\n+\t\t/* Queue is full. Just drop it. */\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data->fifo_fds[server_data->back_pos] = fd;\n+\tserver_data->back_pos = next_back_pos;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Wait for a connection to be queued to the FIFO and return it.\n+ *\n+ * Returns -1 if someone has already requested a shutdown.\n+ */\n+static int worker_thread__wait_for_connection(\n+\tstruct ipc_worker_thread_data *worker_thread_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tint fd = -1;\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\tfor (;;) {\n+\t\tif (server_data->shutdown_requested)\n+\t\t\tbreak;\n+\n+\t\tfd = fifo_dequeue(server_data);\n+\t\tif (fd >= 0)\n+\t\t\tbreak;\n+\n+\t\tpthread_cond_wait(&server_data->work_available_cond,\n+\t\t\t\t  &server_data->work_available_mutex);\n+\t}\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf_no_flush(response, response_len,\n+\t\t\t\t\t\t  reply_data->fd);\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_WAIT_POLL_TIMEOUT_MS (10)\n+\n+/*\n+ * If the client hangs up without sending any data on the wire, just\n+ * quietly close the socket and ignore this client.\n+ *\n+ * This worker thread is committed to reading the IPC request data\n+ * from the client at the other end of this fd.  Wait here for the\n+ * client to actually put something on the wire -- because if the\n+ * client just does a ping (connect and hangup without sending any\n+ * data), our use of the pkt-line read routines will spew an error\n+ * message.\n+ *\n+ * Return -1 if the client hung up.\n+ * Return 0 if data (possibly incomplete) is ready.\n+ */\n+static int worker_thread__wait_for_io_start(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tstruct pollfd pollfd[1];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = fd;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\tint in_shutdown;\n+\n+\t\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\t\tin_shutdown = server_data->shutdown_requested;\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\t\t\t/*\n+\t\t\t * If a shutdown is already in progress and this\n+\t\t\t * client has not started talking yet, just drop it.\n+\t\t\t */\n+\t\t\tif (in_shutdown)\n+\t\t\t\tgoto cleanup;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLHUP)\n+\t\t\tgoto cleanup;\n+\n+\t\tif (pollfd[0].revents & POLLIN)\n+\t\t\treturn 0;\n+\n+\t\tgoto cleanup;\n+\t}\n+\n+cleanup:\n+\tclose(fd);\n+\treturn -1;\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ */\n+static int worker_thread__do_io(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\t/* ASSERT NOT holding lock */\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.worker_thread_data = worker_thread_data;\n+\n+\treply_data.fd = fd;\n+\n+\tret = read_packetized_to_strbuf(\n+\t\treply_data.fd, &buf,\n+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);\n+\tif (ret >= 0) {\n+\t\tret = worker_thread_data->server_data->application_cb(\n+\t\t\tworker_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Block SIGPIPE on the current thread (so that we get EPIPE from\n+ * write() rather than an actual signal).\n+ *\n+ * Note that using sigchain_push() and _pop() to control SIGPIPE\n+ * around our IO calls is not thread safe:\n+ * [] It uses a global stack of handler frames.\n+ * [] It uses ALLOC_GROW() to resize it.\n+ * [] Finally, according to the `signal(2)` man-page:\n+ *    \"The effects of `signal()` in a multithreaded process are unspecified.\"\n+ */\n+static void thread_block_sigpipe(sigset_t *old_set)\n+{\n+\tsigset_t new_set;\n+\n+\tsigemptyset(&new_set);\n+\tsigaddset(&new_set, SIGPIPE);\n+\n+\tsigemptyset(old_set);\n+\tpthread_sigmask(SIG_BLOCK, &new_set, old_set);\n+}\n+\n+/*\n+ * Thread proc for an IPC worker thread.  It handles a series of\n+ * connections from clients.  It pulls the next fd from the queue\n+ * processes it, and then waits for the next client.\n+ *\n+ * Block SIGPIPE in this worker thread for the life of the thread.\n+ * This avoids stray (and sometimes delayed) SIGPIPE signals caused\n+ * by client errors and/or when we are under extremely heavy IO load.\n+ *\n+ * This means that the application callback will have SIGPIPE blocked.\n+ * The callback should not change it.\n+ */\n+static void *worker_thread_proc(void *_worker_thread_data)\n+{\n+\tstruct ipc_worker_thread_data *worker_thread_data = _worker_thread_data;\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tsigset_t old_set;\n+\tint fd, io;\n+\tint ret;\n+\n+\ttrace2_thread_start(\"ipc-worker\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tfd = worker_thread__wait_for_connection(worker_thread_data);\n+\t\tif (fd == -1)\n+\t\t\tbreak; /* in shutdown */\n+\n+\t\tio = worker_thread__wait_for_io_start(worker_thread_data, fd);\n+\t\tif (io == -1)\n+\t\t\tcontinue; /* client hung up without sending anything */\n+\n+\t\tret = worker_thread__do_io(worker_thread_data, fd);\n+\n+\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\ttrace2_data_string(\"ipc-worker\", NULL, \"queue_stop_async\",\n+\t\t\t\t\t   \"application_quit\");\n+\t\t\t/*\n+\t\t\t * The application layer is telling the ipc-server\n+\t\t\t * layer to shutdown.\n+\t\t\t *\n+\t\t\t * We DO NOT have a response to send to the client.\n+\t\t\t *\n+\t\t\t * Queue an async stop (to stop the other threads) and\n+\t\t\t * allow this worker thread to exit now (no sense waiting\n+\t\t\t * for the thread-pool shutdown signal).\n+\t\t\t *\n+\t\t\t * Other non-idle worker threads are allowed to finish\n+\t\t\t * responding to their current clients.\n+\t\t\t */\n+\t\t\tipc_server_stop_async(server_data);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)\n+\n+/*\n+ * Accept a new client connection on our socket.  This uses non-blocking\n+ * IO so that we can also wait for shutdown requests on our socket-pair\n+ * without actually spinning on a fast timeout.\n+ */\n+static int accept_thread__wait_for_connection(\n+\tstruct ipc_accept_thread_data *accept_thread_data)\n+{\n+\tstruct pollfd pollfd[2];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = accept_thread_data->fd_wait_shutdown;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tpollfd[1].fd = accept_thread_data->server_socket->fd_socket;\n+\t\tpollfd[1].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\treturn result;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\t/*\n+\t\t\t * If someone deletes or force-creates a new unix\n+\t\t\t * domain socket at our path, all future clients\n+\t\t\t * will be routed elsewhere and we silently starve.\n+\t\t\t * If that happens, just queue a shutdown.\n+\t\t\t */\n+\t\t\tif (unix_ss_was_stolen(\n+\t\t\t\t    accept_thread_data->server_socket)) {\n+\t\t\t\ttrace2_data_string(\"ipc-accept\", NULL,\n+\t\t\t\t\t\t   \"queue_stop_async\",\n+\t\t\t\t\t\t   \"socket_stolen\");\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\taccept_thread_data->server_data);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLIN) {\n+\t\t\t/* shutdown message queued to socketpair */\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (pollfd[1].revents & POLLIN) {\n+\t\t\t/* a connection is available on server_socket */\n+\n+\t\t\tint client_fd =\n+\t\t\t\taccept(accept_thread_data->server_socket->fd_socket,\n+\t\t\t\t       NULL, NULL);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\treturn client_fd;\n+\n+\t\t\t/*\n+\t\t\t * An error here is unlikely -- it probably\n+\t\t\t * indicates that the connecting process has\n+\t\t\t * already dropped the connection.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tBUG(\"unandled poll result errno=%d r[0]=%d r[1]=%d\",\n+\t\t    errno, pollfd[0].revents, pollfd[1].revents);\n+\t}\n+}\n+\n+/*\n+ * Thread proc for the IPC server \"accept thread\".  This waits for\n+ * an incoming socket connection, appends it to the queue of available\n+ * connections, and notifies a worker thread to process it.\n+ *\n+ * Block SIGPIPE in this thread for the life of the thread.  This\n+ * avoids any stray SIGPIPE signals when closing pipe fds under\n+ * extremely heavy loads (such as when the fifo queue is full and we\n+ * drop incomming connections).\n+ */\n+static void *accept_thread_proc(void *_accept_thread_data)\n+{\n+\tstruct ipc_accept_thread_data *accept_thread_data = _accept_thread_data;\n+\tstruct ipc_server_data *server_data = accept_thread_data->server_data;\n+\tsigset_t old_set;\n+\n+\ttrace2_thread_start(\"ipc-accept\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tint client_fd = accept_thread__wait_for_connection(\n+\t\t\taccept_thread_data);\n+\n+\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\tif (server_data->shutdown_requested) {\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\tclose(client_fd);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (client_fd < 0) {\n+\t\t\t/* ignore transient accept() errors */\n+\t\t}\n+\t\telse {\n+\t\t\tfifo_enqueue(server_data, client_fd);\n+\t\t\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\t\t}\n+\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * We can't predict the connection arrival rate relative to the worker\n+ * processing rate, therefore we allow the \"accept-thread\" to queue up\n+ * a generous number of connections, since we'd rather have the client\n+ * not unnecessarily timeout if we can avoid it.  (The assumption is\n+ * that this will be used for FSMonitor and a few second wait on a\n+ * connection is better than having the client timeout and do the full\n+ * computation itself.)\n+ *\n+ * The FIFO queue size is set to a multiple of the worker pool size.\n+ * This value chosen at random.\n+ */\n+#define FIFO_SCALE (100)\n+\n+/*\n+ * The backlog value for `listen(2)`.  This doesn't need to huge,\n+ * rather just large enough for our \"accept-thread\" to wake up and\n+ * queue incoming connections onto the FIFO without the kernel\n+ * dropping any.\n+ *\n+ * This value chosen at random.\n+ */\n+#define LISTEN_BACKLOG (50)\n+\n+static int create_listener_socket(\n+\tconst char *path,\n+\tconst struct ipc_server_opts *ipc_opts,\n+\tstruct unix_ss_socket **new_server_socket)\n+{\n+\tstruct unix_ss_socket *server_socket = NULL;\n+\tstruct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n+\tint ret;\n+\n+\tuslg_opts.listen_backlog_size = LISTEN_BACKLOG;\n+\tuslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;\n+\n+\tret = unix_ss_create(path, &uslg_opts, -1, &server_socket);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (set_socket_blocking_flag(server_socket->fd_socket, 1)) {\n+\t\tint saved_errno = errno;\n+\t\tunix_ss_free(server_socket);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\t*new_server_socket = server_socket;\n+\n+\ttrace2_data_string(\"ipc-server\", NULL, \"listen-with-lock\", path);\n+\treturn 0;\n+}\n+\n+static int setup_listener_socket(\n+\tconst char *path,\n+\tconst struct ipc_server_opts *ipc_opts,\n+\tstruct unix_ss_socket **new_server_socket)\n+{\n+\tint ret, saved_errno;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n+\n+\tret = create_listener_socket(path, ipc_opts, new_server_socket);\n+\n+\tsaved_errno = errno;\n+\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n+\terrno = saved_errno;\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Start IPC server in a pool of background threads.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct unix_ss_socket *server_socket = NULL;\n+\tstruct ipc_server_data *server_data;\n+\tint sv[2];\n+\tint k;\n+\tint ret;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\t/*\n+\t * Create a socketpair and set sv[1] to non-blocking.  This\n+\t * will used to send a shutdown message to the accept-thread\n+\t * and allows the accept-thread to wait on EITHER a client\n+\t * connection or a shutdown request without spinning.\n+\t */\n+\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)\n+\t\treturn -1;\n+\n+\tif (set_socket_blocking_flag(sv[1], 1)) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tret = setup_listener_socket(path, opts, &server_socket);\n+\tif (ret) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn ret;\n+\t}\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tpthread_mutex_init(&server_data->work_available_mutex, NULL);\n+\tpthread_cond_init(&server_data->work_available_cond, NULL);\n+\n+\tserver_data->queue_size = nr_threads * FIFO_SCALE;\n+\tserver_data->fifo_fds = xcalloc(server_data->queue_size,\n+\t\t\t\t\tsizeof(*server_data->fifo_fds));\n+\n+\tserver_data->accept_thread =\n+\t\txcalloc(1, sizeof(*server_data->accept_thread));\n+\tserver_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;\n+\tserver_data->accept_thread->server_data = server_data;\n+\tserver_data->accept_thread->server_socket = server_socket;\n+\tserver_data->accept_thread->fd_send_shutdown = sv[0];\n+\tserver_data->accept_thread->fd_wait_shutdown = sv[1];\n+\n+\tif (pthread_create(&server_data->accept_thread->pthread_id, NULL,\n+\t\t\t   accept_thread_proc, server_data->accept_thread))\n+\t\tdie_errno(_(\"could not start accept_thread '%s'\"), path);\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_worker_thread_data *wtd;\n+\n+\t\twtd = xcalloc(1, sizeof(*wtd));\n+\t\twtd->magic = MAGIC_WORKER_THREAD_DATA;\n+\t\twtd->server_data = server_data;\n+\n+\t\tif (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc,\n+\t\t\t\t   wtd)) {\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start worker[0] for '%s'\"),\n+\t\t\t\t    path);\n+\t\t\t/*\n+\t\t\t * Limp along with the thread pool that we have.\n+\t\t\t */\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\twtd->next_thread = server_data->worker_thread_list;\n+\t\tserver_data->worker_thread_list = wtd;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+/*\n+ * Gently tell the IPC server treads to shutdown.\n+ * Can be run on any thread.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tint fd;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\n+\tserver_data->shutdown_requested = 1;\n+\n+\t/*\n+\t * Write a byte to the shutdown socket pair to wake up the\n+\t * accept-thread.\n+\t */\n+\tif (write(server_data->accept_thread->fd_send_shutdown, \"Q\", 1) < 0)\n+\t\terror_errno(\"could not write to fd_send_shutdown\");\n+\n+\t/*\n+\t * Drain the queue of existing connections.\n+\t */\n+\twhile ((fd = fifo_dequeue(server_data)) != -1)\n+\t\tclose(fd);\n+\n+\t/*\n+\t * Gently tell worker threads to stop processing new connections\n+\t * and exit.  (This does not abort in-process conversations.)\n+\t */\n+\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\ttrace2_region_leave(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Wait for all IPC server threads to stop.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tpthread_join(server_data->accept_thread->pthread_id, NULL);\n+\n+\tif (!server_data->shutdown_requested)\n+\t\tBUG(\"ipc-server: accept-thread stopped for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tpthread_join(wtd->pthread_id, NULL);\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tstruct ipc_accept_thread_data * accept_thread_data;\n+\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\taccept_thread_data = server_data->accept_thread;\n+\tif (accept_thread_data) {\n+\t\tunix_ss_free(accept_thread_data->server_socket);\n+\n+\t\tif (accept_thread_data->fd_send_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_send_shutdown);\n+\t\tif (accept_thread_data->fd_wait_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_wait_shutdown);\n+\n+\t\tfree(server_data->accept_thread);\n+\t}\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tpthread_cond_destroy(&server_data->work_available_cond);\n+\tpthread_mutex_destroy(&server_data->work_available_mutex);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tfree(server_data->fifo_fds);\n+\tfree(server_data);\n+}\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex c94011269ebb..9897fcc8ea2a 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -248,6 +248,8 @@ endif()\n \n if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+else()\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c)\n endif()\n \n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\ndiff --git a/simple-ipc.h b/simple-ipc.h\nindex ab5619e3d76f..dc3606e30bd6 100644\n--- a/simple-ipc.h\n+++ b/simple-ipc.h\n@@ -5,7 +5,7 @@\n  * See Documentation/technical/api-simple-ipc.txt\n  */\n \n-#if defined(GIT_WINDOWS_NATIVE)\n+#if defined(GIT_WINDOWS_NATIVE) || !defined(NO_UNIX_SOCKETS)\n #define SUPPORTS_SIMPLE_IPC\n #endif\n \n@@ -62,11 +62,17 @@ struct ipc_client_connect_options {\n \t * the service and need to wait for it to become ready.\n \t */\n \tunsigned int wait_if_not_found:1;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n #define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n \t.wait_if_busy = 0, \\\n \t.wait_if_not_found = 0, \\\n+\t.uds_disallow_chdir = 0, \\\n }\n \n /*\n@@ -159,6 +165,11 @@ struct ipc_server_data;\n struct ipc_server_opts\n {\n \tint nr_threads;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"419196","messageId":"132b6f3271be4b2b1ed7b031d7fd31a39c2ba5cc.1615842510.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v6 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T21:08:29Z","receivedAt":"2021-03-15T21:09:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate t0052-simple-ipc.sh with unit tests for the \"simple-ipc\" mechanism.\n\nCreate t/helper/test-simple-ipc test tool to exercise the \"simple-ipc\"\nfunctions.\n\nWhen the tool is invoked with \"run-daemon\", it runs a server to listen\nfor \"simple-ipc\" connections on a test socket or named pipe and\nresponds to a set of commands to exercise/stress the communication\nsetup.\n\nWhen the tool is invoked with \"start-daemon\", it spawns a \"run-daemon\"\ncommand in the background and waits for the server to become ready\nbefore exiting.  (This helps make unit tests in t0052 more predictable\nand avoids the need for arbitrary sleeps in the test script.)\n\nThe tool also has a series of client \"send\" commands to send commands\nand data to a server instance.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                   |   1 +\n t/helper/test-simple-ipc.c | 787 +++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0052-simple-ipc.sh      | 122 ++++++\n 5 files changed, 912 insertions(+)\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\ndiff --git a/Makefile b/Makefile\nindex 20dd65d19658..e556388d28d0 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -734,6 +734,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o\n TEST_BUILTINS_OBJS += test-sha1.o\n TEST_BUILTINS_OBJS += test-sha256.o\n TEST_BUILTINS_OBJS += test-sigchain.o\n+TEST_BUILTINS_OBJS += test-simple-ipc.o\n TEST_BUILTINS_OBJS += test-strcmp-offset.o\n TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\ndiff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c\nnew file mode 100644\nindex 000000000000..42040ef81b1e\n--- /dev/null\n+++ b/t/helper/test-simple-ipc.c\n@@ -0,0 +1,787 @@\n+/*\n+ * test-simple-ipc.c: verify that the Inter-Process Communication works.\n+ */\n+\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"simple-ipc.h\"\n+#include \"parse-options.h\"\n+#include \"thread-utils.h\"\n+#include \"strvec.h\"\n+\n+#ifndef SUPPORTS_SIMPLE_IPC\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tdie(\"simple IPC not available on this platform\");\n+}\n+#else\n+\n+/*\n+ * The test daemon defines an \"application callback\" that supports a\n+ * series of commands (see `test_app_cb()`).\n+ *\n+ * Unknown commands are caught here and we send an error message back\n+ * to the client process.\n+ */\n+static int app__unhandled_command(const char *command,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n+\n+\tstrbuf_addf(&buf, \"unhandled command: %s\", command);\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a single very large buffer.  This is to ensure that\n+ * long response are properly handled -- whether the chunking occurs\n+ * in the kernel or in the (probably pkt-line) layer.\n+ */\n+#define BIG_ROWS (10000)\n+static int app__big_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t    struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < BIG_ROWS; row++)\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a series of lines.  This is to ensure that we can incrementally\n+ * compute the response and chunk it to the client.\n+ */\n+#define CHUNK_ROWS (10000)\n+static int app__chunk_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t      struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < CHUNK_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Slowly reply with a series of lines.  This is to model an expensive to\n+ * compute chunked response (which might happen if this callback is running\n+ * in a thread and is fighting for a lock with other threads).\n+ */\n+#define SLOW_ROWS     (1000)\n+#define SLOW_DELAY_MS (10)\n+static int app__slow_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t     struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < SLOW_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t\tsleep_millisec(SLOW_DELAY_MS);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * The client sent a command followed by a (possibly very) large buffer.\n+ */\n+static int app__sendbytes_command(const char *received,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\tconst char *p = \"?\";\n+\tint len_ballast = 0;\n+\tint k;\n+\tint errs = 0;\n+\tint ret;\n+\n+\tif (skip_prefix(received, \"sendbytes \", &p))\n+\t\tlen_ballast = strlen(p);\n+\n+\t/*\n+\t * Verify that the ballast is n copies of a single letter.\n+\t * And that the multi-threaded IO layer didn't cross the streams.\n+\t */\n+\tfor (k = 1; k < len_ballast; k++)\n+\t\tif (p[k] != p[0])\n+\t\t\terrs++;\n+\n+\tif (errs)\n+\t\tstrbuf_addf(&buf_resp, \"errs:%d\\n\", errs);\n+\telse\n+\t\tstrbuf_addf(&buf_resp, \"rcvd:%c%08d\\n\", p[0], len_ballast);\n+\n+\tret = reply_cb(reply_data, buf_resp.buf, buf_resp.len);\n+\n+\tstrbuf_release(&buf_resp);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * An arbitrary fixed address to verify that the application instance\n+ * data is handled properly.\n+ */\n+static int my_app_data = 42;\n+\n+static ipc_server_application_cb test_app_cb;\n+\n+/*\n+ * This is the \"application callback\" that sits on top of the\n+ * \"ipc-server\".  It completely defines the set of commands supported\n+ * by this application.\n+ */\n+static int test_app_cb(void *application_data,\n+\t\t       const char *command,\n+\t\t       ipc_server_reply_cb *reply_cb,\n+\t\t       struct ipc_server_reply_data *reply_data)\n+{\n+\t/*\n+\t * Verify that we received the application-data that we passed\n+\t * when we started the ipc-server.  (We have several layers of\n+\t * callbacks calling callbacks and it's easy to get things mixed\n+\t * up (especially when some are \"void*\").)\n+\t */\n+\tif (application_data != (void*)&my_app_data)\n+\t\tBUG(\"application_cb: application_data pointer wrong\");\n+\n+\tif (!strcmp(command, \"quit\")) {\n+\t\t/*\n+\t\t * The client sent a \"quit\" command.  This is an async\n+\t\t * request for the server to shutdown.\n+\t\t *\n+\t\t * We DO NOT send the client a response message\n+\t\t * (because we have nothing to say and the other\n+\t\t * server threads have not yet stopped).\n+\t\t *\n+\t\t * Tell the ipc-server layer to start shutting down.\n+\t\t * This includes: stop listening for new connections\n+\t\t * on the socket/pipe and telling all worker threads\n+\t\t * to finish/drain their outgoing responses to other\n+\t\t * clients.\n+\t\t *\n+\t\t * This DOES NOT force an immediate sync shutdown.\n+\t\t */\n+\t\treturn SIMPLE_IPC_QUIT;\n+\t}\n+\n+\tif (!strcmp(command, \"ping\")) {\n+\t\tconst char *answer = \"pong\";\n+\t\treturn reply_cb(reply_data, answer, strlen(answer));\n+\t}\n+\n+\tif (!strcmp(command, \"big\"))\n+\t\treturn app__big_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"chunk\"))\n+\t\treturn app__chunk_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"slow\"))\n+\t\treturn app__slow_command(reply_cb, reply_data);\n+\n+\tif (starts_with(command, \"sendbytes \"))\n+\t\treturn app__sendbytes_command(command, reply_cb, reply_data);\n+\n+\treturn app__unhandled_command(command, reply_cb, reply_data);\n+}\n+\n+struct cl_args\n+{\n+\tconst char *subcommand;\n+\tconst char *path;\n+\tconst char *token;\n+\n+\tint nr_threads;\n+\tint max_wait_sec;\n+\tint bytecount;\n+\tint batchsize;\n+\n+\tchar bytevalue;\n+};\n+\n+static struct cl_args cl_args = {\n+\t.subcommand = NULL,\n+\t.path = \"ipc-test\",\n+\t.token = NULL,\n+\n+\t.nr_threads = 5,\n+\t.max_wait_sec = 60,\n+\t.bytecount = 1024,\n+\t.batchsize = 10,\n+\n+\t.bytevalue = 'x',\n+};\n+\n+/*\n+ * This process will run as a simple-ipc server and listen for IPC commands\n+ * from client processes.\n+ */\n+static int daemon__run_server(void)\n+{\n+\tint ret;\n+\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = cl_args.nr_threads,\n+\t};\n+\n+\t/*\n+\t * Synchronously run the ipc-server.  We don't need any application\n+\t * instance data, so pass an arbitrary pointer (that we'll later\n+\t * verify made the round trip).\n+\t */\n+\tret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data);\n+\tif (ret == -2)\n+\t\terror(_(\"socket/pipe already in use: '%s'\"), cl_args.path);\n+\telse if (ret == -1)\n+\t\terror_errno(_(\"could not start server on: '%s'\"), cl_args.path);\n+\n+\treturn ret;\n+}\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+/*\n+ * This is adapted from `daemonize()`.  Use `fork()` to directly create and\n+ * run the daemon in a child process.\n+ */\n+static int spawn_server(pid_t *pid)\n+{\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = cl_args.nr_threads,\n+\t};\n+\n+\t*pid = fork();\n+\n+\tswitch (*pid) {\n+\tcase 0:\n+\t\tif (setsid() == -1)\n+\t\t\terror_errno(_(\"setsid failed\"));\n+\t\tclose(0);\n+\t\tclose(1);\n+\t\tclose(2);\n+\t\tsanitize_stdfds();\n+\n+\t\treturn ipc_server_run(cl_args.path, &opts, test_app_cb,\n+\t\t\t\t      (void*)&my_app_data);\n+\n+\tcase -1:\n+\t\treturn error_errno(_(\"could not spawn daemon in the background\"));\n+\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+}\n+#else\n+/*\n+ * Conceptually like `daemonize()` but different because Windows does not\n+ * have `fork(2)`.  Spawn a normal Windows child process but without the\n+ * limitations of `start_command()` and `finish_command()`.\n+ */\n+static int spawn_server(pid_t *pid)\n+{\n+\tchar test_tool_exe[MAX_PATH];\n+\tstruct strvec args = STRVEC_INIT;\n+\tint in, out;\n+\n+\tGetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);\n+\n+\tin = open(\"/dev/null\", O_RDONLY);\n+\tout = open(\"/dev/null\", O_WRONLY);\n+\n+\tstrvec_push(&args, test_tool_exe);\n+\tstrvec_push(&args, \"simple-ipc\");\n+\tstrvec_push(&args, \"run-daemon\");\n+\tstrvec_pushf(&args, \"--name=%s\", cl_args.path);\n+\tstrvec_pushf(&args, \"--threads=%d\", cl_args.nr_threads);\n+\n+\t*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);\n+\tclose(in);\n+\tclose(out);\n+\n+\tstrvec_clear(&args);\n+\n+\tif (*pid < 0)\n+\t\treturn error(_(\"could not spawn daemon in the background\"));\n+\n+\treturn 0;\n+}\n+#endif\n+\n+/*\n+ * This is adapted from `wait_or_whine()`.  Watch the child process and\n+ * let it get started and begin listening for requests on the socket\n+ * before reporting our success.\n+ */\n+static int wait_for_server_startup(pid_t pid_child)\n+{\n+\tint status;\n+\tpid_t pid_seen;\n+\tenum ipc_active_state s;\n+\ttime_t time_limit, now;\n+\n+\ttime(&time_limit);\n+\ttime_limit += cl_args.max_wait_sec;\n+\n+\tfor (;;) {\n+\t\tpid_seen = waitpid(pid_child, &status, WNOHANG);\n+\n+\t\tif (pid_seen == -1)\n+\t\t\treturn error_errno(_(\"waitpid failed\"));\n+\n+\t\telse if (pid_seen == 0) {\n+\t\t\t/*\n+\t\t\t * The child is still running (this should be\n+\t\t\t * the normal case).  Try to connect to it on\n+\t\t\t * the socket and see if it is ready for\n+\t\t\t * business.\n+\t\t\t *\n+\t\t\t * If there is another daemon already running,\n+\t\t\t * our child will fail to start (possibly\n+\t\t\t * after a timeout on the lock), but we don't\n+\t\t\t * care (who responds) if the socket is live.\n+\t\t\t */\n+\t\t\ts = ipc_get_active_state(cl_args.path);\n+\t\t\tif (s == IPC_STATE__LISTENING)\n+\t\t\t\treturn 0;\n+\n+\t\t\ttime(&now);\n+\t\t\tif (now > time_limit)\n+\t\t\t\treturn error(_(\"daemon not online yet\"));\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\telse if (pid_seen == pid_child) {\n+\t\t\t/*\n+\t\t\t * The new child daemon process shutdown while\n+\t\t\t * it was starting up, so it is not listening\n+\t\t\t * on the socket.\n+\t\t\t *\n+\t\t\t * Try to ping the socket in the odd chance\n+\t\t\t * that another daemon started (or was already\n+\t\t\t * running) while our child was starting.\n+\t\t\t *\n+\t\t\t * Again, we don't care who services the socket.\n+\t\t\t */\n+\t\t\ts = ipc_get_active_state(cl_args.path);\n+\t\t\tif (s == IPC_STATE__LISTENING)\n+\t\t\t\treturn 0;\n+\n+\t\t\t/*\n+\t\t\t * We don't care about the WEXITSTATUS() nor\n+\t\t\t * any of the WIF*(status) values because\n+\t\t\t * `cmd__simple_ipc()` does the `!!result`\n+\t\t\t * trick on all function return values.\n+\t\t\t *\n+\t\t\t * So it is sufficient to just report the\n+\t\t\t * early shutdown as an error.\n+\t\t\t */\n+\t\t\treturn error(_(\"daemon failed to start\"));\n+\t\t}\n+\n+\t\telse\n+\t\t\treturn error(_(\"waitpid is confused\"));\n+\t}\n+}\n+\n+/*\n+ * This process will start a simple-ipc server in a background process and\n+ * wait for it to become ready.  This is like `daemonize()` but gives us\n+ * more control and better error reporting (and makes it easier to write\n+ * unit tests).\n+ */\n+static int daemon__start_server(void)\n+{\n+\tpid_t pid_child;\n+\tint ret;\n+\n+\t/*\n+\t * Run the actual daemon in a background process.\n+\t */\n+\tret = spawn_server(&pid_child);\n+\tif (pid_child <= 0)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Let the parent wait for the child process to get started\n+\t * and begin listening for requests on the socket.\n+\t */\n+\tret = wait_for_server_startup(pid_child);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * This process will run a quick probe to see if a simple-ipc server\n+ * is active on this path.\n+ *\n+ * Returns 0 if the server is alive.\n+ */\n+static int client__probe_server(void)\n+{\n+\tenum ipc_active_state s;\n+\n+\ts = ipc_get_active_state(cl_args.path);\n+\tswitch (s) {\n+\tcase IPC_STATE__LISTENING:\n+\t\treturn 0;\n+\n+\tcase IPC_STATE__NOT_LISTENING:\n+\t\treturn error(\"no server listening at '%s'\", cl_args.path);\n+\n+\tcase IPC_STATE__PATH_NOT_FOUND:\n+\t\treturn error(\"path not found '%s'\", cl_args.path);\n+\n+\tcase IPC_STATE__INVALID_PATH:\n+\t\treturn error(\"invalid pipe/socket name '%s'\", cl_args.path);\n+\n+\tcase IPC_STATE__OTHER_ERROR:\n+\tdefault:\n+\t\treturn error(\"other error for '%s'\", cl_args.path);\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command token to an already-running server daemon and\n+ * print the response.\n+ *\n+ * This is a simple 1 word command/token that `test_app_cb()` (in the\n+ * daemon process) will understand.\n+ */\n+static int client__send_ipc(void)\n+{\n+\tconst char *command = \"(no-command)\";\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\tif (cl_args.token && *cl_args.token)\n+\t\tcommand = cl_args.token;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (!ipc_client_send_command(cl_args.path, &options, command, &buf)) {\n+\t\tif (buf.len) {\n+\t\t\tprintf(\"%s\\n\", buf.buf);\n+\t\t\tfflush(stdout);\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"failed to send '%s' to '%s'\", command, cl_args.path);\n+}\n+\n+/*\n+ * Send an IPC command to an already-running server and ask it to\n+ * shutdown.  \"send quit\" is an async request and queues a shutdown\n+ * event in the server, so we spin and wait here for it to actually\n+ * shutdown to make the unit tests a little easier to write.\n+ */\n+static int client__stop_server(void)\n+{\n+\tint ret;\n+\ttime_t time_limit, now;\n+\tenum ipc_active_state s;\n+\n+\ttime(&time_limit);\n+\ttime_limit += cl_args.max_wait_sec;\n+\n+\tcl_args.token = \"quit\";\n+\n+\tret = client__send_ipc();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tfor (;;) {\n+\t\tsleep_millisec(100);\n+\n+\t\ts = ipc_get_active_state(cl_args.path);\n+\n+\t\tif (s != IPC_STATE__LISTENING) {\n+\t\t\t/*\n+\t\t\t * The socket/pipe is gone and/or has stopped\n+\t\t\t * responding.  Lets assume that the daemon\n+\t\t\t * process has exited too.\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\ttime(&now);\n+\t\tif (now > time_limit)\n+\t\t\treturn error(_(\"daemon has not shutdown yet\"));\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command followed by ballast to confirm that a large\n+ * message can be sent and that the kernel or pkt-line layers will\n+ * properly chunk it and that the daemon receives the entire message.\n+ */\n+static int do_sendbytes(int bytecount, char byte, const char *path,\n+\t\t\tconst struct ipc_client_connect_options *options)\n+{\n+\tstruct strbuf buf_send = STRBUF_INIT;\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_send, \"sendbytes \");\n+\tstrbuf_addchars(&buf_send, byte, bytecount);\n+\n+\tif (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {\n+\t\tstrbuf_rtrim(&buf_resp);\n+\t\tprintf(\"sent:%c%08d %s\\n\", byte, bytecount, buf_resp.buf);\n+\t\tfflush(stdout);\n+\t\tstrbuf_release(&buf_send);\n+\t\tstrbuf_release(&buf_resp);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"client failed to sendbytes(%d, '%c') to '%s'\",\n+\t\t     bytecount, byte, path);\n+}\n+\n+/*\n+ * Send an IPC command with ballast to an already-running server daemon.\n+ */\n+static int client__sendbytes(void)\n+{\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\toptions.uds_disallow_chdir = 0;\n+\n+\treturn do_sendbytes(cl_args.bytecount, cl_args.bytevalue, cl_args.path,\n+\t\t\t    &options);\n+}\n+\n+struct multiple_thread_data {\n+\tpthread_t pthread_id;\n+\tstruct multiple_thread_data *next;\n+\tconst char *path;\n+\tint bytecount;\n+\tint batchsize;\n+\tint sum_errors;\n+\tint sum_good;\n+\tchar letter;\n+};\n+\n+static void *multiple_thread_proc(void *_multiple_thread_data)\n+{\n+\tstruct multiple_thread_data *d = _multiple_thread_data;\n+\tint k;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\t/*\n+\t * A multi-threaded client should not be randomly calling chdir().\n+\t * The test will pass without this restriction because the test is\n+\t * not otherwise accessing the filesystem, but it makes us honest.\n+\t */\n+\toptions.uds_disallow_chdir = 1;\n+\n+\ttrace2_thread_start(\"multiple\");\n+\n+\tfor (k = 0; k < d->batchsize; k++) {\n+\t\tif (do_sendbytes(d->bytecount + k, d->letter, d->path, &options))\n+\t\t\td->sum_errors++;\n+\t\telse\n+\t\t\td->sum_good++;\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * Start a client-side thread pool.  Each thread sends a series of\n+ * IPC requests.  Each request is on a new connection to the server.\n+ */\n+static int client__multiple(void)\n+{\n+\tstruct multiple_thread_data *list = NULL;\n+\tint k;\n+\tint sum_join_errors = 0;\n+\tint sum_thread_errors = 0;\n+\tint sum_good = 0;\n+\n+\tfor (k = 0; k < cl_args.nr_threads; k++) {\n+\t\tstruct multiple_thread_data *d = xcalloc(1, sizeof(*d));\n+\t\td->next = list;\n+\t\td->path = cl_args.path;\n+\t\td->bytecount = cl_args.bytecount + cl_args.batchsize*(k/26);\n+\t\td->batchsize = cl_args.batchsize;\n+\t\td->sum_errors = 0;\n+\t\td->sum_good = 0;\n+\t\td->letter = 'A' + (k % 26);\n+\n+\t\tif (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) {\n+\t\t\twarning(\"failed to create thread[%d] skipping remainder\", k);\n+\t\t\tfree(d);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tlist = d;\n+\t}\n+\n+\twhile (list) {\n+\t\tstruct multiple_thread_data *d = list;\n+\n+\t\tif (pthread_join(d->pthread_id, NULL))\n+\t\t\tsum_join_errors++;\n+\n+\t\tsum_thread_errors += d->sum_errors;\n+\t\tsum_good += d->sum_good;\n+\n+\t\tlist = d->next;\n+\t\tfree(d);\n+\t}\n+\n+\tprintf(\"client (good %d) (join %d), (errors %d)\\n\",\n+\t       sum_good, sum_join_errors, sum_thread_errors);\n+\n+\treturn (sum_join_errors + sum_thread_errors) ? 1 : 0;\n+}\n+\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tconst char * const simple_ipc_usage[] = {\n+\t\tN_(\"test-helper simple-ipc is-active    [<name>] [<options>]\"),\n+\t\tN_(\"test-helper simple-ipc run-daemon   [<name>] [<threads>]\"),\n+\t\tN_(\"test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]\"),\n+\t\tN_(\"test-helper simple-ipc stop-daemon  [<name>] [<max-wait>]\"),\n+\t\tN_(\"test-helper simple-ipc send         [<name>] [<token>]\"),\n+\t\tN_(\"test-helper simple-ipc sendbytes    [<name>] [<bytecount>] [<byte>]\"),\n+\t\tN_(\"test-helper simple-ipc multiple     [<name>] [<threads>] [<bytecount>] [<batchsize>]\"),\n+\t\tNULL\n+\t};\n+\n+\tconst char *bytevalue = NULL;\n+\n+\tstruct option options[] = {\n+#ifndef GIT_WINDOWS_NATIVE\n+\t\tOPT_STRING(0, \"name\", &cl_args.path, N_(\"name\"), N_(\"name or pathname of unix domain socket\")),\n+#else\n+\t\tOPT_STRING(0, \"name\", &cl_args.path, N_(\"name\"), N_(\"named-pipe name\")),\n+#endif\n+\t\tOPT_INTEGER(0, \"threads\", &cl_args.nr_threads, N_(\"number of threads in server thread pool\")),\n+\t\tOPT_INTEGER(0, \"max-wait\", &cl_args.max_wait_sec, N_(\"seconds to wait for daemon to start or stop\")),\n+\n+\t\tOPT_INTEGER(0, \"bytecount\", &cl_args.bytecount, N_(\"number of bytes\")),\n+\t\tOPT_INTEGER(0, \"batchsize\", &cl_args.batchsize, N_(\"number of requests per thread\")),\n+\n+\t\tOPT_STRING(0, \"byte\", &bytevalue, N_(\"byte\"), N_(\"ballast character\")),\n+\t\tOPT_STRING(0, \"token\", &cl_args.token, N_(\"token\"), N_(\"command token to send to the server\")),\n+\n+\t\tOPT_END()\n+\t};\n+\n+\tif (argc < 2)\n+\t\tusage_with_options(simple_ipc_usage, options);\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage_with_options(simple_ipc_usage, options);\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"SUPPORTS_SIMPLE_IPC\"))\n+\t\treturn 0;\n+\n+\tcl_args.subcommand = argv[1];\n+\n+\targc--;\n+\targv++;\n+\n+\targc = parse_options(argc, argv, NULL, options, simple_ipc_usage, 0);\n+\n+\tif (cl_args.nr_threads < 1)\n+\t\tcl_args.nr_threads = 1;\n+\tif (cl_args.max_wait_sec < 0)\n+\t\tcl_args.max_wait_sec = 0;\n+\tif (cl_args.bytecount < 1)\n+\t\tcl_args.bytecount = 1;\n+\tif (cl_args.batchsize < 1)\n+\t\tcl_args.batchsize = 1;\n+\n+\tif (bytevalue && *bytevalue)\n+\t\tcl_args.bytevalue = bytevalue[0];\n+\n+\t/*\n+\t * Use '!!' on all dispatch functions to map from `error()` style\n+\t * (returns -1) style to `test_must_fail` style (expects 1).  This\n+\t * makes shell error messages less confusing.\n+\t */\n+\n+\tif (!strcmp(cl_args.subcommand, \"is-active\"))\n+\t\treturn !!client__probe_server();\n+\n+\tif (!strcmp(cl_args.subcommand, \"run-daemon\"))\n+\t\treturn !!daemon__run_server();\n+\n+\tif (!strcmp(cl_args.subcommand, \"start-daemon\"))\n+\t\treturn !!daemon__start_server();\n+\n+\t/*\n+\t * Client commands follow.  Ensure a server is running before\n+\t * sending any data.  This might be overkill, but then again\n+\t * this is a test harness.\n+\t */\n+\n+\tif (!strcmp(cl_args.subcommand, \"stop-daemon\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__stop_server();\n+\t}\n+\n+\tif (!strcmp(cl_args.subcommand, \"send\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__send_ipc();\n+\t}\n+\n+\tif (!strcmp(cl_args.subcommand, \"sendbytes\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__sendbytes();\n+\t}\n+\n+\tif (!strcmp(cl_args.subcommand, \"multiple\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__multiple();\n+\t}\n+\n+\tdie(\"Unhandled subcommand: '%s'\", cl_args.subcommand);\n+}\n+#endif\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f97cd9f48a69..287aa6002307 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -65,6 +65,7 @@ static struct test_cmd cmds[] = {\n \t{ \"sha1\", cmd__sha1 },\n \t{ \"sha256\", cmd__sha256 },\n \t{ \"sigchain\", cmd__sigchain },\n+\t{ \"simple-ipc\", cmd__simple_ipc },\n \t{ \"strcmp-offset\", cmd__strcmp_offset },\n \t{ \"string-list\", cmd__string_list },\n \t{ \"submodule-config\", cmd__submodule_config },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 28072c0ad5ab..9ea4b31011dd 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -55,6 +55,7 @@ int cmd__sha1(int argc, const char **argv);\n int cmd__oid_array(int argc, const char **argv);\n int cmd__sha256(int argc, const char **argv);\n int cmd__sigchain(int argc, const char **argv);\n+int cmd__simple_ipc(int argc, const char **argv);\n int cmd__strcmp_offset(int argc, const char **argv);\n int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\ndiff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\nnew file mode 100755\nindex 000000000000..ff98be31a51b\n--- /dev/null\n+++ b/t/t0052-simple-ipc.sh\n@@ -0,0 +1,122 @@\n+#!/bin/sh\n+\n+test_description='simple command server'\n+\n+. ./test-lib.sh\n+\n+test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {\n+\tskip_all='simple IPC not supported on this platform'\n+\ttest_done\n+}\n+\n+stop_simple_IPC_server () {\n+\ttest-tool simple-ipc stop-daemon\n+}\n+\n+test_expect_success 'start simple command server' '\n+\ttest_atexit stop_simple_IPC_server &&\n+\ttest-tool simple-ipc start-daemon --threads=8 &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'simple command server' '\n+\ttest-tool simple-ipc send --token=ping >actual &&\n+\techo pong >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'servers cannot share the same path' '\n+\ttest_must_fail test-tool simple-ipc run-daemon &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'big response' '\n+\ttest-tool simple-ipc send --token=big >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'chunk response' '\n+\ttest-tool simple-ipc send --token=chunk >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'slow response' '\n+\ttest-tool simple-ipc send --token=slow >actual &&\n+\ttest_line_count -ge 100 actual &&\n+\tgrep -q \"big: [0]*99\\$\" actual\n+'\n+\n+# Send an IPC with n=100,000 bytes of ballast.  This should be large enough\n+# to force both the kernel and the pkt-line layer to chunk the message to the\n+# daemon and for the daemon to receive it in chunks.\n+#\n+test_expect_success 'sendbytes' '\n+\ttest-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual &&\n+\tgrep \"sent:A00100000 rcvd:A00100000\" actual\n+'\n+\n+# Start a series of <threads> client threads that each make <batchsize>\n+# IPC requests to the server.  Each (<threads> * <batchsize>) request\n+# will open a new connection to the server and randomly bind to a server\n+# thread.  Each client thread exits after completing its batch.  So the\n+# total number of live client threads will be smaller than the total.\n+# Each request will send a message containing at least <bytecount> bytes\n+# of ballast.  (Responses are small.)\n+#\n+# The purpose here is to test threading in the server and responding to\n+# many concurrent client requests (regardless of whether they come from\n+# 1 client process or many).  And to test that the server side of the\n+# named pipe/socket is stable.  (On Windows this means that the server\n+# pipe is properly recycled.)\n+#\n+# On Windows it also lets us adjust the connection timeout in the\n+# `ipc_client_send_command()`.\n+#\n+# Note it is easy to drive the system into failure by requesting an\n+# insane number of threads on client or server and/or increasing the\n+# per-thread batchsize or the per-request bytecount (ballast).\n+# On Windows these failures look like \"pipe is busy\" errors.\n+# So I've chosen fairly conservative values for now.\n+#\n+# We expect output of the form \"sent:<letter><length> ...\"\n+# With terms (7, 19, 13) we expect:\n+#   <letter> in [A-G]\n+#   <length> in [19+0 .. 19+(13-1)]\n+# and (7 * 13) successful responses.\n+#\n+test_expect_success 'stress test threads' '\n+\ttest-tool simple-ipc multiple \\\n+\t\t--threads=7 \\\n+\t\t--bytecount=19 \\\n+\t\t--batchsize=13 \\\n+\t\t>actual &&\n+\ttest_line_count = 92 actual &&\n+\tgrep \"good 91\" actual &&\n+\tgrep \"sent:A\" <actual >actual_a &&\n+\tcat >expect_a <<-EOF &&\n+\t\tsent:A00000019 rcvd:A00000019\n+\t\tsent:A00000020 rcvd:A00000020\n+\t\tsent:A00000021 rcvd:A00000021\n+\t\tsent:A00000022 rcvd:A00000022\n+\t\tsent:A00000023 rcvd:A00000023\n+\t\tsent:A00000024 rcvd:A00000024\n+\t\tsent:A00000025 rcvd:A00000025\n+\t\tsent:A00000026 rcvd:A00000026\n+\t\tsent:A00000027 rcvd:A00000027\n+\t\tsent:A00000028 rcvd:A00000028\n+\t\tsent:A00000029 rcvd:A00000029\n+\t\tsent:A00000030 rcvd:A00000030\n+\t\tsent:A00000031 rcvd:A00000031\n+\tEOF\n+\ttest_cmp expect_a actual_a\n+'\n+\n+test_expect_success 'stop-daemon works' '\n+\ttest-tool simple-ipc stop-daemon &&\n+\ttest_must_fail test-tool simple-ipc is-active &&\n+\ttest_must_fail test-tool simple-ipc send --token=ping\n+'\n+\n+test_done\n-- \ngitgitgadget\n"},{"id":"419919","messageId":"fe35dc3d292d0f13802f5ec646a3d4c5071920f2.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:37Z","receivedAt":"2021-03-22T10:30:29Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nTeach `packet_write_gently()` to write the pkt-line header and the actual\nbuffer in 2 separate calls to `write_in_full()` and avoid the need for a\nstatic buffer, thread-safe scratch space, or an excessively large stack\nbuffer.\n\nChange `write_packetized_from_fd()` to allocate a temporary buffer rather\nthan using a static buffer to avoid similar issues here.\n\nThese changes are intended to make it easier to use pkt-line routines in\na multi-threaded context with multiple concurrent writers writing to\ndifferent streams.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n pkt-line.c | 28 ++++++++++++++++++++--------\n 1 file changed, 20 insertions(+), 8 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex d633005ef746..66bd0ddfd1d0 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -196,17 +196,26 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)\n \n static int packet_write_gently(const int fd_out, const char *buf, size_t size)\n {\n-\tstatic char packet_write_buffer[LARGE_PACKET_MAX];\n+\tchar header[4];\n \tsize_t packet_size;\n \n-\tif (size > sizeof(packet_write_buffer) - 4)\n+\tif (size > LARGE_PACKET_DATA_MAX)\n \t\treturn error(_(\"packet write failed - data exceeds max packet size\"));\n \n \tpacket_trace(buf, size, 1);\n \tpacket_size = size + 4;\n-\tset_packet_header(packet_write_buffer, packet_size);\n-\tmemcpy(packet_write_buffer + 4, buf, size);\n-\tif (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)\n+\n+\tset_packet_header(header, packet_size);\n+\n+\t/*\n+\t * Write the header and the buffer in 2 parts so that we do\n+\t * not need to allocate a buffer or rely on a static buffer.\n+\t * This also avoids putting a large buffer on the stack which\n+\t * might have multi-threading issues.\n+\t */\n+\n+\tif (write_in_full(fd_out, header, 4) < 0 ||\n+\t    write_in_full(fd_out, buf, size) < 0)\n \t\treturn error(_(\"packet write failed\"));\n \treturn 0;\n }\n@@ -244,20 +253,23 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \n int write_packetized_from_fd(int fd_in, int fd_out)\n {\n-\tstatic char buf[LARGE_PACKET_DATA_MAX];\n+\tchar *buf = xmalloc(LARGE_PACKET_DATA_MAX);\n \tint err = 0;\n \tssize_t bytes_to_write;\n \n \twhile (!err) {\n-\t\tbytes_to_write = xread(fd_in, buf, sizeof(buf));\n-\t\tif (bytes_to_write < 0)\n+\t\tbytes_to_write = xread(fd_in, buf, LARGE_PACKET_DATA_MAX);\n+\t\tif (bytes_to_write < 0) {\n+\t\t\tfree(buf);\n \t\t\treturn COPY_READ_ERROR;\n+\t\t}\n \t\tif (bytes_to_write == 0)\n \t\t\tbreak;\n \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n \t}\n \tif (!err)\n \t\terr = packet_flush_gently(fd_out);\n+\tfree(buf);\n \treturn err;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"419920","messageId":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v6.git.1615842509.gitgitgadget@gmail.com","subject":"[PATCH v7 00/12] Simple IPC Mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:36Z","receivedAt":"2021-03-22T10:30:30Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"Here is version V7 of my simple-ipc series. The only change from V6 is to\nsquash in the CALLOC_ARRAY() suggestion.\n\n$ git range-diff v2.31.0-rc1..pr-766/jeffhostetler/simple-ipc-v6\nv2.31.0-rc1..HEAD 1: fe35dc3d29 = 1: fe35dc3d29 pkt-line: eliminate the need\nfor static buffer in packet_write_gently() 2: de11b30361 = 2: de11b30361\npkt-line: do not issue flush packets in write_packetized_*() 3: 3718da39da =\n3: 3718da39da pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option 4:\nb43df7ad0b = 4: b43df7ad0b pkt-line: add options argument to\nread_packetized_to_strbuf() 5: f829feb2aa = 5: f829feb2aa simple-ipc: design\ndocumentation for new IPC mechanism 6: 58c3fb7cd7 = 6: 58c3fb7cd7\nsimple-ipc: add win32 implementation 7: 4e8c352fb3 = 7: 4e8c352fb3\nunix-socket: eliminate static unix_stream_socket() helper function 8:\n3b71f52d86 = 8: 3b71f52d86 unix-socket: add backlog size option to\nunix_stream_listen() 9: 5972a19836 = 9: 5972a19836 unix-socket: disallow\nchdir() when creating unix domain sockets 10: 02c885fd62 = 10: 02c885fd62\nunix-stream-server: create unix domain socket under lock 11: 4c2199231d !\n11: eee5f4796d simple-ipc: add Unix domain socket implementation @@\ncompat/simple-ipc/ipc-unix-socket.c (new) +\npthread_cond_init(&server_data->work_available_cond, NULL); + +\nserver_data->queue_size = nr_threads * FIFO_SCALE; -+ server_data->fifo_fds\n= xcalloc(server_data->queue_size, -+ sizeof(*server_data->fifo_fds)); ++\nCALLOC_ARRAY(server_data->fifo_fds, server_data->queue_size); + +\nserver_data->accept_thread = + xcalloc(1,\nsizeof(*server_data->accept_thread)); 12: 132b6f3271 = 12: 8b5dcca684 t0052:\nadd simple-ipc tests and t/helper/test-simple-ipc tool\n\nJeff\n\nJeff Hostetler (9):\n  pkt-line: eliminate the need for static buffer in\n    packet_write_gently()\n  simple-ipc: design documentation for new IPC mechanism\n  simple-ipc: add win32 implementation\n  unix-socket: eliminate static unix_stream_socket() helper function\n  unix-socket: add backlog size option to unix_stream_listen()\n  unix-socket: disallow chdir() when creating unix domain sockets\n  unix-stream-server: create unix domain socket under lock\n  simple-ipc: add Unix domain socket implementation\n  t0052: add simple-ipc tests and t/helper/test-simple-ipc tool\n\nJohannes Schindelin (3):\n  pkt-line: do not issue flush packets in write_packetized_*()\n  pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option\n  pkt-line: add options argument to read_packetized_to_strbuf()\n\n Documentation/technical/api-simple-ipc.txt | 105 +++\n Makefile                                   |   9 +\n builtin/credential-cache--daemon.c         |   3 +-\n builtin/credential-cache.c                 |   2 +-\n compat/simple-ipc/ipc-shared.c             |  28 +\n compat/simple-ipc/ipc-unix-socket.c        | 999 +++++++++++++++++++++\n compat/simple-ipc/ipc-win32.c              | 751 ++++++++++++++++\n config.mak.uname                           |   2 +\n contrib/buildsystems/CMakeLists.txt        |   8 +-\n convert.c                                  |  11 +-\n pkt-line.c                                 |  59 +-\n pkt-line.h                                 |  17 +-\n simple-ipc.h                               | 239 +++++\n t/helper/test-simple-ipc.c                 | 787 ++++++++++++++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t0052-simple-ipc.sh                      | 122 +++\n unix-socket.c                              |  53 +-\n unix-socket.h                              |  12 +-\n unix-stream-server.c                       | 125 +++\n unix-stream-server.h                       |  33 +\n 21 files changed, 3315 insertions(+), 52 deletions(-)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n create mode 100644 unix-stream-server.c\n create mode 100644 unix-stream-server.h\n\n\nbase-commit: f01623b2c9d14207e497b21ebc6b3ec4afaf4b46\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v7\nPull-Request: https://github.com/gitgitgadget/git/pull/766\n\nRange-diff vs v6:\n\n  1:  fe35dc3d292d =  1:  fe35dc3d292d pkt-line: eliminate the need for static buffer in packet_write_gently()\n  2:  de11b3036148 =  2:  de11b3036148 pkt-line: do not issue flush packets in write_packetized_*()\n  3:  3718da39da30 =  3:  3718da39da30 pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option\n  4:  b43df7ad0b7a =  4:  b43df7ad0b7a pkt-line: add options argument to read_packetized_to_strbuf()\n  5:  f829feb2aa93 =  5:  f829feb2aa93 simple-ipc: design documentation for new IPC mechanism\n  6:  58c3fb7cd776 =  6:  58c3fb7cd776 simple-ipc: add win32 implementation\n  7:  4e8c352fb366 =  7:  4e8c352fb366 unix-socket: eliminate static unix_stream_socket() helper function\n  8:  3b71f52d8628 =  8:  3b71f52d8628 unix-socket: add backlog size option to unix_stream_listen()\n  9:  5972a198361c =  9:  5972a198361c unix-socket: disallow chdir() when creating unix domain sockets\n 10:  02c885fd623d = 10:  02c885fd623d unix-stream-server: create unix domain socket under lock\n 11:  4c2199231d05 ! 11:  eee5f4796d37 simple-ipc: add Unix domain socket implementation\n     @@ compat/simple-ipc/ipc-unix-socket.c (new)\n      +\tpthread_cond_init(&server_data->work_available_cond, NULL);\n      +\n      +\tserver_data->queue_size = nr_threads * FIFO_SCALE;\n     -+\tserver_data->fifo_fds = xcalloc(server_data->queue_size,\n     -+\t\t\t\t\tsizeof(*server_data->fifo_fds));\n     ++\tCALLOC_ARRAY(server_data->fifo_fds, server_data->queue_size);\n      +\n      +\tserver_data->accept_thread =\n      +\t\txcalloc(1, sizeof(*server_data->accept_thread));\n 12:  132b6f3271be = 12:  8b5dcca68440 t0052: add simple-ipc tests and t/helper/test-simple-ipc tool\n\n-- \ngitgitgadget\n"},{"id":"419921","messageId":"3718da39da30ffc283e74eb94c942d0110eb9676.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 03/12] pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:39Z","receivedAt":"2021-03-22T10:31:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIntroduce PACKET_READ_GENTLE_ON_READ_ERROR option to help libify the\npacket readers.\n\nSo far, the (possibly indirect) callers of `get_packet_data()` can ask\nthat function to return an error instead of `die()`ing upon end-of-file.\nHowever, random read errors will still cause the process to die.\n\nSo let's introduce an explicit option to tell the packet reader\nmachinery to please be nice and only return an error on read errors.\n\nThis change prepares pkt-line for use by long-running daemon processes.\nSuch processes should be able to serve multiple concurrent clients and\nand survive random IO errors.  If there is an error on one connection,\na daemon should be able to drop that connection and continue serving\nexisting and future connections.\n\nThis ability will be used by a Git-aware \"Builtin FSMonitor\" feature\nin a later patch series.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n pkt-line.c | 19 +++++++++++++++++--\n pkt-line.h | 11 ++++++++---\n 2 files changed, 25 insertions(+), 5 deletions(-)\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex bb0fb0c3802c..457ac4e151bb 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -306,8 +306,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\t*src_size -= ret;\n \t} else {\n \t\tret = read_in_full(fd, dst, size);\n-\t\tif (ret < 0)\n+\t\tif (ret < 0) {\n+\t\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\t\treturn error_errno(_(\"read error\"));\n \t\t\tdie_errno(_(\"read error\"));\n+\t\t}\n \t}\n \n \t/* And complain if we didn't get enough bytes to satisfy the read. */\n@@ -315,6 +318,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n \t\t\treturn -1;\n \n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"the remote end hung up unexpectedly\"));\n \t\tdie(_(\"the remote end hung up unexpectedly\"));\n \t}\n \n@@ -343,6 +348,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \tlen = packet_length(linelen);\n \n \tif (len < 0) {\n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"protocol error: bad line length \"\n+\t\t\t\t       \"character: %.4s\"), linelen);\n \t\tdie(_(\"protocol error: bad line length character: %.4s\"), linelen);\n \t} else if (!len) {\n \t\tpacket_trace(\"0000\", 4, 0);\n@@ -357,12 +365,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,\n \t\t*pktlen = 0;\n \t\treturn PACKET_READ_RESPONSE_END;\n \t} else if (len < 4) {\n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n \t}\n \n \tlen -= 4;\n-\tif ((unsigned)len >= size)\n+\tif ((unsigned)len >= size) {\n+\t\tif (options & PACKET_READ_GENTLE_ON_READ_ERROR)\n+\t\t\treturn error(_(\"protocol error: bad line length %d\"),\n+\t\t\t\t     len);\n \t\tdie(_(\"protocol error: bad line length %d\"), len);\n+\t}\n \n \tif (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) {\n \t\t*pktlen = -1;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 31012b9943bf..80ce0187e2ea 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -68,10 +68,15 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou\n  *\n  * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an\n  * ERR packet.\n+ *\n+ * If options contains PACKET_READ_GENTLE_ON_READ_ERROR, we will not die\n+ * on read errors, but instead return -1.  However, we may still die on an\n+ * ERR packet (if requested).\n  */\n-#define PACKET_READ_GENTLE_ON_EOF     (1u<<0)\n-#define PACKET_READ_CHOMP_NEWLINE     (1u<<1)\n-#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)\n+#define PACKET_READ_GENTLE_ON_EOF        (1u<<0)\n+#define PACKET_READ_CHOMP_NEWLINE        (1u<<1)\n+#define PACKET_READ_DIE_ON_ERR_PACKET    (1u<<2)\n+#define PACKET_READ_GENTLE_ON_READ_ERROR (1u<<3)\n int packet_read(int fd, char **src_buffer, size_t *src_len, char\n \t\t*buffer, unsigned size, int options);\n \n-- \ngitgitgadget\n\n"},{"id":"419922","messageId":"de11b3036148104308b22a1af39fbdaa5f54b296.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 02/12] pkt-line: do not issue flush packets in write_packetized_*()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:38Z","receivedAt":"2021-03-22T10:31:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nRemove the `packet_flush_gently()` call in `write_packetized_from_buf() and\n`write_packetized_from_fd()` and require the caller to call it if desired.\nRename both functions to `write_packetized_from_*_no_flush()` to prevent\nlater merge accidents.\n\n`write_packetized_from_buf()` currently only has one caller:\n`apply_multi_file_filter()` in `convert.c`.  It always wants a flush packet\nto be written after writing the payload.\n\nHowever, we are about to introduce a caller that wants to write many\npackets before a final flush packet, so let's make the caller responsible\nfor emitting the flush packet.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n convert.c  | 8 ++++++--\n pkt-line.c | 8 ++------\n pkt-line.h | 4 ++--\n 3 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex ee360c2f07ce..976d4905cb3a 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -884,9 +884,13 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \t\tgoto done;\n \n \tif (fd >= 0)\n-\t\terr = write_packetized_from_fd(fd, process->in);\n+\t\terr = write_packetized_from_fd_no_flush(fd, process->in);\n \telse\n-\t\terr = write_packetized_from_buf(src, len, process->in);\n+\t\terr = write_packetized_from_buf_no_flush(src, len, process->in);\n+\tif (err)\n+\t\tgoto done;\n+\n+\terr = packet_flush_gently(process->in);\n \tif (err)\n \t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 66bd0ddfd1d0..bb0fb0c3802c 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -251,7 +251,7 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)\n \tpacket_trace(data, len, 1);\n }\n \n-int write_packetized_from_fd(int fd_in, int fd_out)\n+int write_packetized_from_fd_no_flush(int fd_in, int fd_out)\n {\n \tchar *buf = xmalloc(LARGE_PACKET_DATA_MAX);\n \tint err = 0;\n@@ -267,13 +267,11 @@ int write_packetized_from_fd(int fd_in, int fd_out)\n \t\t\tbreak;\n \t\terr = packet_write_gently(fd_out, buf, bytes_to_write);\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \tfree(buf);\n \treturn err;\n }\n \n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out)\n {\n \tint err = 0;\n \tsize_t bytes_written = 0;\n@@ -289,8 +287,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)\n \t\terr = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);\n \t\tbytes_written += bytes_to_write;\n \t}\n-\tif (!err)\n-\t\terr = packet_flush_gently(fd_out);\n \treturn err;\n }\n \ndiff --git a/pkt-line.h b/pkt-line.h\nindex 8c90daa59ef0..31012b9943bf 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -32,8 +32,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f\n void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);\n int packet_flush_gently(int fd);\n int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));\n-int write_packetized_from_fd(int fd_in, int fd_out);\n-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);\n+int write_packetized_from_fd_no_flush(int fd_in, int fd_out);\n+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);\n \n /*\n  * Read a packetized line into the buffer, which must be at least size bytes\n-- \ngitgitgadget\n\n"},{"id":"419923","messageId":"f829feb2aa93937b2e2fc493e8ea647051960658.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 05/12] simple-ipc: design documentation for new IPC mechanism","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:41Z","receivedAt":"2021-03-22T10:31:02Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nBrief design documentation for new IPC mechanism allowing\nforeground Git client to talk with an existing daemon process\nat a known location using a named pipe or unix domain socket.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-simple-ipc.txt | 105 +++++++++++++++++++++\n 1 file changed, 105 insertions(+)\n create mode 100644 Documentation/technical/api-simple-ipc.txt\n\ndiff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt\nnew file mode 100644\nindex 000000000000..d79ad323e675\n--- /dev/null\n+++ b/Documentation/technical/api-simple-ipc.txt\n@@ -0,0 +1,105 @@\n+Simple-IPC API\n+==============\n+\n+The Simple-IPC API is a collection of `ipc_` prefixed library routines\n+and a basic communication protocol that allow an IPC-client process to\n+send an application-specific IPC-request message to an IPC-server\n+process and receive an application-specific IPC-response message.\n+\n+Communication occurs over a named pipe on Windows and a Unix domain\n+socket on other platforms.  IPC-clients and IPC-servers rendezvous at\n+a previously agreed-to application-specific pathname (which is outside\n+the scope of this design) that is local to the computer system.\n+\n+The IPC-server routines within the server application process create a\n+thread pool to listen for connections and receive request messages\n+from multiple concurrent IPC-clients.  When received, these messages\n+are dispatched up to the server application callbacks for handling.\n+IPC-server routines then incrementally relay responses back to the\n+IPC-client.\n+\n+The IPC-client routines within a client application process connect\n+to the IPC-server and send a request message and wait for a response.\n+When received, the response is returned back the caller.\n+\n+For example, the `fsmonitor--daemon` feature will be built as a server\n+application on top of the IPC-server library routines.  It will have\n+threads watching for file system events and a thread pool waiting for\n+client connections.  Clients, such as `git status` will request a list\n+of file system events since a point in time and the server will\n+respond with a list of changed files and directories.  The formats of\n+the request and response are application-specific; the IPC-client and\n+IPC-server routines treat them as opaque byte streams.\n+\n+\n+Comparison with sub-process model\n+---------------------------------\n+\n+The Simple-IPC mechanism differs from the existing `sub-process.c`\n+model (Documentation/technical/long-running-process-protocol.txt) and\n+used by applications like Git-LFS.  In the LFS-style sub-process model\n+the helper is started by the foreground process, communication happens\n+via a pair of file descriptors bound to the stdin/stdout of the\n+sub-process, the sub-process only serves the current foreground\n+process, and the sub-process exits when the foreground process\n+terminates.\n+\n+In the Simple-IPC model the server is a very long-running service.  It\n+can service many clients at the same time and has a private socket or\n+named pipe connection to each active client.  It might be started\n+(on-demand) by the current client process or it might have been\n+started by a previous client or by the OS at boot time.  The server\n+process is not associated with a terminal and it persists after\n+clients terminate.  Clients do not have access to the stdin/stdout of\n+the server process and therefore must communicate over sockets or\n+named pipes.\n+\n+\n+Server startup and shutdown\n+---------------------------\n+\n+How an application server based upon IPC-server is started is also\n+outside the scope of the Simple-IPC design and is a property of the\n+application using it.  For example, the server might be started or\n+restarted during routine maintenance operations, or it might be\n+started as a system service during the system boot-up sequence, or it\n+might be started on-demand by a foreground Git command when needed.\n+\n+Similarly, server shutdown is a property of the application using\n+the simple-ipc routines.  For example, the server might decide to\n+shutdown when idle or only upon explicit request.\n+\n+\n+Simple-IPC protocol\n+-------------------\n+\n+The Simple-IPC protocol consists of a single request message from the\n+client and an optional response message from the server.  Both the\n+client and server messages are unlimited in length and are terminated\n+with a flush packet.\n+\n+The pkt-line routines (Documentation/technical/protocol-common.txt)\n+are used to simplify buffer management during message generation,\n+transmission, and reception.  A flush packet is used to mark the end\n+of the message.  This allows the sender to incrementally generate and\n+transmit the message.  It allows the receiver to incrementally receive\n+the message in chunks and to know when they have received the entire\n+message.\n+\n+The actual byte format of the client request and server response\n+messages are application specific.  The IPC layer transmits and\n+receives them as opaque byte buffers without any concern for the\n+content within.  It is the job of the calling application layer to\n+understand the contents of the request and response messages.\n+\n+\n+Summary\n+-------\n+\n+Conceptually, the Simple-IPC protocol is similar to an HTTP REST\n+request.  Clients connect, make an application-specific and\n+stateless request, receive an application-specific\n+response, and disconnect.  It is a one round trip facility for\n+querying the server.  The Simple-IPC routines hide the socket,\n+named pipe, and thread pool details and allow the application\n+layer to focus on the application at hand.\n-- \ngitgitgadget\n\n"},{"id":"419924","messageId":"b43df7ad0b7a8afb686baf166a118432305154ba.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 04/12] pkt-line: add options argument to read_packetized_to_strbuf()","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:40Z","receivedAt":"2021-03-22T10:31:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nUpdate the calling sequence of `read_packetized_to_strbuf()` to take\nan options argument and not assume a fixed set of options.  Update the\nonly existing caller accordingly to explicitly pass the\nformerly-assumed flags.\n\nThe `read_packetized_to_strbuf()` function calls `packet_read()` with\na fixed set of assumed options (`PACKET_READ_GENTLE_ON_EOF`).  This\nassumption has been fine for the single existing caller\n`apply_multi_file_filter()` in `convert.c`.\n\nIn a later commit we would like to add other callers to\n`read_packetized_to_strbuf()` that need a different set of options.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n convert.c  | 3 ++-\n pkt-line.c | 4 ++--\n pkt-line.h | 2 +-\n 3 files changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/convert.c b/convert.c\nindex 976d4905cb3a..516f1095b06e 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -907,7 +907,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len\n \t\tif (err)\n \t\t\tgoto done;\n \n-\t\terr = read_packetized_to_strbuf(process->out, &nbuf) < 0;\n+\t\terr = read_packetized_to_strbuf(process->out, &nbuf,\n+\t\t\t\t\t\tPACKET_READ_GENTLE_ON_EOF) < 0;\n \t\tif (err)\n \t\t\tgoto done;\n \ndiff --git a/pkt-line.c b/pkt-line.c\nindex 457ac4e151bb..0194137528c3 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -444,7 +444,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)\n \treturn packet_read_line_generic(-1, src, src_len, dst_len);\n }\n \n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options)\n {\n \tint packet_len;\n \n@@ -460,7 +460,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)\n \t\t\t * that there is already room for the extra byte.\n \t\t\t */\n \t\t\tsb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,\n-\t\t\tPACKET_READ_GENTLE_ON_EOF);\n+\t\t\toptions);\n \t\tif (packet_len <= 0)\n \t\t\tbreak;\n \t\tsb_out->len += packet_len;\ndiff --git a/pkt-line.h b/pkt-line.h\nindex 80ce0187e2ea..5af5f4568768 100644\n--- a/pkt-line.h\n+++ b/pkt-line.h\n@@ -136,7 +136,7 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);\n /*\n  * Reads a stream of variable sized packets until a flush packet is detected.\n  */\n-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);\n+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options);\n \n /*\n  * Receive multiplexed output stream over git native protocol.\n-- \ngitgitgadget\n\n"},{"id":"419926","messageId":"3b71f52d862832f3eb65d4041baac4c3a9f3e153.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 08/12] unix-socket: add backlog size option to unix_stream_listen()","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:44Z","receivedAt":"2021-03-22T10:31:02Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nUpdate `unix_stream_listen()` to take an options structure to override\ndefault behaviors.  This commit includes the size of the `listen()` backlog.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache--daemon.c |  3 ++-\n unix-socket.c                      | 11 +++++++++--\n unix-socket.h                      |  9 ++++++++-\n 3 files changed, 19 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c\nindex c61f123a3b81..4c6c89ab0de2 100644\n--- a/builtin/credential-cache--daemon.c\n+++ b/builtin/credential-cache--daemon.c\n@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)\n \n static void serve_cache(const char *socket_path, int debug)\n {\n+\tstruct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n \tint fd;\n \n-\tfd = unix_stream_listen(socket_path);\n+\tfd = unix_stream_listen(socket_path, &opts);\n \tif (fd < 0)\n \t\tdie_errno(\"unable to bind to '%s'\", socket_path);\n \ndiff --git a/unix-socket.c b/unix-socket.c\nindex 69f81d64e9d5..012becd93d57 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,6 +1,8 @@\n #include \"cache.h\"\n #include \"unix-socket.h\"\n \n+#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)\n+\n static int chdir_len(const char *orig, int len)\n {\n \tchar *path = xmemdupz(orig, len);\n@@ -89,9 +91,11 @@ int unix_stream_connect(const char *path)\n \treturn -1;\n }\n \n-int unix_stream_listen(const char *path)\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts)\n {\n \tint fd = -1, saved_errno;\n+\tint backlog;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -106,7 +110,10 @@ int unix_stream_listen(const char *path)\n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \n-\tif (listen(fd, 5) < 0)\n+\tbacklog = opts->listen_backlog_size;\n+\tif (backlog <= 0)\n+\t\tbacklog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;\n+\tif (listen(fd, backlog) < 0)\n \t\tgoto fail;\n \n \tunix_sockaddr_cleanup(&ctx);\ndiff --git a/unix-socket.h b/unix-socket.h\nindex e271aeec5a07..ec2fb3ea7267 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -1,7 +1,14 @@\n #ifndef UNIX_SOCKET_H\n #define UNIX_SOCKET_H\n \n+struct unix_stream_listen_opts {\n+\tint listen_backlog_size;\n+};\n+\n+#define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }\n+\n int unix_stream_connect(const char *path);\n-int unix_stream_listen(const char *path);\n+int unix_stream_listen(const char *path,\n+\t\t       const struct unix_stream_listen_opts *opts);\n \n #endif /* UNIX_SOCKET_H */\n-- \ngitgitgadget\n\n"},{"id":"419927","messageId":"5972a198361c153e000a9d11c05bec480cb9f4ce.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 09/12] unix-socket: disallow chdir() when creating unix domain sockets","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:45Z","receivedAt":"2021-03-22T10:31:02Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCalls to `chdir()` are dangerous in a multi-threaded context.  If\n`unix_stream_listen()` or `unix_stream_connect()` is given a socket\npathname that is too long to fit in a `sockaddr_un` structure, it will\n`chdir()` to the parent directory of the requested socket pathname,\ncreate the socket using a relative pathname, and then `chdir()` back.\nThis is not thread-safe.\n\nTeach `unix_sockaddr_init()` to not allow calls to `chdir()` when this\nflag is set.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/credential-cache.c |  2 +-\n unix-socket.c              | 17 ++++++++++++-----\n unix-socket.h              |  3 ++-\n 3 files changed, 15 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/credential-cache.c b/builtin/credential-cache.c\nindex 9b3f70990597..76a6ba37223f 100644\n--- a/builtin/credential-cache.c\n+++ b/builtin/credential-cache.c\n@@ -14,7 +14,7 @@\n static int send_request(const char *socket, const struct strbuf *out)\n {\n \tint got_data = 0;\n-\tint fd = unix_stream_connect(socket);\n+\tint fd = unix_stream_connect(socket, 0);\n \n \tif (fd < 0)\n \t\treturn -1;\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 012becd93d57..e0be1badb58d 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -30,16 +30,23 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)\n }\n \n static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n-\t\t\t      struct unix_sockaddr_context *ctx)\n+\t\t\t      struct unix_sockaddr_context *ctx,\n+\t\t\t      int disallow_chdir)\n {\n \tint size = strlen(path) + 1;\n \n \tctx->orig_dir = NULL;\n \tif (size > sizeof(sa->sun_path)) {\n-\t\tconst char *slash = find_last_dir_sep(path);\n+\t\tconst char *slash;\n \t\tconst char *dir;\n \t\tstruct strbuf cwd = STRBUF_INIT;\n \n+\t\tif (disallow_chdir) {\n+\t\t\terrno = ENAMETOOLONG;\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tslash = find_last_dir_sep(path);\n \t\tif (!slash) {\n \t\t\terrno = ENAMETOOLONG;\n \t\t\treturn -1;\n@@ -65,13 +72,13 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \treturn 0;\n }\n \n-int unix_stream_connect(const char *path)\n+int unix_stream_connect(const char *path, int disallow_chdir)\n {\n \tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\tif (unix_sockaddr_init(&sa, path, &ctx, disallow_chdir) < 0)\n \t\treturn -1;\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\n@@ -101,7 +108,7 @@ int unix_stream_listen(const char *path,\n \n \tunlink(path);\n \n-\tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n+\tif (unix_sockaddr_init(&sa, path, &ctx, opts->disallow_chdir) < 0)\n \t\treturn -1;\n \tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n \tif (fd < 0)\ndiff --git a/unix-socket.h b/unix-socket.h\nindex ec2fb3ea7267..8542cdd7995d 100644\n--- a/unix-socket.h\n+++ b/unix-socket.h\n@@ -3,11 +3,12 @@\n \n struct unix_stream_listen_opts {\n \tint listen_backlog_size;\n+\tunsigned int disallow_chdir:1;\n };\n \n #define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }\n \n-int unix_stream_connect(const char *path);\n+int unix_stream_connect(const char *path, int disallow_chdir);\n int unix_stream_listen(const char *path,\n \t\t       const struct unix_stream_listen_opts *opts);\n \n-- \ngitgitgadget\n\n"},{"id":"419925","messageId":"4e8c352fb366471c02d1cdf605d37e017eb3f507.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 07/12] unix-socket: eliminate static unix_stream_socket() helper function","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:43Z","receivedAt":"2021-03-22T10:31:03Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nThe static helper function `unix_stream_socket()` calls `die()`.  This\nis not appropriate for all callers.  Eliminate the wrapper function\nand make the callers propagate the error.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n unix-socket.c | 27 +++++++++++++--------------\n 1 file changed, 13 insertions(+), 14 deletions(-)\n\ndiff --git a/unix-socket.c b/unix-socket.c\nindex 19ed48be9902..69f81d64e9d5 100644\n--- a/unix-socket.c\n+++ b/unix-socket.c\n@@ -1,14 +1,6 @@\n #include \"cache.h\"\n #include \"unix-socket.h\"\n \n-static int unix_stream_socket(void)\n-{\n-\tint fd = socket(AF_UNIX, SOCK_STREAM, 0);\n-\tif (fd < 0)\n-\t\tdie_errno(\"unable to create socket\");\n-\treturn fd;\n-}\n-\n static int chdir_len(const char *orig, int len)\n {\n \tchar *path = xmemdupz(orig, len);\n@@ -73,13 +65,16 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,\n \n int unix_stream_connect(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n+\n \tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n \tunix_sockaddr_cleanup(&ctx);\n@@ -87,15 +82,16 @@ int unix_stream_connect(const char *path)\n \n fail:\n \tsaved_errno = errno;\n+\tif (fd != -1)\n+\t\tclose(fd);\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n \n int unix_stream_listen(const char *path)\n {\n-\tint fd, saved_errno;\n+\tint fd = -1, saved_errno;\n \tstruct sockaddr_un sa;\n \tstruct unix_sockaddr_context ctx;\n \n@@ -103,7 +99,9 @@ int unix_stream_listen(const char *path)\n \n \tif (unix_sockaddr_init(&sa, path, &ctx) < 0)\n \t\treturn -1;\n-\tfd = unix_stream_socket();\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tgoto fail;\n \n \tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)\n \t\tgoto fail;\n@@ -116,8 +114,9 @@ int unix_stream_listen(const char *path)\n \n fail:\n \tsaved_errno = errno;\n+\tif (fd != -1)\n+\t\tclose(fd);\n \tunix_sockaddr_cleanup(&ctx);\n-\tclose(fd);\n \terrno = saved_errno;\n \treturn -1;\n }\n-- \ngitgitgadget\n\n"},{"id":"419928","messageId":"02c885fd623df3551c46aa270c23f87e7ef79af2.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 10/12] unix-stream-server: create unix domain socket under lock","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:46Z","receivedAt":"2021-03-22T10:31:03Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a wrapper class for `unix_stream_listen()` that uses a \".lock\"\nlockfile to create the unix domain socket in a race-free manner.\n\nUnix domain sockets have a fundamental problem on Unix systems because\nthey persist in the filesystem until they are deleted.  This is\nindependent of whether a server is actually listening for connections.\nWell-behaved servers are expected to delete the socket when they\nshutdown.  A new server cannot easily tell if a found socket is\nattached to an active server or is leftover cruft from a dead server.\nThe traditional solution used by `unix_stream_listen()` is to force\ndelete the socket pathname and then create a new socket.  This solves\nthe latter (cruft) problem, but in the case of the former, it orphans\nthe existing server (by stealing the pathname associated with the\nsocket it is listening on).\n\nWe cannot directly use a .lock lockfile to create the socket because\nthe socket is created by `bind(2)` rather than the `open(2)` mechanism\nused by `tempfile.c`.\n\nAs an alternative, we hold a plain lockfile (\"<path>.lock\") as a\nmutual exclusion device.  Under the lock, we test if an existing\nsocket (\"<path>\") is has an active server.  If not, we create a new\nsocket and begin listening.  Then we use \"rollback\" to delete the\nlockfile in all cases.\n\nThis wrapper code conceptually exists at a higher-level than the core\nunix_stream_connect() and unix_stream_listen() routines that it\nconsumes.  It is isolated in a wrapper class for clarity.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   1 +\n contrib/buildsystems/CMakeLists.txt |   2 +-\n unix-stream-server.c                | 125 ++++++++++++++++++++++++++++\n unix-stream-server.h                |  33 ++++++++\n 4 files changed, 160 insertions(+), 1 deletion(-)\n create mode 100644 unix-stream-server.c\n create mode 100644 unix-stream-server.h\n\ndiff --git a/Makefile b/Makefile\nindex d3c42d3f4f9f..012694276f6d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1665,6 +1665,7 @@ ifdef NO_UNIX_SOCKETS\n \tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n else\n \tLIB_OBJS += unix-socket.o\n+\tLIB_OBJS += unix-stream-server.o\n endif\n \n ifdef USE_WIN32_IPC\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 40c9e8e3bd9d..c94011269ebb 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -243,7 +243,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \n elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n \tadd_compile_definitions(PROCFS_EXECUTABLE_PATH=\"/proc/self/exe\" HAVE_DEV_TTY )\n-\tlist(APPEND compat_SOURCES unix-socket.c)\n+\tlist(APPEND compat_SOURCES unix-socket.c unix-stream-server.c)\n endif()\n \n if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\ndiff --git a/unix-stream-server.c b/unix-stream-server.c\nnew file mode 100644\nindex 000000000000..efa2a207abcd\n--- /dev/null\n+++ b/unix-stream-server.c\n@@ -0,0 +1,125 @@\n+#include \"cache.h\"\n+#include \"lockfile.h\"\n+#include \"unix-socket.h\"\n+#include \"unix-stream-server.h\"\n+\n+#define DEFAULT_LOCK_TIMEOUT (100)\n+\n+/*\n+ * Try to connect to a unix domain socket at `path` (if it exists) and\n+ * see if there is a server listening.\n+ *\n+ * We don't know if the socket exists, whether a server died and\n+ * failed to cleanup, or whether we have a live server listening, so\n+ * we \"poke\" it.\n+ *\n+ * We immediately hangup without sending/receiving any data because we\n+ * don't know anything about the protocol spoken and don't want to\n+ * block while writing/reading data.  It is sufficient to just know\n+ * that someone is listening.\n+ */\n+static int is_another_server_alive(const char *path,\n+\t\t\t\t   const struct unix_stream_listen_opts *opts)\n+{\n+\tint fd = unix_stream_connect(path, opts->disallow_chdir);\n+\tif (fd >= 0) {\n+\t\tclose(fd);\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int unix_ss_create(const char *path,\n+\t\t   const struct unix_stream_listen_opts *opts,\n+\t\t   long timeout_ms,\n+\t\t   struct unix_ss_socket **new_server_socket)\n+{\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint fd_socket;\n+\tstruct unix_ss_socket *server_socket;\n+\n+\t*new_server_socket = NULL;\n+\n+\tif (timeout_ms < 0)\n+\t\ttimeout_ms = DEFAULT_LOCK_TIMEOUT;\n+\n+\t/*\n+\t * Create a lock at \"<path>.lock\" if we can.\n+\t */\n+\tif (hold_lock_file_for_update_timeout(&lock, path, 0, timeout_ms) < 0)\n+\t\treturn -1;\n+\n+\t/*\n+\t * If another server is listening on \"<path>\" give up.  We do not\n+\t * want to create a socket and steal future connections from them.\n+\t */\n+\tif (is_another_server_alive(path, opts)) {\n+\t\trollback_lock_file(&lock);\n+\t\terrno = EADDRINUSE;\n+\t\treturn -2;\n+\t}\n+\n+\t/*\n+\t * Create and bind to a Unix domain socket at \"<path>\".\n+\t */\n+\tfd_socket = unix_stream_listen(path, opts);\n+\tif (fd_socket < 0) {\n+\t\tint saved_errno = errno;\n+\t\trollback_lock_file(&lock);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tserver_socket = xcalloc(1, sizeof(*server_socket));\n+\tserver_socket->path_socket = strdup(path);\n+\tserver_socket->fd_socket = fd_socket;\n+\tlstat(path, &server_socket->st_socket);\n+\n+\t*new_server_socket = server_socket;\n+\n+\t/*\n+\t * Always rollback (just delete) \"<path>.lock\" because we already created\n+\t * \"<path>\" as a socket and do not want to commit_lock to do the atomic\n+\t * rename trick.\n+\t */\n+\trollback_lock_file(&lock);\n+\n+\treturn 0;\n+}\n+\n+void unix_ss_free(struct unix_ss_socket *server_socket)\n+{\n+\tif (!server_socket)\n+\t\treturn;\n+\n+\tif (server_socket->fd_socket >= 0) {\n+\t\tif (!unix_ss_was_stolen(server_socket))\n+\t\t\tunlink(server_socket->path_socket);\n+\t\tclose(server_socket->fd_socket);\n+\t}\n+\n+\tfree(server_socket->path_socket);\n+\tfree(server_socket);\n+}\n+\n+int unix_ss_was_stolen(struct unix_ss_socket *server_socket)\n+{\n+\tstruct stat st_now;\n+\n+\tif (!server_socket)\n+\t\treturn 0;\n+\n+\tif (lstat(server_socket->path_socket, &st_now) == -1)\n+\t\treturn 1;\n+\n+\tif (st_now.st_ino != server_socket->st_socket.st_ino)\n+\t\treturn 1;\n+\tif (st_now.st_dev != server_socket->st_socket.st_dev)\n+\t\treturn 1;\n+\n+\tif (!S_ISSOCK(st_now.st_mode))\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\ndiff --git a/unix-stream-server.h b/unix-stream-server.h\nnew file mode 100644\nindex 000000000000..ae2712ba39b1\n--- /dev/null\n+++ b/unix-stream-server.h\n@@ -0,0 +1,33 @@\n+#ifndef UNIX_STREAM_SERVER_H\n+#define UNIX_STREAM_SERVER_H\n+\n+#include \"unix-socket.h\"\n+\n+struct unix_ss_socket {\n+\tchar *path_socket;\n+\tstruct stat st_socket;\n+\tint fd_socket;\n+};\n+\n+/*\n+ * Create a Unix Domain Socket at the given path under the protection\n+ * of a '.lock' lockfile.\n+ *\n+ * Returns 0 on success, -1 on error, -2 if socket is in use.\n+ */\n+int unix_ss_create(const char *path,\n+\t\t   const struct unix_stream_listen_opts *opts,\n+\t\t   long timeout_ms,\n+\t\t   struct unix_ss_socket **server_socket);\n+\n+/*\n+ * Close and delete the socket.\n+ */\n+void unix_ss_free(struct unix_ss_socket *server_socket);\n+\n+/*\n+ * Return 1 if the inode of the pathname to our socket changes.\n+ */\n+int unix_ss_was_stolen(struct unix_ss_socket *server_socket);\n+\n+#endif /* UNIX_STREAM_SERVER_H */\n-- \ngitgitgadget\n\n"},{"id":"419929","messageId":"8b5dcca68440773e81aa0ac7b52c1dd9e6c132ad.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:48Z","receivedAt":"2021-03-22T10:31:03Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate t0052-simple-ipc.sh with unit tests for the \"simple-ipc\" mechanism.\n\nCreate t/helper/test-simple-ipc test tool to exercise the \"simple-ipc\"\nfunctions.\n\nWhen the tool is invoked with \"run-daemon\", it runs a server to listen\nfor \"simple-ipc\" connections on a test socket or named pipe and\nresponds to a set of commands to exercise/stress the communication\nsetup.\n\nWhen the tool is invoked with \"start-daemon\", it spawns a \"run-daemon\"\ncommand in the background and waits for the server to become ready\nbefore exiting.  (This helps make unit tests in t0052 more predictable\nand avoids the need for arbitrary sleeps in the test script.)\n\nThe tool also has a series of client \"send\" commands to send commands\nand data to a server instance.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                   |   1 +\n t/helper/test-simple-ipc.c | 787 +++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c       |   1 +\n t/helper/test-tool.h       |   1 +\n t/t0052-simple-ipc.sh      | 122 ++++++\n 5 files changed, 912 insertions(+)\n create mode 100644 t/helper/test-simple-ipc.c\n create mode 100755 t/t0052-simple-ipc.sh\n\ndiff --git a/Makefile b/Makefile\nindex 20dd65d19658..e556388d28d0 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -734,6 +734,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o\n TEST_BUILTINS_OBJS += test-sha1.o\n TEST_BUILTINS_OBJS += test-sha256.o\n TEST_BUILTINS_OBJS += test-sigchain.o\n+TEST_BUILTINS_OBJS += test-simple-ipc.o\n TEST_BUILTINS_OBJS += test-strcmp-offset.o\n TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\ndiff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c\nnew file mode 100644\nindex 000000000000..42040ef81b1e\n--- /dev/null\n+++ b/t/helper/test-simple-ipc.c\n@@ -0,0 +1,787 @@\n+/*\n+ * test-simple-ipc.c: verify that the Inter-Process Communication works.\n+ */\n+\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"simple-ipc.h\"\n+#include \"parse-options.h\"\n+#include \"thread-utils.h\"\n+#include \"strvec.h\"\n+\n+#ifndef SUPPORTS_SIMPLE_IPC\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tdie(\"simple IPC not available on this platform\");\n+}\n+#else\n+\n+/*\n+ * The test daemon defines an \"application callback\" that supports a\n+ * series of commands (see `test_app_cb()`).\n+ *\n+ * Unknown commands are caught here and we send an error message back\n+ * to the client process.\n+ */\n+static int app__unhandled_command(const char *command,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n+\n+\tstrbuf_addf(&buf, \"unhandled command: %s\", command);\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a single very large buffer.  This is to ensure that\n+ * long response are properly handled -- whether the chunking occurs\n+ * in the kernel or in the (probably pkt-line) layer.\n+ */\n+#define BIG_ROWS (10000)\n+static int app__big_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t    struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < BIG_ROWS; row++)\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\n+\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Reply with a series of lines.  This is to ensure that we can incrementally\n+ * compute the response and chunk it to the client.\n+ */\n+#define CHUNK_ROWS (10000)\n+static int app__chunk_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t      struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < CHUNK_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Slowly reply with a series of lines.  This is to model an expensive to\n+ * compute chunked response (which might happen if this callback is running\n+ * in a thread and is fighting for a lock with other threads).\n+ */\n+#define SLOW_ROWS     (1000)\n+#define SLOW_DELAY_MS (10)\n+static int app__slow_command(ipc_server_reply_cb *reply_cb,\n+\t\t\t     struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint row;\n+\tint ret;\n+\n+\tfor (row = 0; row < SLOW_ROWS; row++) {\n+\t\tstrbuf_setlen(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"big: %.75d\\n\", row);\n+\t\tret = reply_cb(reply_data, buf.buf, buf.len);\n+\t\tsleep_millisec(SLOW_DELAY_MS);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * The client sent a command followed by a (possibly very) large buffer.\n+ */\n+static int app__sendbytes_command(const char *received,\n+\t\t\t\t  ipc_server_reply_cb *reply_cb,\n+\t\t\t\t  struct ipc_server_reply_data *reply_data)\n+{\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\tconst char *p = \"?\";\n+\tint len_ballast = 0;\n+\tint k;\n+\tint errs = 0;\n+\tint ret;\n+\n+\tif (skip_prefix(received, \"sendbytes \", &p))\n+\t\tlen_ballast = strlen(p);\n+\n+\t/*\n+\t * Verify that the ballast is n copies of a single letter.\n+\t * And that the multi-threaded IO layer didn't cross the streams.\n+\t */\n+\tfor (k = 1; k < len_ballast; k++)\n+\t\tif (p[k] != p[0])\n+\t\t\terrs++;\n+\n+\tif (errs)\n+\t\tstrbuf_addf(&buf_resp, \"errs:%d\\n\", errs);\n+\telse\n+\t\tstrbuf_addf(&buf_resp, \"rcvd:%c%08d\\n\", p[0], len_ballast);\n+\n+\tret = reply_cb(reply_data, buf_resp.buf, buf_resp.len);\n+\n+\tstrbuf_release(&buf_resp);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * An arbitrary fixed address to verify that the application instance\n+ * data is handled properly.\n+ */\n+static int my_app_data = 42;\n+\n+static ipc_server_application_cb test_app_cb;\n+\n+/*\n+ * This is the \"application callback\" that sits on top of the\n+ * \"ipc-server\".  It completely defines the set of commands supported\n+ * by this application.\n+ */\n+static int test_app_cb(void *application_data,\n+\t\t       const char *command,\n+\t\t       ipc_server_reply_cb *reply_cb,\n+\t\t       struct ipc_server_reply_data *reply_data)\n+{\n+\t/*\n+\t * Verify that we received the application-data that we passed\n+\t * when we started the ipc-server.  (We have several layers of\n+\t * callbacks calling callbacks and it's easy to get things mixed\n+\t * up (especially when some are \"void*\").)\n+\t */\n+\tif (application_data != (void*)&my_app_data)\n+\t\tBUG(\"application_cb: application_data pointer wrong\");\n+\n+\tif (!strcmp(command, \"quit\")) {\n+\t\t/*\n+\t\t * The client sent a \"quit\" command.  This is an async\n+\t\t * request for the server to shutdown.\n+\t\t *\n+\t\t * We DO NOT send the client a response message\n+\t\t * (because we have nothing to say and the other\n+\t\t * server threads have not yet stopped).\n+\t\t *\n+\t\t * Tell the ipc-server layer to start shutting down.\n+\t\t * This includes: stop listening for new connections\n+\t\t * on the socket/pipe and telling all worker threads\n+\t\t * to finish/drain their outgoing responses to other\n+\t\t * clients.\n+\t\t *\n+\t\t * This DOES NOT force an immediate sync shutdown.\n+\t\t */\n+\t\treturn SIMPLE_IPC_QUIT;\n+\t}\n+\n+\tif (!strcmp(command, \"ping\")) {\n+\t\tconst char *answer = \"pong\";\n+\t\treturn reply_cb(reply_data, answer, strlen(answer));\n+\t}\n+\n+\tif (!strcmp(command, \"big\"))\n+\t\treturn app__big_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"chunk\"))\n+\t\treturn app__chunk_command(reply_cb, reply_data);\n+\n+\tif (!strcmp(command, \"slow\"))\n+\t\treturn app__slow_command(reply_cb, reply_data);\n+\n+\tif (starts_with(command, \"sendbytes \"))\n+\t\treturn app__sendbytes_command(command, reply_cb, reply_data);\n+\n+\treturn app__unhandled_command(command, reply_cb, reply_data);\n+}\n+\n+struct cl_args\n+{\n+\tconst char *subcommand;\n+\tconst char *path;\n+\tconst char *token;\n+\n+\tint nr_threads;\n+\tint max_wait_sec;\n+\tint bytecount;\n+\tint batchsize;\n+\n+\tchar bytevalue;\n+};\n+\n+static struct cl_args cl_args = {\n+\t.subcommand = NULL,\n+\t.path = \"ipc-test\",\n+\t.token = NULL,\n+\n+\t.nr_threads = 5,\n+\t.max_wait_sec = 60,\n+\t.bytecount = 1024,\n+\t.batchsize = 10,\n+\n+\t.bytevalue = 'x',\n+};\n+\n+/*\n+ * This process will run as a simple-ipc server and listen for IPC commands\n+ * from client processes.\n+ */\n+static int daemon__run_server(void)\n+{\n+\tint ret;\n+\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = cl_args.nr_threads,\n+\t};\n+\n+\t/*\n+\t * Synchronously run the ipc-server.  We don't need any application\n+\t * instance data, so pass an arbitrary pointer (that we'll later\n+\t * verify made the round trip).\n+\t */\n+\tret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data);\n+\tif (ret == -2)\n+\t\terror(_(\"socket/pipe already in use: '%s'\"), cl_args.path);\n+\telse if (ret == -1)\n+\t\terror_errno(_(\"could not start server on: '%s'\"), cl_args.path);\n+\n+\treturn ret;\n+}\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+/*\n+ * This is adapted from `daemonize()`.  Use `fork()` to directly create and\n+ * run the daemon in a child process.\n+ */\n+static int spawn_server(pid_t *pid)\n+{\n+\tstruct ipc_server_opts opts = {\n+\t\t.nr_threads = cl_args.nr_threads,\n+\t};\n+\n+\t*pid = fork();\n+\n+\tswitch (*pid) {\n+\tcase 0:\n+\t\tif (setsid() == -1)\n+\t\t\terror_errno(_(\"setsid failed\"));\n+\t\tclose(0);\n+\t\tclose(1);\n+\t\tclose(2);\n+\t\tsanitize_stdfds();\n+\n+\t\treturn ipc_server_run(cl_args.path, &opts, test_app_cb,\n+\t\t\t\t      (void*)&my_app_data);\n+\n+\tcase -1:\n+\t\treturn error_errno(_(\"could not spawn daemon in the background\"));\n+\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+}\n+#else\n+/*\n+ * Conceptually like `daemonize()` but different because Windows does not\n+ * have `fork(2)`.  Spawn a normal Windows child process but without the\n+ * limitations of `start_command()` and `finish_command()`.\n+ */\n+static int spawn_server(pid_t *pid)\n+{\n+\tchar test_tool_exe[MAX_PATH];\n+\tstruct strvec args = STRVEC_INIT;\n+\tint in, out;\n+\n+\tGetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);\n+\n+\tin = open(\"/dev/null\", O_RDONLY);\n+\tout = open(\"/dev/null\", O_WRONLY);\n+\n+\tstrvec_push(&args, test_tool_exe);\n+\tstrvec_push(&args, \"simple-ipc\");\n+\tstrvec_push(&args, \"run-daemon\");\n+\tstrvec_pushf(&args, \"--name=%s\", cl_args.path);\n+\tstrvec_pushf(&args, \"--threads=%d\", cl_args.nr_threads);\n+\n+\t*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);\n+\tclose(in);\n+\tclose(out);\n+\n+\tstrvec_clear(&args);\n+\n+\tif (*pid < 0)\n+\t\treturn error(_(\"could not spawn daemon in the background\"));\n+\n+\treturn 0;\n+}\n+#endif\n+\n+/*\n+ * This is adapted from `wait_or_whine()`.  Watch the child process and\n+ * let it get started and begin listening for requests on the socket\n+ * before reporting our success.\n+ */\n+static int wait_for_server_startup(pid_t pid_child)\n+{\n+\tint status;\n+\tpid_t pid_seen;\n+\tenum ipc_active_state s;\n+\ttime_t time_limit, now;\n+\n+\ttime(&time_limit);\n+\ttime_limit += cl_args.max_wait_sec;\n+\n+\tfor (;;) {\n+\t\tpid_seen = waitpid(pid_child, &status, WNOHANG);\n+\n+\t\tif (pid_seen == -1)\n+\t\t\treturn error_errno(_(\"waitpid failed\"));\n+\n+\t\telse if (pid_seen == 0) {\n+\t\t\t/*\n+\t\t\t * The child is still running (this should be\n+\t\t\t * the normal case).  Try to connect to it on\n+\t\t\t * the socket and see if it is ready for\n+\t\t\t * business.\n+\t\t\t *\n+\t\t\t * If there is another daemon already running,\n+\t\t\t * our child will fail to start (possibly\n+\t\t\t * after a timeout on the lock), but we don't\n+\t\t\t * care (who responds) if the socket is live.\n+\t\t\t */\n+\t\t\ts = ipc_get_active_state(cl_args.path);\n+\t\t\tif (s == IPC_STATE__LISTENING)\n+\t\t\t\treturn 0;\n+\n+\t\t\ttime(&now);\n+\t\t\tif (now > time_limit)\n+\t\t\t\treturn error(_(\"daemon not online yet\"));\n+\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\telse if (pid_seen == pid_child) {\n+\t\t\t/*\n+\t\t\t * The new child daemon process shutdown while\n+\t\t\t * it was starting up, so it is not listening\n+\t\t\t * on the socket.\n+\t\t\t *\n+\t\t\t * Try to ping the socket in the odd chance\n+\t\t\t * that another daemon started (or was already\n+\t\t\t * running) while our child was starting.\n+\t\t\t *\n+\t\t\t * Again, we don't care who services the socket.\n+\t\t\t */\n+\t\t\ts = ipc_get_active_state(cl_args.path);\n+\t\t\tif (s == IPC_STATE__LISTENING)\n+\t\t\t\treturn 0;\n+\n+\t\t\t/*\n+\t\t\t * We don't care about the WEXITSTATUS() nor\n+\t\t\t * any of the WIF*(status) values because\n+\t\t\t * `cmd__simple_ipc()` does the `!!result`\n+\t\t\t * trick on all function return values.\n+\t\t\t *\n+\t\t\t * So it is sufficient to just report the\n+\t\t\t * early shutdown as an error.\n+\t\t\t */\n+\t\t\treturn error(_(\"daemon failed to start\"));\n+\t\t}\n+\n+\t\telse\n+\t\t\treturn error(_(\"waitpid is confused\"));\n+\t}\n+}\n+\n+/*\n+ * This process will start a simple-ipc server in a background process and\n+ * wait for it to become ready.  This is like `daemonize()` but gives us\n+ * more control and better error reporting (and makes it easier to write\n+ * unit tests).\n+ */\n+static int daemon__start_server(void)\n+{\n+\tpid_t pid_child;\n+\tint ret;\n+\n+\t/*\n+\t * Run the actual daemon in a background process.\n+\t */\n+\tret = spawn_server(&pid_child);\n+\tif (pid_child <= 0)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Let the parent wait for the child process to get started\n+\t * and begin listening for requests on the socket.\n+\t */\n+\tret = wait_for_server_startup(pid_child);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * This process will run a quick probe to see if a simple-ipc server\n+ * is active on this path.\n+ *\n+ * Returns 0 if the server is alive.\n+ */\n+static int client__probe_server(void)\n+{\n+\tenum ipc_active_state s;\n+\n+\ts = ipc_get_active_state(cl_args.path);\n+\tswitch (s) {\n+\tcase IPC_STATE__LISTENING:\n+\t\treturn 0;\n+\n+\tcase IPC_STATE__NOT_LISTENING:\n+\t\treturn error(\"no server listening at '%s'\", cl_args.path);\n+\n+\tcase IPC_STATE__PATH_NOT_FOUND:\n+\t\treturn error(\"path not found '%s'\", cl_args.path);\n+\n+\tcase IPC_STATE__INVALID_PATH:\n+\t\treturn error(\"invalid pipe/socket name '%s'\", cl_args.path);\n+\n+\tcase IPC_STATE__OTHER_ERROR:\n+\tdefault:\n+\t\treturn error(\"other error for '%s'\", cl_args.path);\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command token to an already-running server daemon and\n+ * print the response.\n+ *\n+ * This is a simple 1 word command/token that `test_app_cb()` (in the\n+ * daemon process) will understand.\n+ */\n+static int client__send_ipc(void)\n+{\n+\tconst char *command = \"(no-command)\";\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\tif (cl_args.token && *cl_args.token)\n+\t\tcommand = cl_args.token;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (!ipc_client_send_command(cl_args.path, &options, command, &buf)) {\n+\t\tif (buf.len) {\n+\t\t\tprintf(\"%s\\n\", buf.buf);\n+\t\t\tfflush(stdout);\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"failed to send '%s' to '%s'\", command, cl_args.path);\n+}\n+\n+/*\n+ * Send an IPC command to an already-running server and ask it to\n+ * shutdown.  \"send quit\" is an async request and queues a shutdown\n+ * event in the server, so we spin and wait here for it to actually\n+ * shutdown to make the unit tests a little easier to write.\n+ */\n+static int client__stop_server(void)\n+{\n+\tint ret;\n+\ttime_t time_limit, now;\n+\tenum ipc_active_state s;\n+\n+\ttime(&time_limit);\n+\ttime_limit += cl_args.max_wait_sec;\n+\n+\tcl_args.token = \"quit\";\n+\n+\tret = client__send_ipc();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tfor (;;) {\n+\t\tsleep_millisec(100);\n+\n+\t\ts = ipc_get_active_state(cl_args.path);\n+\n+\t\tif (s != IPC_STATE__LISTENING) {\n+\t\t\t/*\n+\t\t\t * The socket/pipe is gone and/or has stopped\n+\t\t\t * responding.  Lets assume that the daemon\n+\t\t\t * process has exited too.\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\ttime(&now);\n+\t\tif (now > time_limit)\n+\t\t\treturn error(_(\"daemon has not shutdown yet\"));\n+\t}\n+}\n+\n+/*\n+ * Send an IPC command followed by ballast to confirm that a large\n+ * message can be sent and that the kernel or pkt-line layers will\n+ * properly chunk it and that the daemon receives the entire message.\n+ */\n+static int do_sendbytes(int bytecount, char byte, const char *path,\n+\t\t\tconst struct ipc_client_connect_options *options)\n+{\n+\tstruct strbuf buf_send = STRBUF_INIT;\n+\tstruct strbuf buf_resp = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_send, \"sendbytes \");\n+\tstrbuf_addchars(&buf_send, byte, bytecount);\n+\n+\tif (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {\n+\t\tstrbuf_rtrim(&buf_resp);\n+\t\tprintf(\"sent:%c%08d %s\\n\", byte, bytecount, buf_resp.buf);\n+\t\tfflush(stdout);\n+\t\tstrbuf_release(&buf_send);\n+\t\tstrbuf_release(&buf_resp);\n+\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"client failed to sendbytes(%d, '%c') to '%s'\",\n+\t\t     bytecount, byte, path);\n+}\n+\n+/*\n+ * Send an IPC command with ballast to an already-running server daemon.\n+ */\n+static int client__sendbytes(void)\n+{\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\toptions.uds_disallow_chdir = 0;\n+\n+\treturn do_sendbytes(cl_args.bytecount, cl_args.bytevalue, cl_args.path,\n+\t\t\t    &options);\n+}\n+\n+struct multiple_thread_data {\n+\tpthread_t pthread_id;\n+\tstruct multiple_thread_data *next;\n+\tconst char *path;\n+\tint bytecount;\n+\tint batchsize;\n+\tint sum_errors;\n+\tint sum_good;\n+\tchar letter;\n+};\n+\n+static void *multiple_thread_proc(void *_multiple_thread_data)\n+{\n+\tstruct multiple_thread_data *d = _multiple_thread_data;\n+\tint k;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\n+\toptions.wait_if_busy = 1;\n+\toptions.wait_if_not_found = 0;\n+\t/*\n+\t * A multi-threaded client should not be randomly calling chdir().\n+\t * The test will pass without this restriction because the test is\n+\t * not otherwise accessing the filesystem, but it makes us honest.\n+\t */\n+\toptions.uds_disallow_chdir = 1;\n+\n+\ttrace2_thread_start(\"multiple\");\n+\n+\tfor (k = 0; k < d->batchsize; k++) {\n+\t\tif (do_sendbytes(d->bytecount + k, d->letter, d->path, &options))\n+\t\t\td->sum_errors++;\n+\t\telse\n+\t\t\td->sum_good++;\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * Start a client-side thread pool.  Each thread sends a series of\n+ * IPC requests.  Each request is on a new connection to the server.\n+ */\n+static int client__multiple(void)\n+{\n+\tstruct multiple_thread_data *list = NULL;\n+\tint k;\n+\tint sum_join_errors = 0;\n+\tint sum_thread_errors = 0;\n+\tint sum_good = 0;\n+\n+\tfor (k = 0; k < cl_args.nr_threads; k++) {\n+\t\tstruct multiple_thread_data *d = xcalloc(1, sizeof(*d));\n+\t\td->next = list;\n+\t\td->path = cl_args.path;\n+\t\td->bytecount = cl_args.bytecount + cl_args.batchsize*(k/26);\n+\t\td->batchsize = cl_args.batchsize;\n+\t\td->sum_errors = 0;\n+\t\td->sum_good = 0;\n+\t\td->letter = 'A' + (k % 26);\n+\n+\t\tif (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) {\n+\t\t\twarning(\"failed to create thread[%d] skipping remainder\", k);\n+\t\t\tfree(d);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tlist = d;\n+\t}\n+\n+\twhile (list) {\n+\t\tstruct multiple_thread_data *d = list;\n+\n+\t\tif (pthread_join(d->pthread_id, NULL))\n+\t\t\tsum_join_errors++;\n+\n+\t\tsum_thread_errors += d->sum_errors;\n+\t\tsum_good += d->sum_good;\n+\n+\t\tlist = d->next;\n+\t\tfree(d);\n+\t}\n+\n+\tprintf(\"client (good %d) (join %d), (errors %d)\\n\",\n+\t       sum_good, sum_join_errors, sum_thread_errors);\n+\n+\treturn (sum_join_errors + sum_thread_errors) ? 1 : 0;\n+}\n+\n+int cmd__simple_ipc(int argc, const char **argv)\n+{\n+\tconst char * const simple_ipc_usage[] = {\n+\t\tN_(\"test-helper simple-ipc is-active    [<name>] [<options>]\"),\n+\t\tN_(\"test-helper simple-ipc run-daemon   [<name>] [<threads>]\"),\n+\t\tN_(\"test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]\"),\n+\t\tN_(\"test-helper simple-ipc stop-daemon  [<name>] [<max-wait>]\"),\n+\t\tN_(\"test-helper simple-ipc send         [<name>] [<token>]\"),\n+\t\tN_(\"test-helper simple-ipc sendbytes    [<name>] [<bytecount>] [<byte>]\"),\n+\t\tN_(\"test-helper simple-ipc multiple     [<name>] [<threads>] [<bytecount>] [<batchsize>]\"),\n+\t\tNULL\n+\t};\n+\n+\tconst char *bytevalue = NULL;\n+\n+\tstruct option options[] = {\n+#ifndef GIT_WINDOWS_NATIVE\n+\t\tOPT_STRING(0, \"name\", &cl_args.path, N_(\"name\"), N_(\"name or pathname of unix domain socket\")),\n+#else\n+\t\tOPT_STRING(0, \"name\", &cl_args.path, N_(\"name\"), N_(\"named-pipe name\")),\n+#endif\n+\t\tOPT_INTEGER(0, \"threads\", &cl_args.nr_threads, N_(\"number of threads in server thread pool\")),\n+\t\tOPT_INTEGER(0, \"max-wait\", &cl_args.max_wait_sec, N_(\"seconds to wait for daemon to start or stop\")),\n+\n+\t\tOPT_INTEGER(0, \"bytecount\", &cl_args.bytecount, N_(\"number of bytes\")),\n+\t\tOPT_INTEGER(0, \"batchsize\", &cl_args.batchsize, N_(\"number of requests per thread\")),\n+\n+\t\tOPT_STRING(0, \"byte\", &bytevalue, N_(\"byte\"), N_(\"ballast character\")),\n+\t\tOPT_STRING(0, \"token\", &cl_args.token, N_(\"token\"), N_(\"command token to send to the server\")),\n+\n+\t\tOPT_END()\n+\t};\n+\n+\tif (argc < 2)\n+\t\tusage_with_options(simple_ipc_usage, options);\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage_with_options(simple_ipc_usage, options);\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"SUPPORTS_SIMPLE_IPC\"))\n+\t\treturn 0;\n+\n+\tcl_args.subcommand = argv[1];\n+\n+\targc--;\n+\targv++;\n+\n+\targc = parse_options(argc, argv, NULL, options, simple_ipc_usage, 0);\n+\n+\tif (cl_args.nr_threads < 1)\n+\t\tcl_args.nr_threads = 1;\n+\tif (cl_args.max_wait_sec < 0)\n+\t\tcl_args.max_wait_sec = 0;\n+\tif (cl_args.bytecount < 1)\n+\t\tcl_args.bytecount = 1;\n+\tif (cl_args.batchsize < 1)\n+\t\tcl_args.batchsize = 1;\n+\n+\tif (bytevalue && *bytevalue)\n+\t\tcl_args.bytevalue = bytevalue[0];\n+\n+\t/*\n+\t * Use '!!' on all dispatch functions to map from `error()` style\n+\t * (returns -1) style to `test_must_fail` style (expects 1).  This\n+\t * makes shell error messages less confusing.\n+\t */\n+\n+\tif (!strcmp(cl_args.subcommand, \"is-active\"))\n+\t\treturn !!client__probe_server();\n+\n+\tif (!strcmp(cl_args.subcommand, \"run-daemon\"))\n+\t\treturn !!daemon__run_server();\n+\n+\tif (!strcmp(cl_args.subcommand, \"start-daemon\"))\n+\t\treturn !!daemon__start_server();\n+\n+\t/*\n+\t * Client commands follow.  Ensure a server is running before\n+\t * sending any data.  This might be overkill, but then again\n+\t * this is a test harness.\n+\t */\n+\n+\tif (!strcmp(cl_args.subcommand, \"stop-daemon\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__stop_server();\n+\t}\n+\n+\tif (!strcmp(cl_args.subcommand, \"send\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__send_ipc();\n+\t}\n+\n+\tif (!strcmp(cl_args.subcommand, \"sendbytes\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__sendbytes();\n+\t}\n+\n+\tif (!strcmp(cl_args.subcommand, \"multiple\")) {\n+\t\tif (client__probe_server())\n+\t\t\treturn 1;\n+\t\treturn !!client__multiple();\n+\t}\n+\n+\tdie(\"Unhandled subcommand: '%s'\", cl_args.subcommand);\n+}\n+#endif\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f97cd9f48a69..287aa6002307 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -65,6 +65,7 @@ static struct test_cmd cmds[] = {\n \t{ \"sha1\", cmd__sha1 },\n \t{ \"sha256\", cmd__sha256 },\n \t{ \"sigchain\", cmd__sigchain },\n+\t{ \"simple-ipc\", cmd__simple_ipc },\n \t{ \"strcmp-offset\", cmd__strcmp_offset },\n \t{ \"string-list\", cmd__string_list },\n \t{ \"submodule-config\", cmd__submodule_config },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 28072c0ad5ab..9ea4b31011dd 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -55,6 +55,7 @@ int cmd__sha1(int argc, const char **argv);\n int cmd__oid_array(int argc, const char **argv);\n int cmd__sha256(int argc, const char **argv);\n int cmd__sigchain(int argc, const char **argv);\n+int cmd__simple_ipc(int argc, const char **argv);\n int cmd__strcmp_offset(int argc, const char **argv);\n int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\ndiff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh\nnew file mode 100755\nindex 000000000000..ff98be31a51b\n--- /dev/null\n+++ b/t/t0052-simple-ipc.sh\n@@ -0,0 +1,122 @@\n+#!/bin/sh\n+\n+test_description='simple command server'\n+\n+. ./test-lib.sh\n+\n+test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {\n+\tskip_all='simple IPC not supported on this platform'\n+\ttest_done\n+}\n+\n+stop_simple_IPC_server () {\n+\ttest-tool simple-ipc stop-daemon\n+}\n+\n+test_expect_success 'start simple command server' '\n+\ttest_atexit stop_simple_IPC_server &&\n+\ttest-tool simple-ipc start-daemon --threads=8 &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'simple command server' '\n+\ttest-tool simple-ipc send --token=ping >actual &&\n+\techo pong >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'servers cannot share the same path' '\n+\ttest_must_fail test-tool simple-ipc run-daemon &&\n+\ttest-tool simple-ipc is-active\n+'\n+\n+test_expect_success 'big response' '\n+\ttest-tool simple-ipc send --token=big >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'chunk response' '\n+\ttest-tool simple-ipc send --token=chunk >actual &&\n+\ttest_line_count -ge 10000 actual &&\n+\tgrep -q \"big: [0]*9999\\$\" actual\n+'\n+\n+test_expect_success 'slow response' '\n+\ttest-tool simple-ipc send --token=slow >actual &&\n+\ttest_line_count -ge 100 actual &&\n+\tgrep -q \"big: [0]*99\\$\" actual\n+'\n+\n+# Send an IPC with n=100,000 bytes of ballast.  This should be large enough\n+# to force both the kernel and the pkt-line layer to chunk the message to the\n+# daemon and for the daemon to receive it in chunks.\n+#\n+test_expect_success 'sendbytes' '\n+\ttest-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual &&\n+\tgrep \"sent:A00100000 rcvd:A00100000\" actual\n+'\n+\n+# Start a series of <threads> client threads that each make <batchsize>\n+# IPC requests to the server.  Each (<threads> * <batchsize>) request\n+# will open a new connection to the server and randomly bind to a server\n+# thread.  Each client thread exits after completing its batch.  So the\n+# total number of live client threads will be smaller than the total.\n+# Each request will send a message containing at least <bytecount> bytes\n+# of ballast.  (Responses are small.)\n+#\n+# The purpose here is to test threading in the server and responding to\n+# many concurrent client requests (regardless of whether they come from\n+# 1 client process or many).  And to test that the server side of the\n+# named pipe/socket is stable.  (On Windows this means that the server\n+# pipe is properly recycled.)\n+#\n+# On Windows it also lets us adjust the connection timeout in the\n+# `ipc_client_send_command()`.\n+#\n+# Note it is easy to drive the system into failure by requesting an\n+# insane number of threads on client or server and/or increasing the\n+# per-thread batchsize or the per-request bytecount (ballast).\n+# On Windows these failures look like \"pipe is busy\" errors.\n+# So I've chosen fairly conservative values for now.\n+#\n+# We expect output of the form \"sent:<letter><length> ...\"\n+# With terms (7, 19, 13) we expect:\n+#   <letter> in [A-G]\n+#   <length> in [19+0 .. 19+(13-1)]\n+# and (7 * 13) successful responses.\n+#\n+test_expect_success 'stress test threads' '\n+\ttest-tool simple-ipc multiple \\\n+\t\t--threads=7 \\\n+\t\t--bytecount=19 \\\n+\t\t--batchsize=13 \\\n+\t\t>actual &&\n+\ttest_line_count = 92 actual &&\n+\tgrep \"good 91\" actual &&\n+\tgrep \"sent:A\" <actual >actual_a &&\n+\tcat >expect_a <<-EOF &&\n+\t\tsent:A00000019 rcvd:A00000019\n+\t\tsent:A00000020 rcvd:A00000020\n+\t\tsent:A00000021 rcvd:A00000021\n+\t\tsent:A00000022 rcvd:A00000022\n+\t\tsent:A00000023 rcvd:A00000023\n+\t\tsent:A00000024 rcvd:A00000024\n+\t\tsent:A00000025 rcvd:A00000025\n+\t\tsent:A00000026 rcvd:A00000026\n+\t\tsent:A00000027 rcvd:A00000027\n+\t\tsent:A00000028 rcvd:A00000028\n+\t\tsent:A00000029 rcvd:A00000029\n+\t\tsent:A00000030 rcvd:A00000030\n+\t\tsent:A00000031 rcvd:A00000031\n+\tEOF\n+\ttest_cmp expect_a actual_a\n+'\n+\n+test_expect_success 'stop-daemon works' '\n+\ttest-tool simple-ipc stop-daemon &&\n+\ttest_must_fail test-tool simple-ipc is-active &&\n+\ttest_must_fail test-tool simple-ipc send --token=ping\n+'\n+\n+test_done\n-- \ngitgitgadget\n"},{"id":"419930","messageId":"eee5f4796d3772eb7f4b40c2e7ac600814ddbb0a.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 11/12] simple-ipc: add Unix domain socket implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:47Z","receivedAt":"2021-03-22T10:31:03Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Unix domain socket based implementation of \"simple-ipc\".\n\nA set of `ipc_client` routines implement a client library to connect\nto an `ipc_server` over a Unix domain socket, send a simple request,\nand receive a single response.  Clients use blocking IO on the socket.\n\nA set of `ipc_server` routines implement a thread pool to listen for\nand concurrently service client connections.\n\nThe server creates a new Unix domain socket at a known location.  If a\nsocket already exists with that name, the server tries to determine if\nanother server is already listening on the socket or if the socket is\ndead.  If socket is busy, the server exits with an error rather than\nstealing the socket.  If the socket is dead, the server creates a new\none and starts up.\n\nIf while running, the server detects that its socket has been stolen\nby another server, it automatically exits.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   2 +\n compat/simple-ipc/ipc-unix-socket.c | 999 ++++++++++++++++++++++++++++\n contrib/buildsystems/CMakeLists.txt |   2 +\n simple-ipc.h                        |  13 +-\n 4 files changed, 1015 insertions(+), 1 deletion(-)\n create mode 100644 compat/simple-ipc/ipc-unix-socket.c\n\ndiff --git a/Makefile b/Makefile\nindex 012694276f6d..20dd65d19658 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1666,6 +1666,8 @@ ifdef NO_UNIX_SOCKETS\n else\n \tLIB_OBJS += unix-socket.o\n \tLIB_OBJS += unix-stream-server.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-unix-socket.o\n endif\n \n ifdef USE_WIN32_IPC\ndiff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c\nnew file mode 100644\nindex 000000000000..38689b278df3\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-unix-socket.c\n@@ -0,0 +1,999 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+#include \"unix-socket.h\"\n+#include \"unix-stream-server.h\"\n+\n+#ifdef NO_UNIX_SOCKETS\n+#error compat/simple-ipc/ipc-unix-socket.c requires Unix sockets\n+#endif\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tstruct ipc_client_connect_options options\n+\t\t= IPC_CLIENT_CONNECT_OPTIONS_INIT;\n+\tstruct stat st;\n+\tstruct ipc_client_connection *connection_test = NULL;\n+\n+\toptions.wait_if_busy = 0;\n+\toptions.wait_if_not_found = 0;\n+\n+\tif (lstat(path, &st) == -1) {\n+\t\tswitch (errno) {\n+\t\tcase ENOENT:\n+\t\tcase ENOTDIR:\n+\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__INVALID_PATH;\n+\t\t}\n+\t}\n+\n+\t/* also complain if a plain file is in the way */\n+\tif ((st.st_mode & S_IFMT) != S_IFSOCK)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\t/*\n+\t * Just because the filesystem has a S_IFSOCK type inode\n+\t * at `path`, doesn't mean it that there is a server listening.\n+\t * Ping it to be sure.\n+\t */\n+\tstate = ipc_client_try_connect(path, &options, &connection_test);\n+\tipc_client_close_connection(connection_test);\n+\n+\treturn state;\n+}\n+\n+/*\n+ * Retry frequency when trying to connect to a server.\n+ *\n+ * This value should be short enough that we don't seriously delay our\n+ * caller, but not fast enough that our spinning puts pressure on the\n+ * system.\n+ */\n+#define WAIT_STEP_MS (50)\n+\n+/*\n+ * Try to connect to the server.  If the server is just starting up or\n+ * is very busy, we may not get a connection the first time.\n+ */\n+static enum ipc_active_state connect_to_server(\n+\tconst char *path,\n+\tint timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tint k;\n+\n+\t*pfd = -1;\n+\n+\tfor (k = 0; k < timeout_ms; k += WAIT_STEP_MS) {\n+\t\tint fd = unix_stream_connect(path, options->uds_disallow_chdir);\n+\n+\t\tif (fd != -1) {\n+\t\t\t*pfd = fd;\n+\t\t\treturn IPC_STATE__LISTENING;\n+\t\t}\n+\n+\t\tif (errno == ENOENT) {\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ETIMEDOUT) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\tif (errno == ECONNREFUSED) {\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tgoto sleep_and_try_again;\n+\t\t}\n+\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\n+\tsleep_and_try_again:\n+\t\tsleep_millisec(WAIT_STEP_MS);\n+\t}\n+\n+\treturn IPC_STATE__NOT_LISTENING;\n+}\n+\n+/*\n+ * The total amount of time that we are willing to wait when trying to\n+ * connect to a server.\n+ *\n+ * When the server is first started, it might take a little while for\n+ * it to become ready to service requests.  Likewise, the server may\n+ * be very (temporarily) busy and not respond to our connections.\n+ *\n+ * We should gracefully and silently handle those conditions and try\n+ * again for a reasonable time period.\n+ *\n+ * The value chosen here should be long enough for the server\n+ * to reliably heal from the above conditions.\n+ */\n+#define MY_CONNECTION_TIMEOUT_MS (1000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tstate = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n+\t\t\t\t\t       connection->fd) < 0 ||\n+\t    packet_flush_gently(connection->fd) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tif (read_packetized_to_strbuf(\n+\t\t    connection->fd, answer,\n+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, answer);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+static int set_socket_blocking_flag(int fd, int make_nonblocking)\n+{\n+\tint flags;\n+\n+\tflags = fcntl(fd, F_GETFL, NULL);\n+\n+\tif (flags < 0)\n+\t\treturn -1;\n+\n+\tif (make_nonblocking)\n+\t\tflags |= O_NONBLOCK;\n+\telse\n+\t\tflags &= ~O_NONBLOCK;\n+\n+\treturn fcntl(fd, F_SETFL, flags);\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_WORKER_THREAD_DATA,\n+\tMAGIC_ACCEPT_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_worker_thread_data *worker_thread_data;\n+};\n+\n+struct ipc_worker_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_worker_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+};\n+\n+struct ipc_accept_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_data *server_data;\n+\n+\tstruct unix_ss_socket *server_socket;\n+\n+\tint fd_send_shutdown;\n+\tint fd_wait_shutdown;\n+\tpthread_t pthread_id;\n+};\n+\n+/*\n+ * With unix-sockets, the conceptual \"ipc-server\" is implemented as a single\n+ * controller \"accept-thread\" thread and a pool of \"worker-thread\" threads.\n+ * The former does the usual `accept()` loop and dispatches connections\n+ * to an idle worker thread.  The worker threads wait in an idle loop for\n+ * a new connection, communicate with the client and relay data to/from\n+ * the `application_cb` and then wait for another connection from the\n+ * server thread.  This avoids the overhead of constantly creating and\n+ * destroying threads.\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\n+\tstruct ipc_accept_thread_data *accept_thread;\n+\tstruct ipc_worker_thread_data *worker_thread_list;\n+\n+\tpthread_mutex_t work_available_mutex;\n+\tpthread_cond_t work_available_cond;\n+\n+\t/*\n+\t * Accepted but not yet processed client connections are kept\n+\t * in a circular buffer FIFO.  The queue is empty when the\n+\t * positions are equal.\n+\t */\n+\tint *fifo_fds;\n+\tint queue_size;\n+\tint back_pos;\n+\tint front_pos;\n+\n+\tint shutdown_requested;\n+\tint is_stopped;\n+};\n+\n+/*\n+ * Remove and return the oldest queued connection.\n+ *\n+ * Returns -1 if empty.\n+ */\n+static int fifo_dequeue(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint fd;\n+\n+\tif (server_data->back_pos == server_data->front_pos)\n+\t\treturn -1;\n+\n+\tfd = server_data->fifo_fds[server_data->front_pos];\n+\tserver_data->fifo_fds[server_data->front_pos] = -1;\n+\n+\tserver_data->front_pos++;\n+\tif (server_data->front_pos == server_data->queue_size)\n+\t\tserver_data->front_pos = 0;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Push a new fd onto the back of the queue.\n+ *\n+ * Drop it and return -1 if queue is already full.\n+ */\n+static int fifo_enqueue(struct ipc_server_data *server_data, int fd)\n+{\n+\t/* ASSERT holding mutex */\n+\n+\tint next_back_pos;\n+\n+\tnext_back_pos = server_data->back_pos + 1;\n+\tif (next_back_pos == server_data->queue_size)\n+\t\tnext_back_pos = 0;\n+\n+\tif (next_back_pos == server_data->front_pos) {\n+\t\t/* Queue is full. Just drop it. */\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\tserver_data->fifo_fds[server_data->back_pos] = fd;\n+\tserver_data->back_pos = next_back_pos;\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Wait for a connection to be queued to the FIFO and return it.\n+ *\n+ * Returns -1 if someone has already requested a shutdown.\n+ */\n+static int worker_thread__wait_for_connection(\n+\tstruct ipc_worker_thread_data *worker_thread_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tint fd = -1;\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\tfor (;;) {\n+\t\tif (server_data->shutdown_requested)\n+\t\t\tbreak;\n+\n+\t\tfd = fifo_dequeue(server_data);\n+\t\tif (fd >= 0)\n+\t\t\tbreak;\n+\n+\t\tpthread_cond_wait(&server_data->work_available_cond,\n+\t\t\t\t  &server_data->work_available_mutex);\n+\t}\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf_no_flush(response, response_len,\n+\t\t\t\t\t\t  reply_data->fd);\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_WAIT_POLL_TIMEOUT_MS (10)\n+\n+/*\n+ * If the client hangs up without sending any data on the wire, just\n+ * quietly close the socket and ignore this client.\n+ *\n+ * This worker thread is committed to reading the IPC request data\n+ * from the client at the other end of this fd.  Wait here for the\n+ * client to actually put something on the wire -- because if the\n+ * client just does a ping (connect and hangup without sending any\n+ * data), our use of the pkt-line read routines will spew an error\n+ * message.\n+ *\n+ * Return -1 if the client hung up.\n+ * Return 0 if data (possibly incomplete) is ready.\n+ */\n+static int worker_thread__wait_for_io_start(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tstruct pollfd pollfd[1];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = fd;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\tint in_shutdown;\n+\n+\t\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\t\tin_shutdown = server_data->shutdown_requested;\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\t\t\t/*\n+\t\t\t * If a shutdown is already in progress and this\n+\t\t\t * client has not started talking yet, just drop it.\n+\t\t\t */\n+\t\t\tif (in_shutdown)\n+\t\t\t\tgoto cleanup;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLHUP)\n+\t\t\tgoto cleanup;\n+\n+\t\tif (pollfd[0].revents & POLLIN)\n+\t\t\treturn 0;\n+\n+\t\tgoto cleanup;\n+\t}\n+\n+cleanup:\n+\tclose(fd);\n+\treturn -1;\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ */\n+static int worker_thread__do_io(\n+\tstruct ipc_worker_thread_data *worker_thread_data,\n+\tint fd)\n+{\n+\t/* ASSERT NOT holding lock */\n+\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.worker_thread_data = worker_thread_data;\n+\n+\treply_data.fd = fd;\n+\n+\tret = read_packetized_to_strbuf(\n+\t\treply_data.fd, &buf,\n+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);\n+\tif (ret >= 0) {\n+\t\tret = worker_thread_data->server_data->application_cb(\n+\t\t\tworker_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Block SIGPIPE on the current thread (so that we get EPIPE from\n+ * write() rather than an actual signal).\n+ *\n+ * Note that using sigchain_push() and _pop() to control SIGPIPE\n+ * around our IO calls is not thread safe:\n+ * [] It uses a global stack of handler frames.\n+ * [] It uses ALLOC_GROW() to resize it.\n+ * [] Finally, according to the `signal(2)` man-page:\n+ *    \"The effects of `signal()` in a multithreaded process are unspecified.\"\n+ */\n+static void thread_block_sigpipe(sigset_t *old_set)\n+{\n+\tsigset_t new_set;\n+\n+\tsigemptyset(&new_set);\n+\tsigaddset(&new_set, SIGPIPE);\n+\n+\tsigemptyset(old_set);\n+\tpthread_sigmask(SIG_BLOCK, &new_set, old_set);\n+}\n+\n+/*\n+ * Thread proc for an IPC worker thread.  It handles a series of\n+ * connections from clients.  It pulls the next fd from the queue\n+ * processes it, and then waits for the next client.\n+ *\n+ * Block SIGPIPE in this worker thread for the life of the thread.\n+ * This avoids stray (and sometimes delayed) SIGPIPE signals caused\n+ * by client errors and/or when we are under extremely heavy IO load.\n+ *\n+ * This means that the application callback will have SIGPIPE blocked.\n+ * The callback should not change it.\n+ */\n+static void *worker_thread_proc(void *_worker_thread_data)\n+{\n+\tstruct ipc_worker_thread_data *worker_thread_data = _worker_thread_data;\n+\tstruct ipc_server_data *server_data = worker_thread_data->server_data;\n+\tsigset_t old_set;\n+\tint fd, io;\n+\tint ret;\n+\n+\ttrace2_thread_start(\"ipc-worker\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tfd = worker_thread__wait_for_connection(worker_thread_data);\n+\t\tif (fd == -1)\n+\t\t\tbreak; /* in shutdown */\n+\n+\t\tio = worker_thread__wait_for_io_start(worker_thread_data, fd);\n+\t\tif (io == -1)\n+\t\t\tcontinue; /* client hung up without sending anything */\n+\n+\t\tret = worker_thread__do_io(worker_thread_data, fd);\n+\n+\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\ttrace2_data_string(\"ipc-worker\", NULL, \"queue_stop_async\",\n+\t\t\t\t\t   \"application_quit\");\n+\t\t\t/*\n+\t\t\t * The application layer is telling the ipc-server\n+\t\t\t * layer to shutdown.\n+\t\t\t *\n+\t\t\t * We DO NOT have a response to send to the client.\n+\t\t\t *\n+\t\t\t * Queue an async stop (to stop the other threads) and\n+\t\t\t * allow this worker thread to exit now (no sense waiting\n+\t\t\t * for the thread-pool shutdown signal).\n+\t\t\t *\n+\t\t\t * Other non-idle worker threads are allowed to finish\n+\t\t\t * responding to their current clients.\n+\t\t\t */\n+\t\t\tipc_server_stop_async(server_data);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/* A randomly chosen value. */\n+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)\n+\n+/*\n+ * Accept a new client connection on our socket.  This uses non-blocking\n+ * IO so that we can also wait for shutdown requests on our socket-pair\n+ * without actually spinning on a fast timeout.\n+ */\n+static int accept_thread__wait_for_connection(\n+\tstruct ipc_accept_thread_data *accept_thread_data)\n+{\n+\tstruct pollfd pollfd[2];\n+\tint result;\n+\n+\tfor (;;) {\n+\t\tpollfd[0].fd = accept_thread_data->fd_wait_shutdown;\n+\t\tpollfd[0].events = POLLIN;\n+\n+\t\tpollfd[1].fd = accept_thread_data->server_socket->fd_socket;\n+\t\tpollfd[1].events = POLLIN;\n+\n+\t\tresult = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);\n+\t\tif (result < 0) {\n+\t\t\tif (errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\treturn result;\n+\t\t}\n+\n+\t\tif (result == 0) {\n+\t\t\t/* a timeout */\n+\n+\t\t\t/*\n+\t\t\t * If someone deletes or force-creates a new unix\n+\t\t\t * domain socket at our path, all future clients\n+\t\t\t * will be routed elsewhere and we silently starve.\n+\t\t\t * If that happens, just queue a shutdown.\n+\t\t\t */\n+\t\t\tif (unix_ss_was_stolen(\n+\t\t\t\t    accept_thread_data->server_socket)) {\n+\t\t\t\ttrace2_data_string(\"ipc-accept\", NULL,\n+\t\t\t\t\t\t   \"queue_stop_async\",\n+\t\t\t\t\t\t   \"socket_stolen\");\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\taccept_thread_data->server_data);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (pollfd[0].revents & POLLIN) {\n+\t\t\t/* shutdown message queued to socketpair */\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (pollfd[1].revents & POLLIN) {\n+\t\t\t/* a connection is available on server_socket */\n+\n+\t\t\tint client_fd =\n+\t\t\t\taccept(accept_thread_data->server_socket->fd_socket,\n+\t\t\t\t       NULL, NULL);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\treturn client_fd;\n+\n+\t\t\t/*\n+\t\t\t * An error here is unlikely -- it probably\n+\t\t\t * indicates that the connecting process has\n+\t\t\t * already dropped the connection.\n+\t\t\t */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tBUG(\"unandled poll result errno=%d r[0]=%d r[1]=%d\",\n+\t\t    errno, pollfd[0].revents, pollfd[1].revents);\n+\t}\n+}\n+\n+/*\n+ * Thread proc for the IPC server \"accept thread\".  This waits for\n+ * an incoming socket connection, appends it to the queue of available\n+ * connections, and notifies a worker thread to process it.\n+ *\n+ * Block SIGPIPE in this thread for the life of the thread.  This\n+ * avoids any stray SIGPIPE signals when closing pipe fds under\n+ * extremely heavy loads (such as when the fifo queue is full and we\n+ * drop incomming connections).\n+ */\n+static void *accept_thread_proc(void *_accept_thread_data)\n+{\n+\tstruct ipc_accept_thread_data *accept_thread_data = _accept_thread_data;\n+\tstruct ipc_server_data *server_data = accept_thread_data->server_data;\n+\tsigset_t old_set;\n+\n+\ttrace2_thread_start(\"ipc-accept\");\n+\n+\tthread_block_sigpipe(&old_set);\n+\n+\tfor (;;) {\n+\t\tint client_fd = accept_thread__wait_for_connection(\n+\t\t\taccept_thread_data);\n+\n+\t\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\t\tif (server_data->shutdown_requested) {\n+\t\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t\t\tif (client_fd >= 0)\n+\t\t\t\tclose(client_fd);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (client_fd < 0) {\n+\t\t\t/* ignore transient accept() errors */\n+\t\t}\n+\t\telse {\n+\t\t\tfifo_enqueue(server_data, client_fd);\n+\t\t\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\t\t}\n+\t\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\t}\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+/*\n+ * We can't predict the connection arrival rate relative to the worker\n+ * processing rate, therefore we allow the \"accept-thread\" to queue up\n+ * a generous number of connections, since we'd rather have the client\n+ * not unnecessarily timeout if we can avoid it.  (The assumption is\n+ * that this will be used for FSMonitor and a few second wait on a\n+ * connection is better than having the client timeout and do the full\n+ * computation itself.)\n+ *\n+ * The FIFO queue size is set to a multiple of the worker pool size.\n+ * This value chosen at random.\n+ */\n+#define FIFO_SCALE (100)\n+\n+/*\n+ * The backlog value for `listen(2)`.  This doesn't need to huge,\n+ * rather just large enough for our \"accept-thread\" to wake up and\n+ * queue incoming connections onto the FIFO without the kernel\n+ * dropping any.\n+ *\n+ * This value chosen at random.\n+ */\n+#define LISTEN_BACKLOG (50)\n+\n+static int create_listener_socket(\n+\tconst char *path,\n+\tconst struct ipc_server_opts *ipc_opts,\n+\tstruct unix_ss_socket **new_server_socket)\n+{\n+\tstruct unix_ss_socket *server_socket = NULL;\n+\tstruct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;\n+\tint ret;\n+\n+\tuslg_opts.listen_backlog_size = LISTEN_BACKLOG;\n+\tuslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;\n+\n+\tret = unix_ss_create(path, &uslg_opts, -1, &server_socket);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (set_socket_blocking_flag(server_socket->fd_socket, 1)) {\n+\t\tint saved_errno = errno;\n+\t\tunix_ss_free(server_socket);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\t*new_server_socket = server_socket;\n+\n+\ttrace2_data_string(\"ipc-server\", NULL, \"listen-with-lock\", path);\n+\treturn 0;\n+}\n+\n+static int setup_listener_socket(\n+\tconst char *path,\n+\tconst struct ipc_server_opts *ipc_opts,\n+\tstruct unix_ss_socket **new_server_socket)\n+{\n+\tint ret, saved_errno;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"create-listener_socket\", NULL);\n+\n+\tret = create_listener_socket(path, ipc_opts, new_server_socket);\n+\n+\tsaved_errno = errno;\n+\ttrace2_region_leave(\"ipc-server\", \"create-listener_socket\", NULL);\n+\terrno = saved_errno;\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Start IPC server in a pool of background threads.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct unix_ss_socket *server_socket = NULL;\n+\tstruct ipc_server_data *server_data;\n+\tint sv[2];\n+\tint k;\n+\tint ret;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\t/*\n+\t * Create a socketpair and set sv[1] to non-blocking.  This\n+\t * will used to send a shutdown message to the accept-thread\n+\t * and allows the accept-thread to wait on EITHER a client\n+\t * connection or a shutdown request without spinning.\n+\t */\n+\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)\n+\t\treturn -1;\n+\n+\tif (set_socket_blocking_flag(sv[1], 1)) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\tret = setup_listener_socket(path, opts, &server_socket);\n+\tif (ret) {\n+\t\tint saved_errno = errno;\n+\t\tclose(sv[0]);\n+\t\tclose(sv[1]);\n+\t\terrno = saved_errno;\n+\t\treturn ret;\n+\t}\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tpthread_mutex_init(&server_data->work_available_mutex, NULL);\n+\tpthread_cond_init(&server_data->work_available_cond, NULL);\n+\n+\tserver_data->queue_size = nr_threads * FIFO_SCALE;\n+\tCALLOC_ARRAY(server_data->fifo_fds, server_data->queue_size);\n+\n+\tserver_data->accept_thread =\n+\t\txcalloc(1, sizeof(*server_data->accept_thread));\n+\tserver_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;\n+\tserver_data->accept_thread->server_data = server_data;\n+\tserver_data->accept_thread->server_socket = server_socket;\n+\tserver_data->accept_thread->fd_send_shutdown = sv[0];\n+\tserver_data->accept_thread->fd_wait_shutdown = sv[1];\n+\n+\tif (pthread_create(&server_data->accept_thread->pthread_id, NULL,\n+\t\t\t   accept_thread_proc, server_data->accept_thread))\n+\t\tdie_errno(_(\"could not start accept_thread '%s'\"), path);\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_worker_thread_data *wtd;\n+\n+\t\twtd = xcalloc(1, sizeof(*wtd));\n+\t\twtd->magic = MAGIC_WORKER_THREAD_DATA;\n+\t\twtd->server_data = server_data;\n+\n+\t\tif (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc,\n+\t\t\t\t   wtd)) {\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start worker[0] for '%s'\"),\n+\t\t\t\t    path);\n+\t\t\t/*\n+\t\t\t * Limp along with the thread pool that we have.\n+\t\t\t */\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\twtd->next_thread = server_data->worker_thread_list;\n+\t\tserver_data->worker_thread_list = wtd;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+/*\n+ * Gently tell the IPC server treads to shutdown.\n+ * Can be run on any thread.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\t/* ASSERT NOT holding mutex */\n+\n+\tint fd;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\ttrace2_region_enter(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\tpthread_mutex_lock(&server_data->work_available_mutex);\n+\n+\tserver_data->shutdown_requested = 1;\n+\n+\t/*\n+\t * Write a byte to the shutdown socket pair to wake up the\n+\t * accept-thread.\n+\t */\n+\tif (write(server_data->accept_thread->fd_send_shutdown, \"Q\", 1) < 0)\n+\t\terror_errno(\"could not write to fd_send_shutdown\");\n+\n+\t/*\n+\t * Drain the queue of existing connections.\n+\t */\n+\twhile ((fd = fifo_dequeue(server_data)) != -1)\n+\t\tclose(fd);\n+\n+\t/*\n+\t * Gently tell worker threads to stop processing new connections\n+\t * and exit.  (This does not abort in-process conversations.)\n+\t */\n+\tpthread_cond_broadcast(&server_data->work_available_cond);\n+\n+\tpthread_mutex_unlock(&server_data->work_available_mutex);\n+\n+\ttrace2_region_leave(\"ipc-server\", \"server-stop-async\", NULL);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Wait for all IPC server threads to stop.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tpthread_join(server_data->accept_thread->pthread_id, NULL);\n+\n+\tif (!server_data->shutdown_requested)\n+\t\tBUG(\"ipc-server: accept-thread stopped for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tpthread_join(wtd->pthread_id, NULL);\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tstruct ipc_accept_thread_data * accept_thread_data;\n+\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\taccept_thread_data = server_data->accept_thread;\n+\tif (accept_thread_data) {\n+\t\tunix_ss_free(accept_thread_data->server_socket);\n+\n+\t\tif (accept_thread_data->fd_send_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_send_shutdown);\n+\t\tif (accept_thread_data->fd_wait_shutdown != -1)\n+\t\t\tclose(accept_thread_data->fd_wait_shutdown);\n+\n+\t\tfree(server_data->accept_thread);\n+\t}\n+\n+\twhile (server_data->worker_thread_list) {\n+\t\tstruct ipc_worker_thread_data *wtd =\n+\t\t\tserver_data->worker_thread_list;\n+\n+\t\tserver_data->worker_thread_list = wtd->next_thread;\n+\t\tfree(wtd);\n+\t}\n+\n+\tpthread_cond_destroy(&server_data->work_available_cond);\n+\tpthread_mutex_destroy(&server_data->work_available_mutex);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tfree(server_data->fifo_fds);\n+\tfree(server_data);\n+}\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex c94011269ebb..9897fcc8ea2a 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -248,6 +248,8 @@ endif()\n \n if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+else()\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c)\n endif()\n \n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\ndiff --git a/simple-ipc.h b/simple-ipc.h\nindex ab5619e3d76f..dc3606e30bd6 100644\n--- a/simple-ipc.h\n+++ b/simple-ipc.h\n@@ -5,7 +5,7 @@\n  * See Documentation/technical/api-simple-ipc.txt\n  */\n \n-#if defined(GIT_WINDOWS_NATIVE)\n+#if defined(GIT_WINDOWS_NATIVE) || !defined(NO_UNIX_SOCKETS)\n #define SUPPORTS_SIMPLE_IPC\n #endif\n \n@@ -62,11 +62,17 @@ struct ipc_client_connect_options {\n \t * the service and need to wait for it to become ready.\n \t */\n \tunsigned int wait_if_not_found:1;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n #define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n \t.wait_if_busy = 0, \\\n \t.wait_if_not_found = 0, \\\n+\t.uds_disallow_chdir = 0, \\\n }\n \n /*\n@@ -159,6 +165,11 @@ struct ipc_server_data;\n struct ipc_server_opts\n {\n \tint nr_threads;\n+\n+\t/*\n+\t * Disallow chdir() when creating a Unix domain socket.\n+\t */\n+\tunsigned int uds_disallow_chdir:1;\n };\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"419931","messageId":"58c3fb7cd776600c2e859a7db23dcfe00b52c6f0.1616408989.git.gitgitgadget@gmail.com","threadId":"54978","inReplyTo":"pull.766.v7.git.1616408989.gitgitgadget@gmail.com","subject":"[PATCH v7 06/12] simple-ipc: add win32 implementation","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T10:29:42Z","receivedAt":"2021-03-22T10:31:03Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate Windows implementation of \"simple-ipc\" using named pipes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                            |   5 +\n compat/simple-ipc/ipc-shared.c      |  28 ++\n compat/simple-ipc/ipc-win32.c       | 751 ++++++++++++++++++++++++++++\n config.mak.uname                    |   2 +\n contrib/buildsystems/CMakeLists.txt |   4 +\n simple-ipc.h                        | 228 +++++++++\n 6 files changed, 1018 insertions(+)\n create mode 100644 compat/simple-ipc/ipc-shared.c\n create mode 100644 compat/simple-ipc/ipc-win32.c\n create mode 100644 simple-ipc.h\n\ndiff --git a/Makefile b/Makefile\nindex dd08b4ced01c..d3c42d3f4f9f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1667,6 +1667,11 @@ else\n \tLIB_OBJS += unix-socket.o\n endif\n \n+ifdef USE_WIN32_IPC\n+\tLIB_OBJS += compat/simple-ipc/ipc-shared.o\n+\tLIB_OBJS += compat/simple-ipc/ipc-win32.o\n+endif\n+\n ifdef NO_ICONV\n \tBASIC_CFLAGS += -DNO_ICONV\n endif\ndiff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c\nnew file mode 100644\nindex 000000000000..1edec8159532\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-shared.c\n@@ -0,0 +1,28 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data)\n+{\n+\tstruct ipc_server_data *server_data = NULL;\n+\tint ret;\n+\n+\tret = ipc_server_run_async(&server_data, path, opts,\n+\t\t\t\t   application_cb, application_data);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = ipc_server_await(server_data);\n+\n+\tipc_server_free(server_data);\n+\n+\treturn ret;\n+}\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\ndiff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c\nnew file mode 100644\nindex 000000000000..8f89c02037e3\n--- /dev/null\n+++ b/compat/simple-ipc/ipc-win32.c\n@@ -0,0 +1,751 @@\n+#include \"cache.h\"\n+#include \"simple-ipc.h\"\n+#include \"strbuf.h\"\n+#include \"pkt-line.h\"\n+#include \"thread-utils.h\"\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+#error This file can only be compiled on Windows\n+#endif\n+\n+static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc)\n+{\n+\tint off = 0;\n+\tstruct strbuf realpath = STRBUF_INIT;\n+\n+\tif (!strbuf_realpath(&realpath, path, 0))\n+\t\treturn -1;\n+\n+\toff = swprintf(wpath, alloc, L\"\\\\\\\\.\\\\pipe\\\\\");\n+\tif (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0)\n+\t\treturn -1;\n+\n+\t/* Handle drive prefix */\n+\tif (wpath[off] && wpath[off + 1] == L':') {\n+\t\twpath[off + 1] = L'_';\n+\t\toff += 2;\n+\t}\n+\n+\tfor (; wpath[off]; off++)\n+\t\tif (wpath[off] == L'/')\n+\t\t\twpath[off] = L'\\\\';\n+\n+\tstrbuf_release(&realpath);\n+\treturn 0;\n+}\n+\n+static enum ipc_active_state get_active_state(wchar_t *pipe_path)\n+{\n+\tif (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT))\n+\t\treturn IPC_STATE__LISTENING;\n+\n+\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\tif (GetLastError() == ERROR_FILE_NOT_FOUND)\n+\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\treturn IPC_STATE__OTHER_ERROR;\n+}\n+\n+enum ipc_active_state ipc_get_active_state(const char *path)\n+{\n+\twchar_t pipe_path[MAX_PATH];\n+\n+\tif (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0)\n+\t\treturn IPC_STATE__INVALID_PATH;\n+\n+\treturn get_active_state(pipe_path);\n+}\n+\n+#define WAIT_STEP_MS (50)\n+\n+static enum ipc_active_state connect_to_server(\n+\tconst wchar_t *wpath,\n+\tDWORD timeout_ms,\n+\tconst struct ipc_client_connect_options *options,\n+\tint *pfd)\n+{\n+\tDWORD t_start_ms, t_waited_ms;\n+\tDWORD step_ms;\n+\tHANDLE hPipe = INVALID_HANDLE_VALUE;\n+\tDWORD mode = PIPE_READMODE_BYTE;\n+\tDWORD gle;\n+\n+\t*pfd = -1;\n+\n+\tfor (;;) {\n+\t\thPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE,\n+\t\t\t\t    0, NULL, OPEN_EXISTING, 0, NULL);\n+\t\tif (hPipe != INVALID_HANDLE_VALUE)\n+\t\t\tbreak;\n+\n+\t\tgle = GetLastError();\n+\n+\t\tswitch (gle) {\n+\t\tcase ERROR_FILE_NOT_FOUND:\n+\t\t\tif (!options->wait_if_not_found)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__PATH_NOT_FOUND;\n+\n+\t\t\tstep_ms = (timeout_ms < WAIT_STEP_MS) ?\n+\t\t\t\ttimeout_ms : WAIT_STEP_MS;\n+\t\t\tsleep_millisec(step_ms);\n+\n+\t\t\ttimeout_ms -= step_ms;\n+\t\t\tbreak; /* try again */\n+\n+\t\tcase ERROR_PIPE_BUSY:\n+\t\t\tif (!options->wait_if_busy)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\t\t\tif (!timeout_ms)\n+\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\tt_start_ms = (DWORD)(getnanotime() / 1000000);\n+\n+\t\t\tif (!WaitNamedPipeW(wpath, timeout_ms)) {\n+\t\t\t\tif (GetLastError() == ERROR_SEM_TIMEOUT)\n+\t\t\t\t\treturn IPC_STATE__NOT_LISTENING;\n+\n+\t\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * A pipe server instance became available.\n+\t\t\t * Race other client processes to connect to\n+\t\t\t * it.\n+\t\t\t *\n+\t\t\t * But first decrement our overall timeout so\n+\t\t\t * that we don't starve if we keep losing the\n+\t\t\t * race.  But also guard against special\n+\t\t\t * NPMWAIT_ values (0 and -1).\n+\t\t\t */\n+\t\t\tt_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms;\n+\t\t\tif (t_waited_ms < timeout_ms)\n+\t\t\t\ttimeout_ms -= t_waited_ms;\n+\t\t\telse\n+\t\t\t\ttimeout_ms = 1;\n+\t\t\tbreak; /* try again */\n+\n+\t\tdefault:\n+\t\t\treturn IPC_STATE__OTHER_ERROR;\n+\t\t}\n+\t}\n+\n+\tif (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t*pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);\n+\tif (*pfd < 0) {\n+\t\tCloseHandle(hPipe);\n+\t\treturn IPC_STATE__OTHER_ERROR;\n+\t}\n+\n+\t/* fd now owns hPipe */\n+\n+\treturn IPC_STATE__LISTENING;\n+}\n+\n+/*\n+ * The default connection timeout for Windows clients.\n+ *\n+ * This is not currently part of the ipc_ API (nor the config settings)\n+ * because of differences between Windows and other platforms.\n+ *\n+ * This value was chosen at random.\n+ */\n+#define WINDOWS_CONNECTION_TIMEOUT_MS (30000)\n+\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection)\n+{\n+\twchar_t wpath[MAX_PATH];\n+\tenum ipc_active_state state = IPC_STATE__OTHER_ERROR;\n+\tint fd = -1;\n+\n+\t*p_connection = NULL;\n+\n+\ttrace2_region_enter(\"ipc-client\", \"try-connect\", NULL);\n+\ttrace2_data_string(\"ipc-client\", NULL, \"try-connect/path\", path);\n+\n+\tif (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0)\n+\t\tstate = IPC_STATE__INVALID_PATH;\n+\telse\n+\t\tstate = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,\n+\t\t\t\t\t  options, &fd);\n+\n+\ttrace2_data_intmax(\"ipc-client\", NULL, \"try-connect/state\",\n+\t\t\t   (intmax_t)state);\n+\ttrace2_region_leave(\"ipc-client\", \"try-connect\", NULL);\n+\n+\tif (state == IPC_STATE__LISTENING) {\n+\t\t(*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));\n+\t\t(*p_connection)->fd = fd;\n+\t}\n+\n+\treturn state;\n+}\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection)\n+{\n+\tif (!connection)\n+\t\treturn;\n+\n+\tif (connection->fd != -1)\n+\t\tclose(connection->fd);\n+\n+\tfree(connection);\n+}\n+\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer)\n+{\n+\tint ret = 0;\n+\n+\tstrbuf_setlen(answer, 0);\n+\n+\ttrace2_region_enter(\"ipc-client\", \"send-command\", NULL);\n+\n+\tif (write_packetized_from_buf_no_flush(message, strlen(message),\n+\t\t\t\t\t       connection->fd) < 0 ||\n+\t    packet_flush_gently(connection->fd) < 0) {\n+\t\tret = error(_(\"could not send IPC command\"));\n+\t\tgoto done;\n+\t}\n+\n+\tFlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));\n+\n+\tif (read_packetized_to_strbuf(\n+\t\t    connection->fd, answer,\n+\t\t    PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {\n+\t\tret = error(_(\"could not read IPC response\"));\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\ttrace2_region_leave(\"ipc-client\", \"send-command\", NULL);\n+\treturn ret;\n+}\n+\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *response)\n+{\n+\tint ret = -1;\n+\tenum ipc_active_state state;\n+\tstruct ipc_client_connection *connection = NULL;\n+\n+\tstate = ipc_client_try_connect(path, options, &connection);\n+\n+\tif (state != IPC_STATE__LISTENING)\n+\t\treturn ret;\n+\n+\tret = ipc_client_send_command_to_connection(connection, message, response);\n+\n+\tipc_client_close_connection(connection);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Duplicate the given pipe handle and wrap it in a file descriptor so\n+ * that we can use pkt-line on it.\n+ */\n+static int dup_fd_from_pipe(const HANDLE pipe)\n+{\n+\tHANDLE process = GetCurrentProcess();\n+\tHANDLE handle;\n+\tint fd;\n+\n+\tif (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE,\n+\t\t\t     DUPLICATE_SAME_ACCESS)) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\treturn -1;\n+\t}\n+\n+\tfd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY);\n+\tif (fd < 0) {\n+\t\terrno = err_win_to_posix(GetLastError());\n+\t\tCloseHandle(handle);\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * `handle` is now owned by `fd` and will be automatically closed\n+\t * when the descriptor is closed.\n+\t */\n+\n+\treturn fd;\n+}\n+\n+/*\n+ * Magic numbers used to annotate callback instance data.\n+ * These are used to help guard against accidentally passing the\n+ * wrong instance data across multiple levels of callbacks (which\n+ * is easy to do if there are `void*` arguments).\n+ */\n+enum magic {\n+\tMAGIC_SERVER_REPLY_DATA,\n+\tMAGIC_SERVER_THREAD_DATA,\n+\tMAGIC_SERVER_DATA,\n+};\n+\n+struct ipc_server_reply_data {\n+\tenum magic magic;\n+\tint fd;\n+\tstruct ipc_server_thread_data *server_thread_data;\n+};\n+\n+struct ipc_server_thread_data {\n+\tenum magic magic;\n+\tstruct ipc_server_thread_data *next_thread;\n+\tstruct ipc_server_data *server_data;\n+\tpthread_t pthread_id;\n+\tHANDLE hPipe;\n+};\n+\n+/*\n+ * On Windows, the conceptual \"ipc-server\" is implemented as a pool of\n+ * n idential/peer \"server-thread\" threads.  That is, there is no\n+ * hierarchy of threads; and therefore no controller thread managing\n+ * the pool.  Each thread has an independent handle to the named pipe,\n+ * receives incoming connections, processes the client, and re-uses\n+ * the pipe for the next client connection.\n+ *\n+ * Therefore, the \"ipc-server\" only needs to maintain a list of the\n+ * spawned threads for eventual \"join\" purposes.\n+ *\n+ * A single \"stop-event\" is visible to all of the server threads to\n+ * tell them to shutdown (when idle).\n+ */\n+struct ipc_server_data {\n+\tenum magic magic;\n+\tipc_server_application_cb *application_cb;\n+\tvoid *application_data;\n+\tstruct strbuf buf_path;\n+\twchar_t wpath[MAX_PATH];\n+\n+\tHANDLE hEventStopRequested;\n+\tstruct ipc_server_thread_data *thread_list;\n+\tint is_stopped;\n+};\n+\n+enum connect_result {\n+\tCR_CONNECTED = 0,\n+\tCR_CONNECT_PENDING,\n+\tCR_CONNECT_ERROR,\n+\tCR_WAIT_ERROR,\n+\tCR_SHUTDOWN,\n+};\n+\n+static enum connect_result queue_overlapped_connect(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tif (ConnectNamedPipe(server_thread_data->hPipe, lpo))\n+\t\tgoto failed;\n+\n+\tswitch (GetLastError()) {\n+\tcase ERROR_IO_PENDING:\n+\t\treturn CR_CONNECT_PENDING;\n+\n+\tcase ERROR_PIPE_CONNECTED:\n+\t\tSetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+failed:\n+\terror(_(\"ConnectNamedPipe failed for '%s' (%lu)\"),\n+\t      server_thread_data->server_data->buf_path.buf,\n+\t      GetLastError());\n+\treturn CR_CONNECT_ERROR;\n+}\n+\n+/*\n+ * Use Windows Overlapped IO to wait for a connection or for our event\n+ * to be signalled.\n+ */\n+static enum connect_result wait_for_connection(\n+\tstruct ipc_server_thread_data *server_thread_data,\n+\tOVERLAPPED *lpo)\n+{\n+\tenum connect_result r;\n+\tHANDLE waitHandles[2];\n+\tDWORD dwWaitResult;\n+\n+\tr = queue_overlapped_connect(server_thread_data, lpo);\n+\tif (r != CR_CONNECT_PENDING)\n+\t\treturn r;\n+\n+\twaitHandles[0] = server_thread_data->server_data->hEventStopRequested;\n+\twaitHandles[1] = lpo->hEvent;\n+\n+\tdwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);\n+\tswitch (dwWaitResult) {\n+\tcase WAIT_OBJECT_0 + 0:\n+\t\treturn CR_SHUTDOWN;\n+\n+\tcase WAIT_OBJECT_0 + 1:\n+\t\tResetEvent(lpo->hEvent);\n+\t\treturn CR_CONNECTED;\n+\n+\tdefault:\n+\t\treturn CR_WAIT_ERROR;\n+\t}\n+}\n+\n+/*\n+ * Forward declare our reply callback function so that any compiler\n+ * errors are reported when we actually define the function (in addition\n+ * to any errors reported when we try to pass this callback function as\n+ * a parameter in a function call).  The former are easier to understand.\n+ */\n+static ipc_server_reply_cb do_io_reply_callback;\n+\n+/*\n+ * Relay application's response message to the client process.\n+ * (We do not flush at this point because we allow the caller\n+ * to chunk data to the client thru us.)\n+ */\n+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,\n+\t\t       const char *response, size_t response_len)\n+{\n+\tif (reply_data->magic != MAGIC_SERVER_REPLY_DATA)\n+\t\tBUG(\"reply_cb called with wrong instance data\");\n+\n+\treturn write_packetized_from_buf_no_flush(response, response_len,\n+\t\t\t\t\t\t  reply_data->fd);\n+}\n+\n+/*\n+ * Receive the request/command from the client and pass it to the\n+ * registered request-callback.  The request-callback will compose\n+ * a response and call our reply-callback to send it to the client.\n+ *\n+ * Simple-IPC only contains one round trip, so we flush and close\n+ * here after the response.\n+ */\n+static int do_io(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ipc_server_reply_data reply_data;\n+\tint ret = 0;\n+\n+\treply_data.magic = MAGIC_SERVER_REPLY_DATA;\n+\treply_data.server_thread_data = server_thread_data;\n+\n+\treply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe);\n+\tif (reply_data.fd < 0)\n+\t\treturn error(_(\"could not create fd from pipe for '%s'\"),\n+\t\t\t     server_thread_data->server_data->buf_path.buf);\n+\n+\tret = read_packetized_to_strbuf(\n+\t\treply_data.fd, &buf,\n+\t\tPACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);\n+\tif (ret >= 0) {\n+\t\tret = server_thread_data->server_data->application_cb(\n+\t\t\tserver_thread_data->server_data->application_data,\n+\t\t\tbuf.buf, do_io_reply_callback, &reply_data);\n+\n+\t\tpacket_flush_gently(reply_data.fd);\n+\n+\t\tFlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd)));\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * The client probably disconnected/shutdown before it\n+\t\t * could send a well-formed message.  Ignore it.\n+\t\t */\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tclose(reply_data.fd);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Handle IPC request and response with this connected client.  And reset\n+ * the pipe to prepare for the next client.\n+ */\n+static int use_connection(struct ipc_server_thread_data *server_thread_data)\n+{\n+\tint ret;\n+\n+\tret = do_io(server_thread_data);\n+\n+\tFlushFileBuffers(server_thread_data->hPipe);\n+\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Thread proc for an IPC server worker thread.  It handles a series of\n+ * connections from clients.  It cleans and reuses the hPipe between each\n+ * client.\n+ */\n+static void *server_thread_proc(void *_server_thread_data)\n+{\n+\tstruct ipc_server_thread_data *server_thread_data = _server_thread_data;\n+\tHANDLE hEventConnected = INVALID_HANDLE_VALUE;\n+\tOVERLAPPED oConnect;\n+\tenum connect_result cr;\n+\tint ret;\n+\n+\tassert(server_thread_data->hPipe != INVALID_HANDLE_VALUE);\n+\n+\ttrace2_thread_start(\"ipc-server\");\n+\ttrace2_data_string(\"ipc-server\", NULL, \"pipe\",\n+\t\t\t   server_thread_data->server_data->buf_path.buf);\n+\n+\thEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL);\n+\n+\tmemset(&oConnect, 0, sizeof(oConnect));\n+\toConnect.hEvent = hEventConnected;\n+\n+\tfor (;;) {\n+\t\tcr = wait_for_connection(server_thread_data, &oConnect);\n+\n+\t\tswitch (cr) {\n+\t\tcase CR_SHUTDOWN:\n+\t\t\tgoto finished;\n+\n+\t\tcase CR_CONNECTED:\n+\t\t\tret = use_connection(server_thread_data);\n+\t\t\tif (ret == SIMPLE_IPC_QUIT) {\n+\t\t\t\tipc_server_stop_async(\n+\t\t\t\t\tserver_thread_data->server_data);\n+\t\t\t\tgoto finished;\n+\t\t\t}\n+\t\t\tif (ret > 0) {\n+\t\t\t\t/*\n+\t\t\t\t * Ignore (transient) IO errors with this\n+\t\t\t\t * client and reset for the next client.\n+\t\t\t\t */\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase CR_CONNECT_PENDING:\n+\t\t\t/* By construction, this should not happen. */\n+\t\t\tBUG(\"ipc-server[%s]: unexpeced CR_CONNECT_PENDING\",\n+\t\t\t    server_thread_data->server_data->buf_path.buf);\n+\n+\t\tcase CR_CONNECT_ERROR:\n+\t\tcase CR_WAIT_ERROR:\n+\t\t\t/*\n+\t\t\t * Ignore these theoretical errors.\n+\t\t\t */\n+\t\t\tDisconnectNamedPipe(server_thread_data->hPipe);\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tBUG(\"unandled case after wait_for_connection\");\n+\t\t}\n+\t}\n+\n+finished:\n+\tCloseHandle(server_thread_data->hPipe);\n+\tCloseHandle(hEventConnected);\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+static HANDLE create_new_pipe(wchar_t *wpath, int is_first)\n+{\n+\tHANDLE hPipe;\n+\tDWORD dwOpenMode, dwPipeMode;\n+\tLPSECURITY_ATTRIBUTES lpsa = NULL;\n+\n+\tdwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |\n+\t\tFILE_FLAG_OVERLAPPED;\n+\n+\tdwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |\n+\t\tPIPE_REJECT_REMOTE_CLIENTS;\n+\n+\tif (is_first) {\n+\t\tdwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE;\n+\n+\t\t/*\n+\t\t * On Windows, the first server pipe instance gets to\n+\t\t * set the ACL / Security Attributes on the named\n+\t\t * pipe; subsequent instances inherit and cannot\n+\t\t * change them.\n+\t\t *\n+\t\t * TODO Should we allow the application layer to\n+\t\t * specify security attributes, such as `LocalService`\n+\t\t * or `LocalSystem`, when we create the named pipe?\n+\t\t * This question is probably not important when the\n+\t\t * daemon is started by a foreground user process and\n+\t\t * only needs to talk to the current user, but may be\n+\t\t * if the daemon is run via the Control Panel as a\n+\t\t * System Service.\n+\t\t */\n+\t}\n+\n+\thPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,\n+\t\t\t\t PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);\n+\n+\treturn hPipe;\n+}\n+\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data)\n+{\n+\tstruct ipc_server_data *server_data;\n+\twchar_t wpath[MAX_PATH];\n+\tHANDLE hPipeFirst = INVALID_HANDLE_VALUE;\n+\tint k;\n+\tint ret = 0;\n+\tint nr_threads = opts->nr_threads;\n+\n+\t*returned_server_data = NULL;\n+\n+\tret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));\n+\tif (ret < 0) {\n+\t\terrno = EINVAL;\n+\t\treturn -1;\n+\t}\n+\n+\thPipeFirst = create_new_pipe(wpath, 1);\n+\tif (hPipeFirst == INVALID_HANDLE_VALUE) {\n+\t\terrno = EADDRINUSE;\n+\t\treturn -2;\n+\t}\n+\n+\tserver_data = xcalloc(1, sizeof(*server_data));\n+\tserver_data->magic = MAGIC_SERVER_DATA;\n+\tserver_data->application_cb = application_cb;\n+\tserver_data->application_data = application_data;\n+\tserver_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL);\n+\tstrbuf_init(&server_data->buf_path, 0);\n+\tstrbuf_addstr(&server_data->buf_path, path);\n+\twcscpy(server_data->wpath, wpath);\n+\n+\tif (nr_threads < 1)\n+\t\tnr_threads = 1;\n+\n+\tfor (k = 0; k < nr_threads; k++) {\n+\t\tstruct ipc_server_thread_data *std;\n+\n+\t\tstd = xcalloc(1, sizeof(*std));\n+\t\tstd->magic = MAGIC_SERVER_THREAD_DATA;\n+\t\tstd->server_data = server_data;\n+\t\tstd->hPipe = INVALID_HANDLE_VALUE;\n+\n+\t\tstd->hPipe = (k == 0)\n+\t\t\t? hPipeFirst\n+\t\t\t: create_new_pipe(server_data->wpath, 0);\n+\n+\t\tif (std->hPipe == INVALID_HANDLE_VALUE) {\n+\t\t\t/*\n+\t\t\t * If we've reached a pipe instance limit for\n+\t\t\t * this path, just use fewer threads.\n+\t\t\t */\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (pthread_create(&std->pthread_id, NULL,\n+\t\t\t\t   server_thread_proc, std)) {\n+\t\t\t/*\n+\t\t\t * Likewise, if we're out of threads, just use\n+\t\t\t * fewer threads than requested.\n+\t\t\t *\n+\t\t\t * However, we just give up if we can't even get\n+\t\t\t * one thread.  This should not happen.\n+\t\t\t */\n+\t\t\tif (k == 0)\n+\t\t\t\tdie(_(\"could not start thread[0] for '%s'\"),\n+\t\t\t\t    path);\n+\n+\t\t\tCloseHandle(std->hPipe);\n+\t\t\tfree(std);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tstd->next_thread = server_data->thread_list;\n+\t\tserver_data->thread_list = std;\n+\t}\n+\n+\t*returned_server_data = server_data;\n+\treturn 0;\n+}\n+\n+int ipc_server_stop_async(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Gently tell all of the ipc_server threads to shutdown.\n+\t * This will be seen the next time they are idle (and waiting\n+\t * for a connection).\n+\t *\n+\t * We DO NOT attempt to force them to drop an active connection.\n+\t */\n+\tSetEvent(server_data->hEventStopRequested);\n+\treturn 0;\n+}\n+\n+int ipc_server_await(struct ipc_server_data *server_data)\n+{\n+\tDWORD dwWaitResult;\n+\n+\tif (!server_data)\n+\t\treturn 0;\n+\n+\tdwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE);\n+\tif (dwWaitResult != WAIT_OBJECT_0)\n+\t\treturn error(_(\"wait for hEvent failed for '%s'\"),\n+\t\t\t     server_data->buf_path.buf);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tpthread_join(std->pthread_id, NULL);\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tserver_data->is_stopped = 1;\n+\n+\treturn 0;\n+}\n+\n+void ipc_server_free(struct ipc_server_data *server_data)\n+{\n+\tif (!server_data)\n+\t\treturn;\n+\n+\tif (!server_data->is_stopped)\n+\t\tBUG(\"cannot free ipc-server while running for '%s'\",\n+\t\t    server_data->buf_path.buf);\n+\n+\tstrbuf_release(&server_data->buf_path);\n+\n+\tif (server_data->hEventStopRequested != INVALID_HANDLE_VALUE)\n+\t\tCloseHandle(server_data->hEventStopRequested);\n+\n+\twhile (server_data->thread_list) {\n+\t\tstruct ipc_server_thread_data *std = server_data->thread_list;\n+\n+\t\tserver_data->thread_list = std->next_thread;\n+\t\tfree(std);\n+\t}\n+\n+\tfree(server_data);\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex e22d4b6d67a3..2b3303f34be8 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows)\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \t# USE_NED_ALLOCATOR = YesPlease\n@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\n \tNO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease\n+\tUSE_WIN32_IPC = YesPlease\n \tUSE_WIN32_MMAP = YesPlease\n \tMMAP_PREVENTS_DELETE = UnfortunatelyYes\n \tUSE_NED_ALLOCATOR = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex ac3dbc079af8..40c9e8e3bd9d 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n \tlist(APPEND compat_SOURCES unix-socket.c)\n endif()\n \n+if(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tlist(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)\n+endif()\n+\n set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})\n \n #header checks\ndiff --git a/simple-ipc.h b/simple-ipc.h\nnew file mode 100644\nindex 000000000000..ab5619e3d76f\n--- /dev/null\n+++ b/simple-ipc.h\n@@ -0,0 +1,228 @@\n+#ifndef GIT_SIMPLE_IPC_H\n+#define GIT_SIMPLE_IPC_H\n+\n+/*\n+ * See Documentation/technical/api-simple-ipc.txt\n+ */\n+\n+#if defined(GIT_WINDOWS_NATIVE)\n+#define SUPPORTS_SIMPLE_IPC\n+#endif\n+\n+#ifdef SUPPORTS_SIMPLE_IPC\n+#include \"pkt-line.h\"\n+\n+/*\n+ * Simple IPC Client Side API.\n+ */\n+\n+enum ipc_active_state {\n+\t/*\n+\t * The pipe/socket exists and the daemon is waiting for connections.\n+\t */\n+\tIPC_STATE__LISTENING = 0,\n+\n+\t/*\n+\t * The pipe/socket exists, but the daemon is not listening.\n+\t * Perhaps it is very busy.\n+\t * Perhaps the daemon died without deleting the path.\n+\t * Perhaps it is shutting down and draining existing clients.\n+\t * Perhaps it is dead, but other clients are lingering and\n+\t * still holding a reference to the pathname.\n+\t */\n+\tIPC_STATE__NOT_LISTENING,\n+\n+\t/*\n+\t * The requested pathname is bogus and no amount of retries\n+\t * will fix that.\n+\t */\n+\tIPC_STATE__INVALID_PATH,\n+\n+\t/*\n+\t * The requested pathname is not found.  This usually means\n+\t * that there is no daemon present.\n+\t */\n+\tIPC_STATE__PATH_NOT_FOUND,\n+\n+\tIPC_STATE__OTHER_ERROR,\n+};\n+\n+struct ipc_client_connect_options {\n+\t/*\n+\t * Spin under timeout if the server is running but can't\n+\t * accept our connection yet.  This should always be set\n+\t * unless you just want to poke the server and see if it\n+\t * is alive.\n+\t */\n+\tunsigned int wait_if_busy:1;\n+\n+\t/*\n+\t * Spin under timeout if the pipe/socket is not yet present\n+\t * on the file system.  This is useful if we just started\n+\t * the service and need to wait for it to become ready.\n+\t */\n+\tunsigned int wait_if_not_found:1;\n+};\n+\n+#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \\\n+\t.wait_if_busy = 0, \\\n+\t.wait_if_not_found = 0, \\\n+}\n+\n+/*\n+ * Determine if a server is listening on this named pipe or socket using\n+ * platform-specific logic.  This might just probe the filesystem or it\n+ * might make a trivial connection to the server using this pathname.\n+ */\n+enum ipc_active_state ipc_get_active_state(const char *path);\n+\n+struct ipc_client_connection {\n+\tint fd;\n+};\n+\n+/*\n+ * Try to connect to the daemon on the named pipe or socket.\n+ *\n+ * Returns IPC_STATE__LISTENING and a connection handle.\n+ *\n+ * Otherwise, returns info to help decide whether to retry or to\n+ * spawn/respawn the server.\n+ */\n+enum ipc_active_state ipc_client_try_connect(\n+\tconst char *path,\n+\tconst struct ipc_client_connect_options *options,\n+\tstruct ipc_client_connection **p_connection);\n+\n+void ipc_client_close_connection(struct ipc_client_connection *connection);\n+\n+/*\n+ * Used by the client to synchronously send and receive a message with\n+ * the server on the provided client connection.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command_to_connection(\n+\tstruct ipc_client_connection *connection,\n+\tconst char *message, struct strbuf *answer);\n+\n+/*\n+ * Used by the client to synchronously connect and send and receive a\n+ * message to the server listening at the given path.\n+ *\n+ * Returns 0 when successful.\n+ *\n+ * Calls error() and returns non-zero otherwise.\n+ */\n+int ipc_client_send_command(const char *path,\n+\t\t\t    const struct ipc_client_connect_options *options,\n+\t\t\t    const char *message, struct strbuf *answer);\n+\n+/*\n+ * Simple IPC Server Side API.\n+ */\n+\n+struct ipc_server_reply_data;\n+\n+typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *,\n+\t\t\t\t  const char *response,\n+\t\t\t\t  size_t response_len);\n+\n+/*\n+ * Prototype for an application-supplied callback to process incoming\n+ * client IPC messages and compose a reply.  The `application_cb` should\n+ * use the provided `reply_cb` and `reply_data` to send an IPC response\n+ * back to the client.  The `reply_cb` callback can be called multiple\n+ * times for chunking purposes.  A reply message is optional and may be\n+ * omitted if not necessary for the application.\n+ *\n+ * The return value from the application callback is ignored.\n+ * The value `SIMPLE_IPC_QUIT` can be used to shutdown the server.\n+ */\n+typedef int (ipc_server_application_cb)(void *application_data,\n+\t\t\t\t\tconst char *request,\n+\t\t\t\t\tipc_server_reply_cb *reply_cb,\n+\t\t\t\t\tstruct ipc_server_reply_data *reply_data);\n+\n+#define SIMPLE_IPC_QUIT -2\n+\n+/*\n+ * Opaque instance data to represent an IPC server instance.\n+ */\n+struct ipc_server_data;\n+\n+/*\n+ * Control parameters for the IPC server instance.\n+ * Use this to hide platform-specific settings.\n+ */\n+struct ipc_server_opts\n+{\n+\tint nr_threads;\n+};\n+\n+/*\n+ * Start an IPC server instance in one or more background threads\n+ * and return a handle to the pool.\n+ *\n+ * Returns 0 if the asynchronous server pool was started successfully.\n+ * Returns -1 if not.\n+ * Returns -2 if we could not startup because another server is using\n+ * the socket or named pipe.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ */\n+int ipc_server_run_async(struct ipc_server_data **returned_server_data,\n+\t\t\t const char *path, const struct ipc_server_opts *opts,\n+\t\t\t ipc_server_application_cb *application_cb,\n+\t\t\t void *application_data);\n+\n+/*\n+ * Gently signal the IPC server pool to shutdown.  No new client\n+ * connections will be accepted, but existing connections will be\n+ * allowed to complete.\n+ */\n+int ipc_server_stop_async(struct ipc_server_data *server_data);\n+\n+/*\n+ * Block the calling thread until all threads in the IPC server pool\n+ * have completed and been joined.\n+ */\n+int ipc_server_await(struct ipc_server_data *server_data);\n+\n+/*\n+ * Close and free all resource handles associated with the IPC server\n+ * pool.\n+ */\n+void ipc_server_free(struct ipc_server_data *server_data);\n+\n+/*\n+ * Run an IPC server instance and block the calling thread of the\n+ * current process.  It does not return until the IPC server has\n+ * either shutdown or had an unrecoverable error.\n+ *\n+ * The IPC server handles incoming IPC messages from client processes\n+ * and may use one or more background threads as necessary.\n+ *\n+ * Returns 0 after the server has completed successfully.\n+ * Returns -1 if the server cannot be started.\n+ * Returns -2 if we could not startup because another server is using\n+ * the socket or named pipe.\n+ *\n+ * When a client IPC message is received, the `application_cb` will be\n+ * called (possibly on a random thread) to handle the message and\n+ * optionally compose a reply message.\n+ *\n+ * Note that `ipc_server_run()` is a synchronous wrapper around the\n+ * above asynchronous routines.  It effectively hides all of the\n+ * server state and thread details from the caller and presents a\n+ * simple synchronous interface.\n+ */\n+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,\n+\t\t   ipc_server_application_cb *application_cb,\n+\t\t   void *application_data);\n+\n+#endif /* SUPPORTS_SIMPLE_IPC */\n+#endif /* GIT_SIMPLE_IPC_H */\n-- \ngitgitgadget\n\n"}]}