 bc5c5ec044
			
		
	
	bc5c5ec044
	
	
	
		
			
			Since this header showed up in some places besides just #include statements, update/clean-up/remove those other places as well. Note that compat/fsmonitor/fsm-path-utils-darwin.c previously got away with violating the rule that all files must start with an include of git-compat-util.h (or a short-list of alternate headers that happen to include it first). This change exposed the violation and caused it to stop building correctly; fix it by having it include git-compat-util.h first, as per policy. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
		
			
				
	
	
		
			905 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			905 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #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;
 | |
| };
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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;
 | |
| 
 | |
| 	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_run_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);
 | |
| 
 | |
| 	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;
 | |
| }
 | |
| 
 | |
| 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);
 | |
| 	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);
 | |
| 	}
 | |
| 
 | |
| 	free(server_data);
 | |
| }
 |