|  | #include "git-compat-util.h" | 
|  | #include "abspath.h" | 
|  | #include "gettext.h" | 
|  | #include "simple-ipc.h" | 
|  | #include "strbuf.h" | 
|  | #include "pkt-line.h" | 
|  | #include "thread-utils.h" | 
|  | #include "trace.h" | 
|  | #include "trace2.h" | 
|  | #include "accctrl.h" | 
|  | #include "aclapi.h" | 
|  |  | 
|  | #ifndef SUPPORTS_SIMPLE_IPC | 
|  | /* | 
|  | * This source file should only be compiled when Simple IPC is supported. | 
|  | * See the top-level Makefile. | 
|  | */ | 
|  | #error SUPPORTS_SIMPLE_IPC not defined | 
|  | #endif | 
|  |  | 
|  | static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc) | 
|  | { | 
|  | int off = 0; | 
|  | struct strbuf realpath = STRBUF_INIT; | 
|  |  | 
|  | if (!strbuf_realpath(&realpath, path, 0)) | 
|  | return -1; | 
|  |  | 
|  | off = swprintf(wpath, alloc, L"\\\\.\\pipe\\"); | 
|  | if (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0) | 
|  | return -1; | 
|  |  | 
|  | /* Handle drive prefix */ | 
|  | if (wpath[off] && wpath[off + 1] == L':') { | 
|  | wpath[off + 1] = L'_'; | 
|  | off += 2; | 
|  | } | 
|  |  | 
|  | for (; wpath[off]; off++) | 
|  | if (wpath[off] == L'/') | 
|  | wpath[off] = L'\\'; | 
|  |  | 
|  | strbuf_release(&realpath); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static enum ipc_active_state get_active_state(wchar_t *pipe_path) | 
|  | { | 
|  | if (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT)) | 
|  | return IPC_STATE__LISTENING; | 
|  |  | 
|  | if (GetLastError() == ERROR_SEM_TIMEOUT) | 
|  | return IPC_STATE__NOT_LISTENING; | 
|  |  | 
|  | if (GetLastError() == ERROR_FILE_NOT_FOUND) | 
|  | return IPC_STATE__PATH_NOT_FOUND; | 
|  |  | 
|  | trace2_data_intmax("ipc-debug", NULL, "getstate/waitpipe/gle", | 
|  | (intmax_t)GetLastError()); | 
|  |  | 
|  | return IPC_STATE__OTHER_ERROR; | 
|  | } | 
|  |  | 
|  | enum ipc_active_state ipc_get_active_state(const char *path) | 
|  | { | 
|  | wchar_t pipe_path[MAX_PATH]; | 
|  |  | 
|  | if (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0) | 
|  | return IPC_STATE__INVALID_PATH; | 
|  |  | 
|  | return get_active_state(pipe_path); | 
|  | } | 
|  |  | 
|  | #define WAIT_STEP_MS (50) | 
|  |  | 
|  | static enum ipc_active_state connect_to_server( | 
|  | const wchar_t *wpath, | 
|  | DWORD timeout_ms, | 
|  | const struct ipc_client_connect_options *options, | 
|  | int *pfd) | 
|  | { | 
|  | DWORD t_start_ms, t_waited_ms; | 
|  | DWORD step_ms; | 
|  | HANDLE hPipe = INVALID_HANDLE_VALUE; | 
|  | DWORD mode = PIPE_READMODE_BYTE; | 
|  | DWORD gle; | 
|  |  | 
|  | *pfd = -1; | 
|  |  | 
|  | for (;;) { | 
|  | hPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE, | 
|  | 0, NULL, OPEN_EXISTING, 0, NULL); | 
|  | if (hPipe != INVALID_HANDLE_VALUE) | 
|  | break; | 
|  |  | 
|  | gle = GetLastError(); | 
|  |  | 
|  | switch (gle) { | 
|  | case ERROR_FILE_NOT_FOUND: | 
|  | if (!options->wait_if_not_found) | 
|  | return IPC_STATE__PATH_NOT_FOUND; | 
|  | if (!timeout_ms) | 
|  | return IPC_STATE__PATH_NOT_FOUND; | 
|  |  | 
|  | step_ms = (timeout_ms < WAIT_STEP_MS) ? | 
|  | timeout_ms : WAIT_STEP_MS; | 
|  | sleep_millisec(step_ms); | 
|  |  | 
|  | timeout_ms -= step_ms; | 
|  | break; /* try again */ | 
|  |  | 
|  | case ERROR_PIPE_BUSY: | 
|  | if (!options->wait_if_busy) | 
|  | return IPC_STATE__NOT_LISTENING; | 
|  | if (!timeout_ms) | 
|  | return IPC_STATE__NOT_LISTENING; | 
|  |  | 
|  | t_start_ms = (DWORD)(getnanotime() / 1000000); | 
|  |  | 
|  | if (!WaitNamedPipeW(wpath, timeout_ms)) { | 
|  | DWORD gleWait = GetLastError(); | 
|  |  | 
|  | if (gleWait == ERROR_SEM_TIMEOUT) | 
|  | return IPC_STATE__NOT_LISTENING; | 
|  |  | 
|  | trace2_data_intmax("ipc-debug", NULL, | 
|  | "connect/waitpipe/gle", | 
|  | (intmax_t)gleWait); | 
|  |  | 
|  | return IPC_STATE__OTHER_ERROR; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * A pipe server instance became available. | 
|  | * Race other client processes to connect to | 
|  | * it. | 
|  | * | 
|  | * But first decrement our overall timeout so | 
|  | * that we don't starve if we keep losing the | 
|  | * race.  But also guard against special | 
|  | * NPMWAIT_ values (0 and -1). | 
|  | */ | 
|  | t_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms; | 
|  | if (t_waited_ms < timeout_ms) | 
|  | timeout_ms -= t_waited_ms; | 
|  | else | 
|  | timeout_ms = 1; | 
|  | break; /* try again */ | 
|  |  | 
|  | default: | 
|  | trace2_data_intmax("ipc-debug", NULL, | 
|  | "connect/createfile/gle", | 
|  | (intmax_t)gle); | 
|  |  | 
|  | return IPC_STATE__OTHER_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) { | 
|  | gle = GetLastError(); | 
|  | trace2_data_intmax("ipc-debug", NULL, | 
|  | "connect/setpipestate/gle", | 
|  | (intmax_t)gle); | 
|  |  | 
|  | CloseHandle(hPipe); | 
|  | return IPC_STATE__OTHER_ERROR; | 
|  | } | 
|  |  | 
|  | *pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY); | 
|  | if (*pfd < 0) { | 
|  | gle = GetLastError(); | 
|  | trace2_data_intmax("ipc-debug", NULL, | 
|  | "connect/openosfhandle/gle", | 
|  | (intmax_t)gle); | 
|  |  | 
|  | CloseHandle(hPipe); | 
|  | return IPC_STATE__OTHER_ERROR; | 
|  | } | 
|  |  | 
|  | /* fd now owns hPipe */ | 
|  |  | 
|  | return IPC_STATE__LISTENING; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The default connection timeout for Windows clients. | 
|  | * | 
|  | * This is not currently part of the ipc_ API (nor the config settings) | 
|  | * because of differences between Windows and other platforms. | 
|  | * | 
|  | * This value was chosen at random. | 
|  | */ | 
|  | #define WINDOWS_CONNECTION_TIMEOUT_MS (30000) | 
|  |  | 
|  | enum ipc_active_state ipc_client_try_connect( | 
|  | const char *path, | 
|  | const struct ipc_client_connect_options *options, | 
|  | struct ipc_client_connection **p_connection) | 
|  | { | 
|  | wchar_t wpath[MAX_PATH]; | 
|  | enum ipc_active_state state = IPC_STATE__OTHER_ERROR; | 
|  | int fd = -1; | 
|  |  | 
|  | *p_connection = NULL; | 
|  |  | 
|  | trace2_region_enter("ipc-client", "try-connect", NULL); | 
|  | trace2_data_string("ipc-client", NULL, "try-connect/path", path); | 
|  |  | 
|  | if (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0) | 
|  | state = IPC_STATE__INVALID_PATH; | 
|  | else | 
|  | state = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS, | 
|  | options, &fd); | 
|  |  | 
|  | trace2_data_intmax("ipc-client", NULL, "try-connect/state", | 
|  | (intmax_t)state); | 
|  | trace2_region_leave("ipc-client", "try-connect", NULL); | 
|  |  | 
|  | if (state == IPC_STATE__LISTENING) { | 
|  | (*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection)); | 
|  | (*p_connection)->fd = fd; | 
|  | } | 
|  |  | 
|  | return state; | 
|  | } | 
|  |  | 
|  | void ipc_client_close_connection(struct ipc_client_connection *connection) | 
|  | { | 
|  | if (!connection) | 
|  | return; | 
|  |  | 
|  | if (connection->fd != -1) | 
|  | close(connection->fd); | 
|  |  | 
|  | free(connection); | 
|  | } | 
|  |  | 
|  | int ipc_client_send_command_to_connection( | 
|  | struct ipc_client_connection *connection, | 
|  | const char *message, size_t message_len, | 
|  | struct strbuf *answer) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | strbuf_setlen(answer, 0); | 
|  |  | 
|  | trace2_region_enter("ipc-client", "send-command", NULL); | 
|  |  | 
|  | if (write_packetized_from_buf_no_flush(message, message_len, | 
|  | connection->fd) < 0 || | 
|  | packet_flush_gently(connection->fd) < 0) { | 
|  | ret = error(_("could not send IPC command")); | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | FlushFileBuffers((HANDLE)_get_osfhandle(connection->fd)); | 
|  |  | 
|  | if (read_packetized_to_strbuf( | 
|  | connection->fd, answer, | 
|  | PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) { | 
|  | ret = error(_("could not read IPC response")); | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | done: | 
|  | trace2_region_leave("ipc-client", "send-command", NULL); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int ipc_client_send_command(const char *path, | 
|  | const struct ipc_client_connect_options *options, | 
|  | const char *message, size_t message_len, | 
|  | struct strbuf *response) | 
|  | { | 
|  | int ret = -1; | 
|  | enum ipc_active_state state; | 
|  | struct ipc_client_connection *connection = NULL; | 
|  |  | 
|  | state = ipc_client_try_connect(path, options, &connection); | 
|  |  | 
|  | if (state != IPC_STATE__LISTENING) | 
|  | return ret; | 
|  |  | 
|  | ret = ipc_client_send_command_to_connection(connection, | 
|  | message, message_len, | 
|  | response); | 
|  |  | 
|  | ipc_client_close_connection(connection); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Duplicate the given pipe handle and wrap it in a file descriptor so | 
|  | * that we can use pkt-line on it. | 
|  | */ | 
|  | static int dup_fd_from_pipe(const HANDLE pipe) | 
|  | { | 
|  | HANDLE process = GetCurrentProcess(); | 
|  | HANDLE handle; | 
|  | int fd; | 
|  |  | 
|  | if (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE, | 
|  | DUPLICATE_SAME_ACCESS)) { | 
|  | errno = err_win_to_posix(GetLastError()); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | fd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY); | 
|  | if (fd < 0) { | 
|  | errno = err_win_to_posix(GetLastError()); | 
|  | CloseHandle(handle); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * `handle` is now owned by `fd` and will be automatically closed | 
|  | * when the descriptor is closed. | 
|  | */ | 
|  |  | 
|  | return fd; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Magic numbers used to annotate callback instance data. | 
|  | * These are used to help guard against accidentally passing the | 
|  | * wrong instance data across multiple levels of callbacks (which | 
|  | * is easy to do if there are `void*` arguments). | 
|  | */ | 
|  | enum magic { | 
|  | MAGIC_SERVER_REPLY_DATA, | 
|  | MAGIC_SERVER_THREAD_DATA, | 
|  | MAGIC_SERVER_DATA, | 
|  | }; | 
|  |  | 
|  | struct ipc_server_reply_data { | 
|  | enum magic magic; | 
|  | int fd; | 
|  | struct ipc_server_thread_data *server_thread_data; | 
|  | }; | 
|  |  | 
|  | struct ipc_server_thread_data { | 
|  | enum magic magic; | 
|  | struct ipc_server_thread_data *next_thread; | 
|  | struct ipc_server_data *server_data; | 
|  | pthread_t pthread_id; | 
|  | HANDLE hPipe; | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * On Windows, the conceptual "ipc-server" is implemented as a pool of | 
|  | * n idential/peer "server-thread" threads.  That is, there is no | 
|  | * hierarchy of threads; and therefore no controller thread managing | 
|  | * the pool.  Each thread has an independent handle to the named pipe, | 
|  | * receives incoming connections, processes the client, and re-uses | 
|  | * the pipe for the next client connection. | 
|  | * | 
|  | * Therefore, the "ipc-server" only needs to maintain a list of the | 
|  | * spawned threads for eventual "join" purposes. | 
|  | * | 
|  | * A single "stop-event" is visible to all of the server threads to | 
|  | * tell them to shutdown (when idle). | 
|  | */ | 
|  | struct ipc_server_data { | 
|  | enum magic magic; | 
|  | ipc_server_application_cb *application_cb; | 
|  | void *application_data; | 
|  | struct strbuf buf_path; | 
|  | wchar_t wpath[MAX_PATH]; | 
|  |  | 
|  | HANDLE hEventStopRequested; | 
|  | struct ipc_server_thread_data *thread_list; | 
|  | int is_stopped; | 
|  |  | 
|  | pthread_mutex_t startup_barrier; | 
|  | int started; | 
|  | }; | 
|  |  | 
|  | enum connect_result { | 
|  | CR_CONNECTED = 0, | 
|  | CR_CONNECT_PENDING, | 
|  | CR_CONNECT_ERROR, | 
|  | CR_WAIT_ERROR, | 
|  | CR_SHUTDOWN, | 
|  | }; | 
|  |  | 
|  | static enum connect_result queue_overlapped_connect( | 
|  | struct ipc_server_thread_data *server_thread_data, | 
|  | OVERLAPPED *lpo) | 
|  | { | 
|  | if (ConnectNamedPipe(server_thread_data->hPipe, lpo)) | 
|  | goto failed; | 
|  |  | 
|  | switch (GetLastError()) { | 
|  | case ERROR_IO_PENDING: | 
|  | return CR_CONNECT_PENDING; | 
|  |  | 
|  | case ERROR_PIPE_CONNECTED: | 
|  | SetEvent(lpo->hEvent); | 
|  | return CR_CONNECTED; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | failed: | 
|  | error(_("ConnectNamedPipe failed for '%s' (%lu)"), | 
|  | server_thread_data->server_data->buf_path.buf, | 
|  | GetLastError()); | 
|  | return CR_CONNECT_ERROR; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Use Windows Overlapped IO to wait for a connection or for our event | 
|  | * to be signalled. | 
|  | */ | 
|  | static enum connect_result wait_for_connection( | 
|  | struct ipc_server_thread_data *server_thread_data, | 
|  | OVERLAPPED *lpo) | 
|  | { | 
|  | enum connect_result r; | 
|  | HANDLE waitHandles[2]; | 
|  | DWORD dwWaitResult; | 
|  |  | 
|  | r = queue_overlapped_connect(server_thread_data, lpo); | 
|  | if (r != CR_CONNECT_PENDING) | 
|  | return r; | 
|  |  | 
|  | waitHandles[0] = server_thread_data->server_data->hEventStopRequested; | 
|  | waitHandles[1] = lpo->hEvent; | 
|  |  | 
|  | dwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE); | 
|  | switch (dwWaitResult) { | 
|  | case WAIT_OBJECT_0 + 0: | 
|  | return CR_SHUTDOWN; | 
|  |  | 
|  | case WAIT_OBJECT_0 + 1: | 
|  | ResetEvent(lpo->hEvent); | 
|  | return CR_CONNECTED; | 
|  |  | 
|  | default: | 
|  | return CR_WAIT_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Forward declare our reply callback function so that any compiler | 
|  | * errors are reported when we actually define the function (in addition | 
|  | * to any errors reported when we try to pass this callback function as | 
|  | * a parameter in a function call).  The former are easier to understand. | 
|  | */ | 
|  | static ipc_server_reply_cb do_io_reply_callback; | 
|  |  | 
|  | /* | 
|  | * Relay application's response message to the client process. | 
|  | * (We do not flush at this point because we allow the caller | 
|  | * to chunk data to the client thru us.) | 
|  | */ | 
|  | static int do_io_reply_callback(struct ipc_server_reply_data *reply_data, | 
|  | const char *response, size_t response_len) | 
|  | { | 
|  | if (reply_data->magic != MAGIC_SERVER_REPLY_DATA) | 
|  | BUG("reply_cb called with wrong instance data"); | 
|  |  | 
|  | return write_packetized_from_buf_no_flush(response, response_len, | 
|  | reply_data->fd); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Receive the request/command from the client and pass it to the | 
|  | * registered request-callback.  The request-callback will compose | 
|  | * a response and call our reply-callback to send it to the client. | 
|  | * | 
|  | * Simple-IPC only contains one round trip, so we flush and close | 
|  | * here after the response. | 
|  | */ | 
|  | static int do_io(struct ipc_server_thread_data *server_thread_data) | 
|  | { | 
|  | struct strbuf buf = STRBUF_INIT; | 
|  | struct ipc_server_reply_data reply_data; | 
|  | int ret = 0; | 
|  |  | 
|  | reply_data.magic = MAGIC_SERVER_REPLY_DATA; | 
|  | reply_data.server_thread_data = server_thread_data; | 
|  |  | 
|  | reply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe); | 
|  | if (reply_data.fd < 0) | 
|  | return error(_("could not create fd from pipe for '%s'"), | 
|  | server_thread_data->server_data->buf_path.buf); | 
|  |  | 
|  | ret = read_packetized_to_strbuf( | 
|  | reply_data.fd, &buf, | 
|  | PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR); | 
|  | if (ret >= 0) { | 
|  | ret = server_thread_data->server_data->application_cb( | 
|  | server_thread_data->server_data->application_data, | 
|  | buf.buf, buf.len, do_io_reply_callback, &reply_data); | 
|  |  | 
|  | packet_flush_gently(reply_data.fd); | 
|  |  | 
|  | FlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd))); | 
|  | } | 
|  | else { | 
|  | /* | 
|  | * The client probably disconnected/shutdown before it | 
|  | * could send a well-formed message.  Ignore it. | 
|  | */ | 
|  | } | 
|  |  | 
|  | strbuf_release(&buf); | 
|  | close(reply_data.fd); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Handle IPC request and response with this connected client.  And reset | 
|  | * the pipe to prepare for the next client. | 
|  | */ | 
|  | static int use_connection(struct ipc_server_thread_data *server_thread_data) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = do_io(server_thread_data); | 
|  |  | 
|  | FlushFileBuffers(server_thread_data->hPipe); | 
|  | DisconnectNamedPipe(server_thread_data->hPipe); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void wait_for_startup_barrier(struct ipc_server_data *server_data) | 
|  | { | 
|  | /* | 
|  | * Temporarily hold the startup_barrier mutex before starting, | 
|  | * which lets us know that it's OK to start serving requests. | 
|  | */ | 
|  | pthread_mutex_lock(&server_data->startup_barrier); | 
|  | pthread_mutex_unlock(&server_data->startup_barrier); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Thread proc for an IPC server worker thread.  It handles a series of | 
|  | * connections from clients.  It cleans and reuses the hPipe between each | 
|  | * client. | 
|  | */ | 
|  | static void *server_thread_proc(void *_server_thread_data) | 
|  | { | 
|  | struct ipc_server_thread_data *server_thread_data = _server_thread_data; | 
|  | HANDLE hEventConnected = INVALID_HANDLE_VALUE; | 
|  | OVERLAPPED oConnect; | 
|  | enum connect_result cr; | 
|  | int ret; | 
|  |  | 
|  | assert(server_thread_data->hPipe != INVALID_HANDLE_VALUE); | 
|  |  | 
|  | trace2_thread_start("ipc-server"); | 
|  | trace2_data_string("ipc-server", NULL, "pipe", | 
|  | server_thread_data->server_data->buf_path.buf); | 
|  |  | 
|  | hEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL); | 
|  |  | 
|  | memset(&oConnect, 0, sizeof(oConnect)); | 
|  | oConnect.hEvent = hEventConnected; | 
|  |  | 
|  | wait_for_startup_barrier(server_thread_data->server_data); | 
|  |  | 
|  | for (;;) { | 
|  | cr = wait_for_connection(server_thread_data, &oConnect); | 
|  |  | 
|  | switch (cr) { | 
|  | case CR_SHUTDOWN: | 
|  | goto finished; | 
|  |  | 
|  | case CR_CONNECTED: | 
|  | ret = use_connection(server_thread_data); | 
|  | if (ret == SIMPLE_IPC_QUIT) { | 
|  | ipc_server_stop_async( | 
|  | server_thread_data->server_data); | 
|  | goto finished; | 
|  | } | 
|  | if (ret > 0) { | 
|  | /* | 
|  | * Ignore (transient) IO errors with this | 
|  | * client and reset for the next client. | 
|  | */ | 
|  | } | 
|  | break; | 
|  |  | 
|  | case CR_CONNECT_PENDING: | 
|  | /* By construction, this should not happen. */ | 
|  | BUG("ipc-server[%s]: unexpeced CR_CONNECT_PENDING", | 
|  | server_thread_data->server_data->buf_path.buf); | 
|  |  | 
|  | case CR_CONNECT_ERROR: | 
|  | case CR_WAIT_ERROR: | 
|  | /* | 
|  | * Ignore these theoretical errors. | 
|  | */ | 
|  | DisconnectNamedPipe(server_thread_data->hPipe); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | BUG("unandled case after wait_for_connection"); | 
|  | } | 
|  | } | 
|  |  | 
|  | finished: | 
|  | CloseHandle(server_thread_data->hPipe); | 
|  | CloseHandle(hEventConnected); | 
|  |  | 
|  | trace2_thread_exit(); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * We need to build a Windows "SECURITY_ATTRIBUTES" object and use it | 
|  | * to apply an ACL when we create the initial instance of the Named | 
|  | * Pipe.  The construction is somewhat involved and consists of | 
|  | * several sequential steps and intermediate objects. | 
|  | * | 
|  | * We use this structure to hold these intermediate pointers so that | 
|  | * we can free them as a group.  (It is unclear from the docs whether | 
|  | * some of these intermediate pointers can be freed before we are | 
|  | * finished using the "lpSA" member.) | 
|  | */ | 
|  | struct my_sa_data | 
|  | { | 
|  | PSID pEveryoneSID; | 
|  | PACL pACL; | 
|  | PSECURITY_DESCRIPTOR pSD; | 
|  | LPSECURITY_ATTRIBUTES lpSA; | 
|  | }; | 
|  |  | 
|  | static void init_sa(struct my_sa_data *d) | 
|  | { | 
|  | memset(d, 0, sizeof(*d)); | 
|  | } | 
|  |  | 
|  | static void release_sa(struct my_sa_data *d) | 
|  | { | 
|  | if (d->pEveryoneSID) | 
|  | FreeSid(d->pEveryoneSID); | 
|  | if (d->pACL) | 
|  | LocalFree(d->pACL); | 
|  | if (d->pSD) | 
|  | LocalFree(d->pSD); | 
|  | if (d->lpSA) | 
|  | LocalFree(d->lpSA); | 
|  |  | 
|  | memset(d, 0, sizeof(*d)); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Create SECURITY_ATTRIBUTES to apply to the initial named pipe.  The | 
|  | * creator of the first server instance gets to set the ACLs on it. | 
|  | * | 
|  | * We allow the well-known group `EVERYONE` to have read+write access | 
|  | * to the named pipe so that clients can send queries to the daemon | 
|  | * and receive the response. | 
|  | * | 
|  | * Normally, this is not necessary since the daemon is usually | 
|  | * automatically started by a foreground command like `git status`, | 
|  | * but in those cases where an elevated Git command started the daemon | 
|  | * (such that the daemon itself runs with elevation), we need to add | 
|  | * the ACL so that non-elevated commands can write to it. | 
|  | * | 
|  | * The following document was helpful: | 
|  | * https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c-- | 
|  | * | 
|  | * Returns d->lpSA set to a SA or NULL. | 
|  | */ | 
|  | static LPSECURITY_ATTRIBUTES get_sa(struct my_sa_data *d) | 
|  | { | 
|  | SID_IDENTIFIER_AUTHORITY sid_auth_world = SECURITY_WORLD_SID_AUTHORITY; | 
|  | #define NR_EA (1) | 
|  | EXPLICIT_ACCESS ea[NR_EA]; | 
|  | DWORD dwResult; | 
|  |  | 
|  | if (!AllocateAndInitializeSid(&sid_auth_world, 1, | 
|  | SECURITY_WORLD_RID, 0,0,0,0,0,0,0, | 
|  | &d->pEveryoneSID)) { | 
|  | DWORD gle = GetLastError(); | 
|  | trace2_data_intmax("ipc-debug", NULL, "alloc-world-sid/gle", | 
|  | (intmax_t)gle); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | memset(ea, 0, NR_EA * sizeof(EXPLICIT_ACCESS)); | 
|  |  | 
|  | ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE; | 
|  | ea[0].grfAccessMode = SET_ACCESS; | 
|  | ea[0].grfInheritance = NO_INHERITANCE; | 
|  | ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; | 
|  | ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; | 
|  | ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; | 
|  | ea[0].Trustee.ptstrName = (LPTSTR)d->pEveryoneSID; | 
|  |  | 
|  | dwResult = SetEntriesInAcl(NR_EA, ea, NULL, &d->pACL); | 
|  | if (dwResult != ERROR_SUCCESS) { | 
|  | DWORD gle = GetLastError(); | 
|  | trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/gle", | 
|  | (intmax_t)gle); | 
|  | trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/dw", | 
|  | (intmax_t)dwResult); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | d->pSD = (PSECURITY_DESCRIPTOR)LocalAlloc( | 
|  | LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); | 
|  | if (!InitializeSecurityDescriptor(d->pSD, SECURITY_DESCRIPTOR_REVISION)) { | 
|  | DWORD gle = GetLastError(); | 
|  | trace2_data_intmax("ipc-debug", NULL, "init-sd/gle", (intmax_t)gle); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | if (!SetSecurityDescriptorDacl(d->pSD, TRUE, d->pACL, FALSE)) { | 
|  | DWORD gle = GetLastError(); | 
|  | trace2_data_intmax("ipc-debug", NULL, "set-sd-dacl/gle", (intmax_t)gle); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | d->lpSA = (LPSECURITY_ATTRIBUTES)LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES)); | 
|  | d->lpSA->nLength = sizeof(SECURITY_ATTRIBUTES); | 
|  | d->lpSA->lpSecurityDescriptor = d->pSD; | 
|  | d->lpSA->bInheritHandle = FALSE; | 
|  |  | 
|  | return d->lpSA; | 
|  |  | 
|  | fail: | 
|  | release_sa(d); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static HANDLE create_new_pipe(wchar_t *wpath, int is_first) | 
|  | { | 
|  | HANDLE hPipe; | 
|  | DWORD dwOpenMode, dwPipeMode; | 
|  | struct my_sa_data my_sa_data; | 
|  |  | 
|  | init_sa(&my_sa_data); | 
|  |  | 
|  | dwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND | | 
|  | FILE_FLAG_OVERLAPPED; | 
|  |  | 
|  | dwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT | | 
|  | PIPE_REJECT_REMOTE_CLIENTS; | 
|  |  | 
|  | if (is_first) { | 
|  | dwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE; | 
|  |  | 
|  | /* | 
|  | * On Windows, the first server pipe instance gets to | 
|  | * set the ACL / Security Attributes on the named | 
|  | * pipe; subsequent instances inherit and cannot | 
|  | * change them. | 
|  | */ | 
|  | get_sa(&my_sa_data); | 
|  | } | 
|  |  | 
|  | hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode, | 
|  | PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, | 
|  | my_sa_data.lpSA); | 
|  |  | 
|  | release_sa(&my_sa_data); | 
|  |  | 
|  | return hPipe; | 
|  | } | 
|  |  | 
|  | int ipc_server_init_async(struct ipc_server_data **returned_server_data, | 
|  | const char *path, const struct ipc_server_opts *opts, | 
|  | ipc_server_application_cb *application_cb, | 
|  | void *application_data) | 
|  | { | 
|  | struct ipc_server_data *server_data; | 
|  | wchar_t wpath[MAX_PATH]; | 
|  | HANDLE hPipeFirst = INVALID_HANDLE_VALUE; | 
|  | int k; | 
|  | int ret = 0; | 
|  | int nr_threads = opts->nr_threads; | 
|  |  | 
|  | *returned_server_data = NULL; | 
|  |  | 
|  | ret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)); | 
|  | if (ret < 0) { | 
|  | errno = EINVAL; | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | hPipeFirst = create_new_pipe(wpath, 1); | 
|  | if (hPipeFirst == INVALID_HANDLE_VALUE) { | 
|  | errno = EADDRINUSE; | 
|  | return -2; | 
|  | } | 
|  |  | 
|  | server_data = xcalloc(1, sizeof(*server_data)); | 
|  | server_data->magic = MAGIC_SERVER_DATA; | 
|  | server_data->application_cb = application_cb; | 
|  | server_data->application_data = application_data; | 
|  | server_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL); | 
|  | strbuf_init(&server_data->buf_path, 0); | 
|  | strbuf_addstr(&server_data->buf_path, path); | 
|  | wcscpy(server_data->wpath, wpath); | 
|  |  | 
|  | /* | 
|  | * Hold the startup_barrier lock so that no threads will progress | 
|  | * until ipc_server_start_async() is called. | 
|  | */ | 
|  | pthread_mutex_init(&server_data->startup_barrier, NULL); | 
|  | pthread_mutex_lock(&server_data->startup_barrier); | 
|  |  | 
|  | if (nr_threads < 1) | 
|  | nr_threads = 1; | 
|  |  | 
|  | for (k = 0; k < nr_threads; k++) { | 
|  | struct ipc_server_thread_data *std; | 
|  |  | 
|  | std = xcalloc(1, sizeof(*std)); | 
|  | std->magic = MAGIC_SERVER_THREAD_DATA; | 
|  | std->server_data = server_data; | 
|  | std->hPipe = INVALID_HANDLE_VALUE; | 
|  |  | 
|  | std->hPipe = (k == 0) | 
|  | ? hPipeFirst | 
|  | : create_new_pipe(server_data->wpath, 0); | 
|  |  | 
|  | if (std->hPipe == INVALID_HANDLE_VALUE) { | 
|  | /* | 
|  | * If we've reached a pipe instance limit for | 
|  | * this path, just use fewer threads. | 
|  | */ | 
|  | free(std); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (pthread_create(&std->pthread_id, NULL, | 
|  | server_thread_proc, std)) { | 
|  | /* | 
|  | * Likewise, if we're out of threads, just use | 
|  | * fewer threads than requested. | 
|  | * | 
|  | * However, we just give up if we can't even get | 
|  | * one thread.  This should not happen. | 
|  | */ | 
|  | if (k == 0) | 
|  | die(_("could not start thread[0] for '%s'"), | 
|  | path); | 
|  |  | 
|  | CloseHandle(std->hPipe); | 
|  | free(std); | 
|  | break; | 
|  | } | 
|  |  | 
|  | std->next_thread = server_data->thread_list; | 
|  | server_data->thread_list = std; | 
|  | } | 
|  |  | 
|  | *returned_server_data = server_data; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void ipc_server_start_async(struct ipc_server_data *server_data) | 
|  | { | 
|  | if (!server_data || server_data->started) | 
|  | return; | 
|  |  | 
|  | server_data->started = 1; | 
|  | pthread_mutex_unlock(&server_data->startup_barrier); | 
|  | } | 
|  |  | 
|  | int ipc_server_stop_async(struct ipc_server_data *server_data) | 
|  | { | 
|  | if (!server_data) | 
|  | return 0; | 
|  |  | 
|  | /* | 
|  | * Gently tell all of the ipc_server threads to shutdown. | 
|  | * This will be seen the next time they are idle (and waiting | 
|  | * for a connection). | 
|  | * | 
|  | * We DO NOT attempt to force them to drop an active connection. | 
|  | */ | 
|  | SetEvent(server_data->hEventStopRequested); | 
|  |  | 
|  | /* | 
|  | * If we haven't yet told the threads they are allowed to run, | 
|  | * do so now, so they can receive the shutdown event. | 
|  | */ | 
|  | ipc_server_start_async(server_data); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int ipc_server_await(struct ipc_server_data *server_data) | 
|  | { | 
|  | DWORD dwWaitResult; | 
|  |  | 
|  | if (!server_data) | 
|  | return 0; | 
|  |  | 
|  | dwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE); | 
|  | if (dwWaitResult != WAIT_OBJECT_0) | 
|  | return error(_("wait for hEvent failed for '%s'"), | 
|  | server_data->buf_path.buf); | 
|  |  | 
|  | while (server_data->thread_list) { | 
|  | struct ipc_server_thread_data *std = server_data->thread_list; | 
|  |  | 
|  | pthread_join(std->pthread_id, NULL); | 
|  |  | 
|  | server_data->thread_list = std->next_thread; | 
|  | free(std); | 
|  | } | 
|  |  | 
|  | server_data->is_stopped = 1; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void ipc_server_free(struct ipc_server_data *server_data) | 
|  | { | 
|  | if (!server_data) | 
|  | return; | 
|  |  | 
|  | if (!server_data->is_stopped) | 
|  | BUG("cannot free ipc-server while running for '%s'", | 
|  | server_data->buf_path.buf); | 
|  |  | 
|  | strbuf_release(&server_data->buf_path); | 
|  |  | 
|  | if (server_data->hEventStopRequested != INVALID_HANDLE_VALUE) | 
|  | CloseHandle(server_data->hEventStopRequested); | 
|  |  | 
|  | while (server_data->thread_list) { | 
|  | struct ipc_server_thread_data *std = server_data->thread_list; | 
|  |  | 
|  | server_data->thread_list = std->next_thread; | 
|  | free(std); | 
|  | } | 
|  |  | 
|  | pthread_mutex_destroy(&server_data->startup_barrier); | 
|  |  | 
|  | free(server_data); | 
|  | } |