summaryrefslogtreecommitdiff
path: root/compat
diff options
context:
space:
mode:
Diffstat (limited to 'compat')
-rw-r--r--compat/.gitattributes1
-rw-r--r--compat/compiler.h1
-rw-r--r--compat/disk.h57
-rw-r--r--compat/fsmonitor/fsm-darwin-gcc.h90
-rw-r--r--compat/fsmonitor/fsm-health-darwin.c24
-rw-r--r--compat/fsmonitor/fsm-health-win32.c280
-rw-r--r--compat/fsmonitor/fsm-health.h47
-rw-r--r--compat/fsmonitor/fsm-ipc-darwin.c56
-rw-r--r--compat/fsmonitor/fsm-ipc-win32.c11
-rw-r--r--compat/fsmonitor/fsm-listen-darwin.c540
-rw-r--r--compat/fsmonitor/fsm-listen-win32.c873
-rw-r--r--compat/fsmonitor/fsm-listen.h49
-rw-r--r--compat/fsmonitor/fsm-path-utils-darwin.c138
-rw-r--r--compat/fsmonitor/fsm-path-utils-win32.c148
-rw-r--r--compat/fsmonitor/fsm-settings-darwin.c63
-rw-r--r--compat/fsmonitor/fsm-settings-win32.c37
-rw-r--r--compat/linux/procinfo.c176
-rw-r--r--compat/mingw.c340
-rw-r--r--compat/mingw.h27
-rw-r--r--compat/mkdir.c2
-rw-r--r--compat/mmap.c9
-rw-r--r--compat/nedmalloc/nedmalloc.c3
-rw-r--r--compat/nonblock.c50
-rw-r--r--compat/nonblock.h9
-rw-r--r--compat/open.c25
-rw-r--r--compat/pread.c1
-rw-r--r--compat/precompose_utf8.c57
-rw-r--r--compat/precompose_utf8.h3
-rw-r--r--compat/qsort_s.c14
-rw-r--r--compat/regcomp_enhanced.c9
-rw-r--r--compat/sha1-chunked.c3
-rw-r--r--compat/simple-ipc/ipc-shared.c29
-rw-r--r--compat/simple-ipc/ipc-unix-socket.c1030
-rw-r--r--compat/simple-ipc/ipc-win32.c904
-rw-r--r--compat/stub/procinfo.c11
-rw-r--r--compat/terminal.c331
-rw-r--r--compat/terminal.h18
-rw-r--r--compat/unsetenv.c4
-rw-r--r--compat/vcbuild/README2
-rw-r--r--compat/win32/flush.c28
-rw-r--r--compat/win32/headless.c115
-rw-r--r--compat/win32/lazyload.h18
-rw-r--r--compat/win32/path-utils.h6
-rw-r--r--compat/win32/pthread.c25
-rw-r--r--compat/win32/pthread.h2
-rw-r--r--compat/win32/syslog.c7
-rw-r--r--compat/win32/trace2_win32_process_info.c12
-rw-r--r--compat/winansi.c8
-rw-r--r--compat/zlib-uncompress2.c96
49 files changed, 5633 insertions, 156 deletions
diff --git a/compat/.gitattributes b/compat/.gitattributes
new file mode 100644
index 0000000..40dbfb1
--- /dev/null
+++ b/compat/.gitattributes
@@ -0,0 +1 @@
+/zlib-uncompress2.c whitespace=-indent-with-non-tab,-trailing-space
diff --git a/compat/compiler.h b/compat/compiler.h
index 10dbb65..e9ad9db 100644
--- a/compat/compiler.h
+++ b/compat/compiler.h
@@ -1,7 +1,6 @@
#ifndef COMPILER_H
#define COMPILER_H
-#include "git-compat-util.h"
#include "strbuf.h"
#ifdef __GLIBC__
diff --git a/compat/disk.h b/compat/disk.h
new file mode 100644
index 0000000..23bc1be
--- /dev/null
+++ b/compat/disk.h
@@ -0,0 +1,57 @@
+#ifndef COMPAT_DISK_H
+#define COMPAT_DISK_H
+
+#include "abspath.h"
+#include "gettext.h"
+
+static int get_disk_info(struct strbuf *out)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int res = 0;
+
+#ifdef GIT_WINDOWS_NATIVE
+ char volume_name[MAX_PATH], fs_name[MAX_PATH];
+ DWORD serial_number, component_length, flags;
+ ULARGE_INTEGER avail2caller, total, avail;
+
+ strbuf_realpath(&buf, ".", 1);
+ if (!GetDiskFreeSpaceExA(buf.buf, &avail2caller, &total, &avail)) {
+ error(_("could not determine free disk size for '%s'"),
+ buf.buf);
+ res = -1;
+ goto cleanup;
+ }
+
+ strbuf_setlen(&buf, offset_1st_component(buf.buf));
+ if (!GetVolumeInformationA(buf.buf, volume_name, sizeof(volume_name),
+ &serial_number, &component_length, &flags,
+ fs_name, sizeof(fs_name))) {
+ error(_("could not get info for '%s'"), buf.buf);
+ res = -1;
+ goto cleanup;
+ }
+ strbuf_addf(out, "Available space on '%s': ", buf.buf);
+ strbuf_humanise_bytes(out, avail2caller.QuadPart);
+ strbuf_addch(out, '\n');
+#else
+ struct statvfs stat;
+
+ strbuf_realpath(&buf, ".", 1);
+ if (statvfs(buf.buf, &stat) < 0) {
+ error_errno(_("could not determine free disk size for '%s'"),
+ buf.buf);
+ res = -1;
+ goto cleanup;
+ }
+
+ strbuf_addf(out, "Available space on '%s': ", buf.buf);
+ strbuf_humanise_bytes(out, (off_t)stat.f_bsize * (off_t)stat.f_bavail);
+ strbuf_addf(out, " (mount flags 0x%lx)\n", stat.f_flag);
+#endif
+
+cleanup:
+ strbuf_release(&buf);
+ return res;
+}
+
+#endif /* COMPAT_DISK_H */
diff --git a/compat/fsmonitor/fsm-darwin-gcc.h b/compat/fsmonitor/fsm-darwin-gcc.h
new file mode 100644
index 0000000..3496e29
--- /dev/null
+++ b/compat/fsmonitor/fsm-darwin-gcc.h
@@ -0,0 +1,90 @@
+#ifndef FSM_DARWIN_GCC_H
+#define FSM_DARWIN_GCC_H
+
+#ifndef __clang__
+/*
+ * It is possible to #include CoreFoundation/CoreFoundation.h when compiling
+ * with clang, but not with GCC as of time of writing.
+ *
+ * See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93082 for details.
+ */
+typedef unsigned int FSEventStreamCreateFlags;
+#define kFSEventStreamEventFlagNone 0x00000000
+#define kFSEventStreamEventFlagMustScanSubDirs 0x00000001
+#define kFSEventStreamEventFlagUserDropped 0x00000002
+#define kFSEventStreamEventFlagKernelDropped 0x00000004
+#define kFSEventStreamEventFlagEventIdsWrapped 0x00000008
+#define kFSEventStreamEventFlagHistoryDone 0x00000010
+#define kFSEventStreamEventFlagRootChanged 0x00000020
+#define kFSEventStreamEventFlagMount 0x00000040
+#define kFSEventStreamEventFlagUnmount 0x00000080
+#define kFSEventStreamEventFlagItemCreated 0x00000100
+#define kFSEventStreamEventFlagItemRemoved 0x00000200
+#define kFSEventStreamEventFlagItemInodeMetaMod 0x00000400
+#define kFSEventStreamEventFlagItemRenamed 0x00000800
+#define kFSEventStreamEventFlagItemModified 0x00001000
+#define kFSEventStreamEventFlagItemFinderInfoMod 0x00002000
+#define kFSEventStreamEventFlagItemChangeOwner 0x00004000
+#define kFSEventStreamEventFlagItemXattrMod 0x00008000
+#define kFSEventStreamEventFlagItemIsFile 0x00010000
+#define kFSEventStreamEventFlagItemIsDir 0x00020000
+#define kFSEventStreamEventFlagItemIsSymlink 0x00040000
+#define kFSEventStreamEventFlagOwnEvent 0x00080000
+#define kFSEventStreamEventFlagItemIsHardlink 0x00100000
+#define kFSEventStreamEventFlagItemIsLastHardlink 0x00200000
+#define kFSEventStreamEventFlagItemCloned 0x00400000
+
+typedef struct __FSEventStream *FSEventStreamRef;
+typedef const FSEventStreamRef ConstFSEventStreamRef;
+
+typedef unsigned int CFStringEncoding;
+#define kCFStringEncodingUTF8 0x08000100
+
+typedef const struct __CFString *CFStringRef;
+typedef const struct __CFArray *CFArrayRef;
+typedef const struct __CFRunLoop *CFRunLoopRef;
+
+struct FSEventStreamContext {
+ long long version;
+ void *cb_data, *retain, *release, *copy_description;
+};
+
+typedef struct FSEventStreamContext FSEventStreamContext;
+typedef unsigned int FSEventStreamEventFlags;
+#define kFSEventStreamCreateFlagNoDefer 0x02
+#define kFSEventStreamCreateFlagWatchRoot 0x04
+#define kFSEventStreamCreateFlagFileEvents 0x10
+
+typedef unsigned long long FSEventStreamEventId;
+#define kFSEventStreamEventIdSinceNow 0xFFFFFFFFFFFFFFFFULL
+
+typedef void (*FSEventStreamCallback)(ConstFSEventStreamRef streamRef,
+ void *context,
+ __SIZE_TYPE__ num_of_events,
+ void *event_paths,
+ const FSEventStreamEventFlags event_flags[],
+ const FSEventStreamEventId event_ids[]);
+typedef double CFTimeInterval;
+FSEventStreamRef FSEventStreamCreate(void *allocator,
+ FSEventStreamCallback callback,
+ FSEventStreamContext *context,
+ CFArrayRef paths_to_watch,
+ FSEventStreamEventId since_when,
+ CFTimeInterval latency,
+ FSEventStreamCreateFlags flags);
+CFStringRef CFStringCreateWithCString(void *allocator, const char *string,
+ CFStringEncoding encoding);
+CFArrayRef CFArrayCreate(void *allocator, const void **items, long long count,
+ void *callbacks);
+void CFRunLoopRun(void);
+void CFRunLoopStop(CFRunLoopRef run_loop);
+CFRunLoopRef CFRunLoopGetCurrent(void);
+extern CFStringRef kCFRunLoopDefaultMode;
+void FSEventStreamSetDispatchQueue(FSEventStreamRef stream, dispatch_queue_t q);
+unsigned char FSEventStreamStart(FSEventStreamRef stream);
+void FSEventStreamStop(FSEventStreamRef stream);
+void FSEventStreamInvalidate(FSEventStreamRef stream);
+void FSEventStreamRelease(FSEventStreamRef stream);
+
+#endif /* !clang */
+#endif /* FSM_DARWIN_GCC_H */
diff --git a/compat/fsmonitor/fsm-health-darwin.c b/compat/fsmonitor/fsm-health-darwin.c
new file mode 100644
index 0000000..c2afcbe
--- /dev/null
+++ b/compat/fsmonitor/fsm-health-darwin.c
@@ -0,0 +1,24 @@
+#include "git-compat-util.h"
+#include "config.h"
+#include "fsmonitor-ll.h"
+#include "fsm-health.h"
+#include "fsmonitor--daemon.h"
+
+int fsm_health__ctor(struct fsmonitor_daemon_state *state UNUSED)
+{
+ return 0;
+}
+
+void fsm_health__dtor(struct fsmonitor_daemon_state *state UNUSED)
+{
+ return;
+}
+
+void fsm_health__loop(struct fsmonitor_daemon_state *state UNUSED)
+{
+ return;
+}
+
+void fsm_health__stop_async(struct fsmonitor_daemon_state *state UNUSED)
+{
+}
diff --git a/compat/fsmonitor/fsm-health-win32.c b/compat/fsmonitor/fsm-health-win32.c
new file mode 100644
index 0000000..2aa8c21
--- /dev/null
+++ b/compat/fsmonitor/fsm-health-win32.c
@@ -0,0 +1,280 @@
+#include "git-compat-util.h"
+#include "config.h"
+#include "fsmonitor-ll.h"
+#include "fsm-health.h"
+#include "fsmonitor--daemon.h"
+#include "gettext.h"
+#include "simple-ipc.h"
+
+/*
+ * Every minute wake up and test our health.
+ */
+#define WAIT_FREQ_MS (60 * 1000)
+
+/*
+ * State machine states for each of the interval functions
+ * used for polling our health.
+ */
+enum interval_fn_ctx {
+ CTX_INIT = 0,
+ CTX_TERM,
+ CTX_TIMER
+};
+
+typedef int (interval_fn)(struct fsmonitor_daemon_state *state,
+ enum interval_fn_ctx ctx);
+
+struct fsm_health_data
+{
+ HANDLE hEventShutdown;
+
+ HANDLE hHandles[1]; /* the array does not own these handles */
+#define HEALTH_SHUTDOWN 0
+ int nr_handles; /* number of active event handles */
+
+ struct wt_moved
+ {
+ wchar_t wpath[MAX_PATH + 1];
+ BY_HANDLE_FILE_INFORMATION bhfi;
+ } wt_moved;
+};
+
+/*
+ * Lookup the system unique ID for the path. This is as close as
+ * we get to an inode number, but this also contains volume info,
+ * so it is a little stronger.
+ */
+static int lookup_bhfi(wchar_t *wpath,
+ BY_HANDLE_FILE_INFORMATION *bhfi)
+{
+ DWORD desired_access = FILE_LIST_DIRECTORY;
+ DWORD share_mode =
+ FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
+ HANDLE hDir;
+
+ hDir = CreateFileW(wpath, desired_access, share_mode, NULL,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ if (hDir == INVALID_HANDLE_VALUE) {
+ error(_("[GLE %ld] health thread could not open '%ls'"),
+ GetLastError(), wpath);
+ return -1;
+ }
+
+ if (!GetFileInformationByHandle(hDir, bhfi)) {
+ error(_("[GLE %ld] health thread getting BHFI for '%ls'"),
+ GetLastError(), wpath);
+ CloseHandle(hDir);
+ return -1;
+ }
+
+ CloseHandle(hDir);
+ return 0;
+}
+
+/*
+ * Compare the relevant fields from two system unique IDs.
+ * We use this to see if two different handles to the same
+ * path actually refer to the same *instance* of the file
+ * or directory.
+ */
+static int bhfi_eq(const BY_HANDLE_FILE_INFORMATION *bhfi_1,
+ const BY_HANDLE_FILE_INFORMATION *bhfi_2)
+{
+ return (bhfi_1->dwVolumeSerialNumber == bhfi_2->dwVolumeSerialNumber &&
+ bhfi_1->nFileIndexHigh == bhfi_2->nFileIndexHigh &&
+ bhfi_1->nFileIndexLow == bhfi_2->nFileIndexLow);
+}
+
+/*
+ * Shutdown if the original worktree root directory been deleted,
+ * moved, or renamed?
+ *
+ * Since the main thread did a "chdir(getenv($HOME))" and our CWD
+ * is not in the worktree root directory and because the listener
+ * thread added FILE_SHARE_DELETE to the watch handle, it is possible
+ * for the root directory to be moved or deleted while we are still
+ * watching it. We want to detect that here and force a shutdown.
+ *
+ * Granted, a delete MAY cause some operations to fail, such as
+ * GetOverlappedResult(), but it is not guaranteed. And because
+ * ReadDirectoryChangesW() only reports on changes *WITHIN* the
+ * directory, not changes *ON* the directory, our watch will not
+ * receive a delete event for it.
+ *
+ * A move/rename of the worktree root will also not generate an event.
+ * And since the listener thread already has an open handle, it may
+ * continue to receive events for events within the directory.
+ * However, the pathname of the named-pipe was constructed using the
+ * original location of the worktree root. (Remember named-pipes are
+ * stored in the NPFS and not in the actual file system.) Clients
+ * trying to talk to the worktree after the move/rename will not
+ * reach our daemon process, since we're still listening on the
+ * pipe with original path.
+ *
+ * Furthermore, if the user does something like:
+ *
+ * $ mv repo repo.old
+ * $ git init repo
+ *
+ * A new daemon cannot be started in the new instance of "repo"
+ * because the named-pipe is still being used by the daemon on
+ * the original instance.
+ *
+ * So, detect move/rename/delete and shutdown. This should also
+ * handle unsafe drive removal.
+ *
+ * We use the file system unique ID to distinguish the original
+ * directory instance from a new instance and force a shutdown
+ * if the unique ID changes.
+ *
+ * Since a worktree move/rename/delete/unmount doesn't happen
+ * that often (and we can't get an immediate event anyway), we
+ * use a timeout and periodically poll it.
+ */
+static int has_worktree_moved(struct fsmonitor_daemon_state *state,
+ enum interval_fn_ctx ctx)
+{
+ struct fsm_health_data *data = state->health_data;
+ BY_HANDLE_FILE_INFORMATION bhfi;
+ int r;
+
+ switch (ctx) {
+ case CTX_TERM:
+ return 0;
+
+ case CTX_INIT:
+ if (xutftowcs_path(data->wt_moved.wpath,
+ state->path_worktree_watch.buf) < 0) {
+ error(_("could not convert to wide characters: '%s'"),
+ state->path_worktree_watch.buf);
+ return -1;
+ }
+
+ /*
+ * On the first call we lookup the unique sequence ID for
+ * the worktree root directory.
+ */
+ return lookup_bhfi(data->wt_moved.wpath, &data->wt_moved.bhfi);
+
+ case CTX_TIMER:
+ r = lookup_bhfi(data->wt_moved.wpath, &bhfi);
+ if (r)
+ return r;
+ if (!bhfi_eq(&data->wt_moved.bhfi, &bhfi)) {
+ error(_("BHFI changed '%ls'"), data->wt_moved.wpath);
+ return -1;
+ }
+ return 0;
+
+ default:
+ die(_("unhandled case in 'has_worktree_moved': %d"),
+ (int)ctx);
+ }
+
+ return 0;
+}
+
+
+int fsm_health__ctor(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_health_data *data;
+
+ CALLOC_ARRAY(data, 1);
+
+ data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ data->hHandles[HEALTH_SHUTDOWN] = data->hEventShutdown;
+ data->nr_handles++;
+
+ state->health_data = data;
+ return 0;
+}
+
+void fsm_health__dtor(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_health_data *data;
+
+ if (!state || !state->health_data)
+ return;
+
+ data = state->health_data;
+
+ CloseHandle(data->hEventShutdown);
+
+ FREE_AND_NULL(state->health_data);
+}
+
+/*
+ * A table of the polling functions.
+ */
+static interval_fn *table[] = {
+ has_worktree_moved,
+ NULL, /* must be last */
+};
+
+/*
+ * Call all of the polling functions in the table.
+ * Shortcut and return first error.
+ *
+ * Return 0 if all succeeded.
+ */
+static int call_all(struct fsmonitor_daemon_state *state,
+ enum interval_fn_ctx ctx)
+{
+ int k;
+
+ for (k = 0; table[k]; k++) {
+ int r = table[k](state, ctx);
+ if (r)
+ return r;
+ }
+
+ return 0;
+}
+
+void fsm_health__loop(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_health_data *data = state->health_data;
+ int r;
+
+ r = call_all(state, CTX_INIT);
+ if (r < 0)
+ goto force_error_stop;
+ if (r > 0)
+ goto force_shutdown;
+
+ for (;;) {
+ DWORD dwWait = WaitForMultipleObjects(data->nr_handles,
+ data->hHandles,
+ FALSE, WAIT_FREQ_MS);
+
+ if (dwWait == WAIT_OBJECT_0 + HEALTH_SHUTDOWN)
+ goto clean_shutdown;
+
+ if (dwWait == WAIT_TIMEOUT) {
+ r = call_all(state, CTX_TIMER);
+ if (r < 0)
+ goto force_error_stop;
+ if (r > 0)
+ goto force_shutdown;
+ continue;
+ }
+
+ error(_("health thread wait failed [GLE %ld]"),
+ GetLastError());
+ goto force_error_stop;
+ }
+
+force_error_stop:
+ state->health_error_code = -1;
+force_shutdown:
+ ipc_server_stop_async(state->ipc_server_data);
+clean_shutdown:
+ call_all(state, CTX_TERM);
+ return;
+}
+
+void fsm_health__stop_async(struct fsmonitor_daemon_state *state)
+{
+ SetEvent(state->health_data->hHandles[HEALTH_SHUTDOWN]);
+}
diff --git a/compat/fsmonitor/fsm-health.h b/compat/fsmonitor/fsm-health.h
new file mode 100644
index 0000000..45547ba
--- /dev/null
+++ b/compat/fsmonitor/fsm-health.h
@@ -0,0 +1,47 @@
+#ifndef FSM_HEALTH_H
+#define FSM_HEALTH_H
+
+/* This needs to be implemented by each backend */
+
+#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
+
+struct fsmonitor_daemon_state;
+
+/*
+ * Initialize platform-specific data for the fsmonitor health thread.
+ * This will be called from the main thread PRIOR to staring the
+ * thread.
+ *
+ * Returns 0 if successful.
+ * Returns -1 otherwise.
+ */
+int fsm_health__ctor(struct fsmonitor_daemon_state *state);
+
+/*
+ * Cleanup platform-specific data for the health thread.
+ * This will be called from the main thread AFTER joining the thread.
+ */
+void fsm_health__dtor(struct fsmonitor_daemon_state *state);
+
+/*
+ * The main body of the platform-specific event loop to monitor the
+ * health of the daemon process. This will run in the health thread.
+ *
+ * The health thread should call `ipc_server_stop_async()` if it needs
+ * to cause a shutdown. (It should NOT do so if it receives a shutdown
+ * shutdown signal.)
+ *
+ * It should set `state->health_error_code` to -1 if the daemon should exit
+ * with an error.
+ */
+void fsm_health__loop(struct fsmonitor_daemon_state *state);
+
+/*
+ * Gently request that the health thread shutdown.
+ * It does not wait for it to stop. The caller should do a JOIN
+ * to wait for it.
+ */
+void fsm_health__stop_async(struct fsmonitor_daemon_state *state);
+
+#endif /* HAVE_FSMONITOR_DAEMON_BACKEND */
+#endif /* FSM_HEALTH_H */
diff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-darwin.c
new file mode 100644
index 0000000..6f3a954
--- /dev/null
+++ b/compat/fsmonitor/fsm-ipc-darwin.c
@@ -0,0 +1,56 @@
+#include "git-compat-util.h"
+#include "config.h"
+#include "gettext.h"
+#include "hex.h"
+#include "path.h"
+#include "repository.h"
+#include "strbuf.h"
+#include "fsmonitor-ll.h"
+#include "fsmonitor-ipc.h"
+#include "fsmonitor-path-utils.h"
+
+static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, "fsmonitor--daemon.ipc")
+
+const char *fsmonitor_ipc__get_path(struct repository *r)
+{
+ static const char *ipc_path = NULL;
+ git_SHA_CTX sha1ctx;
+ char *sock_dir = NULL;
+ struct strbuf ipc_file = STRBUF_INIT;
+ unsigned char hash[GIT_MAX_RAWSZ];
+
+ if (!r)
+ BUG("No repository passed into fsmonitor_ipc__get_path");
+
+ if (ipc_path)
+ return ipc_path;
+
+
+ /* By default the socket file is created in the .git directory */
+ if (fsmonitor__is_fs_remote(r->gitdir) < 1) {
+ ipc_path = fsmonitor_ipc__get_default_path();
+ return ipc_path;
+ }
+
+ git_SHA1_Init(&sha1ctx);
+ git_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));
+ git_SHA1_Final(hash, &sha1ctx);
+
+ repo_config_get_string(r, "fsmonitor.socketdir", &sock_dir);
+
+ /* Create the socket file in either socketDir or $HOME */
+ if (sock_dir && *sock_dir) {
+ strbuf_addf(&ipc_file, "%s/.git-fsmonitor-%s",
+ sock_dir, hash_to_hex(hash));
+ } else {
+ strbuf_addf(&ipc_file, "~/.git-fsmonitor-%s", hash_to_hex(hash));
+ }
+ free(sock_dir);
+
+ ipc_path = interpolate_path(ipc_file.buf, 1);
+ if (!ipc_path)
+ die(_("Invalid path: %s"), ipc_file.buf);
+
+ strbuf_release(&ipc_file);
+ return ipc_path;
+}
diff --git a/compat/fsmonitor/fsm-ipc-win32.c b/compat/fsmonitor/fsm-ipc-win32.c
new file mode 100644
index 0000000..41984ea
--- /dev/null
+++ b/compat/fsmonitor/fsm-ipc-win32.c
@@ -0,0 +1,11 @@
+#include "git-compat-util.h"
+#include "config.h"
+#include "fsmonitor-ipc.h"
+#include "path.h"
+
+const char *fsmonitor_ipc__get_path(struct repository *r) {
+ static char *ret;
+ if (!ret)
+ ret = repo_git_path(r, "fsmonitor--daemon.ipc");
+ return ret;
+}
diff --git a/compat/fsmonitor/fsm-listen-darwin.c b/compat/fsmonitor/fsm-listen-darwin.c
new file mode 100644
index 0000000..2fc6744
--- /dev/null
+++ b/compat/fsmonitor/fsm-listen-darwin.c
@@ -0,0 +1,540 @@
+#ifndef __clang__
+#include <dispatch/dispatch.h>
+#include "fsm-darwin-gcc.h"
+#else
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h>
+
+#ifndef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER
+/*
+ * This enum value was added in 10.13 to:
+ *
+ * /Applications/Xcode.app/Contents/Developer/Platforms/ \
+ * MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/ \
+ * Library/Frameworks/CoreServices.framework/Frameworks/ \
+ * FSEvents.framework/Versions/Current/Headers/FSEvents.h
+ *
+ * If we're compiling against an older SDK, this symbol won't be
+ * present. Silently define it here so that we don't have to ifdef
+ * the logging or masking below. This should be harmless since older
+ * versions of macOS won't ever emit this FS event anyway.
+ */
+#define kFSEventStreamEventFlagItemCloned 0x00400000
+#endif
+#endif
+
+#include "git-compat-util.h"
+#include "fsmonitor-ll.h"
+#include "fsm-listen.h"
+#include "fsmonitor--daemon.h"
+#include "fsmonitor-path-utils.h"
+#include "gettext.h"
+#include "simple-ipc.h"
+#include "string-list.h"
+#include "trace.h"
+
+struct fsm_listen_data
+{
+ CFStringRef cfsr_worktree_path;
+ CFStringRef cfsr_gitdir_path;
+
+ CFArrayRef cfar_paths_to_watch;
+ int nr_paths_watching;
+
+ FSEventStreamRef stream;
+
+ dispatch_queue_t dq;
+ pthread_cond_t dq_finished;
+ pthread_mutex_t dq_lock;
+
+ enum shutdown_style {
+ SHUTDOWN_EVENT = 0,
+ FORCE_SHUTDOWN,
+ FORCE_ERROR_STOP,
+ } shutdown_style;
+
+ unsigned int stream_scheduled:1;
+ unsigned int stream_started:1;
+};
+
+static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)
+{
+ struct strbuf msg = STRBUF_INIT;
+
+ if (flag & kFSEventStreamEventFlagMustScanSubDirs)
+ strbuf_addstr(&msg, "MustScanSubDirs|");
+ if (flag & kFSEventStreamEventFlagUserDropped)
+ strbuf_addstr(&msg, "UserDropped|");
+ if (flag & kFSEventStreamEventFlagKernelDropped)
+ strbuf_addstr(&msg, "KernelDropped|");
+ if (flag & kFSEventStreamEventFlagEventIdsWrapped)
+ strbuf_addstr(&msg, "EventIdsWrapped|");
+ if (flag & kFSEventStreamEventFlagHistoryDone)
+ strbuf_addstr(&msg, "HistoryDone|");
+ if (flag & kFSEventStreamEventFlagRootChanged)
+ strbuf_addstr(&msg, "RootChanged|");
+ if (flag & kFSEventStreamEventFlagMount)
+ strbuf_addstr(&msg, "Mount|");
+ if (flag & kFSEventStreamEventFlagUnmount)
+ strbuf_addstr(&msg, "Unmount|");
+ if (flag & kFSEventStreamEventFlagItemChangeOwner)
+ strbuf_addstr(&msg, "ItemChangeOwner|");
+ if (flag & kFSEventStreamEventFlagItemCreated)
+ strbuf_addstr(&msg, "ItemCreated|");
+ if (flag & kFSEventStreamEventFlagItemFinderInfoMod)
+ strbuf_addstr(&msg, "ItemFinderInfoMod|");
+ if (flag & kFSEventStreamEventFlagItemInodeMetaMod)
+ strbuf_addstr(&msg, "ItemInodeMetaMod|");
+ if (flag & kFSEventStreamEventFlagItemIsDir)
+ strbuf_addstr(&msg, "ItemIsDir|");
+ if (flag & kFSEventStreamEventFlagItemIsFile)
+ strbuf_addstr(&msg, "ItemIsFile|");
+ if (flag & kFSEventStreamEventFlagItemIsHardlink)
+ strbuf_addstr(&msg, "ItemIsHardlink|");
+ if (flag & kFSEventStreamEventFlagItemIsLastHardlink)
+ strbuf_addstr(&msg, "ItemIsLastHardlink|");
+ if (flag & kFSEventStreamEventFlagItemIsSymlink)
+ strbuf_addstr(&msg, "ItemIsSymlink|");
+ if (flag & kFSEventStreamEventFlagItemModified)
+ strbuf_addstr(&msg, "ItemModified|");
+ if (flag & kFSEventStreamEventFlagItemRemoved)
+ strbuf_addstr(&msg, "ItemRemoved|");
+ if (flag & kFSEventStreamEventFlagItemRenamed)
+ strbuf_addstr(&msg, "ItemRenamed|");
+ if (flag & kFSEventStreamEventFlagItemXattrMod)
+ strbuf_addstr(&msg, "ItemXattrMod|");
+ if (flag & kFSEventStreamEventFlagOwnEvent)
+ strbuf_addstr(&msg, "OwnEvent|");
+ if (flag & kFSEventStreamEventFlagItemCloned)
+ strbuf_addstr(&msg, "ItemCloned|");
+
+ trace_printf_key(&trace_fsmonitor, "fsevent: '%s', flags=0x%x %s",
+ path, flag, msg.buf);
+
+ strbuf_release(&msg);
+}
+
+static int ef_is_root_changed(const FSEventStreamEventFlags ef)
+{
+ return (ef & kFSEventStreamEventFlagRootChanged);
+}
+
+static int ef_is_root_delete(const FSEventStreamEventFlags ef)
+{
+ return (ef & kFSEventStreamEventFlagItemIsDir &&
+ ef & kFSEventStreamEventFlagItemRemoved);
+}
+
+static int ef_is_root_renamed(const FSEventStreamEventFlags ef)
+{
+ return (ef & kFSEventStreamEventFlagItemIsDir &&
+ ef & kFSEventStreamEventFlagItemRenamed);
+}
+
+static int ef_is_dropped(const FSEventStreamEventFlags ef)
+{
+ return (ef & kFSEventStreamEventFlagMustScanSubDirs ||
+ ef & kFSEventStreamEventFlagKernelDropped ||
+ ef & kFSEventStreamEventFlagUserDropped);
+}
+
+/*
+ * If an `xattr` change is the only reason we received this event,
+ * then silently ignore it. Git doesn't care about xattr's. We
+ * have to be careful here because the kernel can combine multiple
+ * events for a single path. And because events always have certain
+ * bits set, such as `ItemIsFile` or `ItemIsDir`.
+ *
+ * Return 1 if we should ignore it.
+ */
+static int ef_ignore_xattr(const FSEventStreamEventFlags ef)
+{
+ static const FSEventStreamEventFlags mask =
+ kFSEventStreamEventFlagItemChangeOwner |
+ kFSEventStreamEventFlagItemCreated |
+ kFSEventStreamEventFlagItemFinderInfoMod |
+ kFSEventStreamEventFlagItemInodeMetaMod |
+ kFSEventStreamEventFlagItemModified |
+ kFSEventStreamEventFlagItemRemoved |
+ kFSEventStreamEventFlagItemRenamed |
+ kFSEventStreamEventFlagItemXattrMod |
+ kFSEventStreamEventFlagItemCloned;
+
+ return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod);
+}
+
+/*
+ * On MacOS we have to adjust for Unicode composition insensitivity
+ * (where NFC and NFD spellings are not respected). The different
+ * spellings are essentially aliases regardless of how the path is
+ * actually stored on the disk.
+ *
+ * This is related to "core.precomposeUnicode" (which wants to try
+ * to hide NFD completely and treat everything as NFC). Here, we
+ * don't know what the value the client has (or will have) for this
+ * config setting when they make a query, so assume the worst and
+ * emit both when the OS gives us an NFD path.
+ */
+static void my_add_path(struct fsmonitor_batch *batch, const char *path)
+{
+ char *composed;
+
+ /* add the NFC or NFD path as received from the OS */
+ fsmonitor_batch__add_path(batch, path);
+
+ /* if NFD, also add the corresponding NFC spelling */
+ composed = (char *)precompose_string_if_needed(path);
+ if (!composed || composed == path)
+ return;
+
+ fsmonitor_batch__add_path(batch, composed);
+ free(composed);
+}
+
+
+static void fsevent_callback(ConstFSEventStreamRef streamRef UNUSED,
+ void *ctx,
+ size_t num_of_events,
+ void *event_paths,
+ const FSEventStreamEventFlags event_flags[],
+ const FSEventStreamEventId event_ids[] UNUSED)
+{
+ struct fsmonitor_daemon_state *state = ctx;
+ struct fsm_listen_data *data = state->listen_data;
+ char **paths = (char **)event_paths;
+ struct fsmonitor_batch *batch = NULL;
+ struct string_list cookie_list = STRING_LIST_INIT_DUP;
+ const char *path_k;
+ const char *slash;
+ char *resolved = NULL;
+ struct strbuf tmp = STRBUF_INIT;
+ int k;
+
+ /*
+ * Build a list of all filesystem changes into a private/local
+ * list and without holding any locks.
+ */
+ for (k = 0; k < num_of_events; k++) {
+ /*
+ * On Mac, we receive an array of absolute paths.
+ */
+ free(resolved);
+ resolved = fsmonitor__resolve_alias(paths[k], &state->alias);
+ if (resolved)
+ path_k = resolved;
+ else
+ path_k = paths[k];
+
+ /*
+ * If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.
+ * Please don't log them to Trace2.
+ *
+ * trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k);
+ */
+
+ /*
+ * If event[k] is marked as dropped, we assume that we have
+ * lost sync with the filesystem and should flush our cached
+ * data. We need to:
+ *
+ * [1] Abort/wake any client threads waiting for a cookie and
+ * flush the cached state data (the current token), and
+ * create a new token.
+ *
+ * [2] Discard the batch that we were locally building (since
+ * they are conceptually relative to the just flushed
+ * token).
+ */
+ if (ef_is_dropped(event_flags[k])) {
+ if (trace_pass_fl(&trace_fsmonitor))
+ log_flags_set(path_k, event_flags[k]);
+
+ fsmonitor_force_resync(state);
+ fsmonitor_batch__free_list(batch);
+ string_list_clear(&cookie_list, 0);
+ batch = NULL;
+
+ /*
+ * We assume that any events that we received
+ * in this callback after this dropped event
+ * may still be valid, so we continue rather
+ * than break. (And just in case there is a
+ * delete of ".git" hiding in there.)
+ */
+ continue;
+ }
+
+ if (ef_is_root_changed(event_flags[k])) {
+ /*
+ * The spelling of the pathname of the root directory
+ * has changed. This includes the name of the root
+ * directory itself or of any parent directory in the
+ * path.
+ *
+ * (There may be other conditions that throw this,
+ * but I couldn't find any information on it.)
+ *
+ * Force a shutdown now and avoid things getting
+ * out of sync. The Unix domain socket is inside
+ * the .git directory and a spelling change will make
+ * it hard for clients to rendezvous with us.
+ */
+ trace_printf_key(&trace_fsmonitor,
+ "event: root changed");
+ goto force_shutdown;
+ }
+
+ if (ef_ignore_xattr(event_flags[k])) {
+ trace_printf_key(&trace_fsmonitor,
+ "ignore-xattr: '%s', flags=0x%x",
+ path_k, event_flags[k]);
+ continue;
+ }
+
+ switch (fsmonitor_classify_path_absolute(state, path_k)) {
+
+ case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
+ case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
+ /* special case cookie files within .git or gitdir */
+
+ /* Use just the filename of the cookie file. */
+ slash = find_last_dir_sep(path_k);
+ string_list_append(&cookie_list,
+ slash ? slash + 1 : path_k);
+ break;
+
+ case IS_INSIDE_DOT_GIT:
+ case IS_INSIDE_GITDIR:
+ /* ignore all other paths inside of .git or gitdir */
+ break;
+
+ case IS_DOT_GIT:
+ case IS_GITDIR:
+ /*
+ * If .git directory is deleted or renamed away,
+ * we have to quit.
+ */
+ if (ef_is_root_delete(event_flags[k])) {
+ trace_printf_key(&trace_fsmonitor,
+ "event: gitdir removed");
+ goto force_shutdown;
+ }
+ if (ef_is_root_renamed(event_flags[k])) {
+ trace_printf_key(&trace_fsmonitor,
+ "event: gitdir renamed");
+ goto force_shutdown;
+ }
+ break;
+
+ case IS_WORKDIR_PATH:
+ /* try to queue normal pathnames */
+
+ if (trace_pass_fl(&trace_fsmonitor))
+ log_flags_set(path_k, event_flags[k]);
+
+ /*
+ * Because of the implicit "binning" (the
+ * kernel calls us at a given frequency) and
+ * de-duping (the kernel is free to combine
+ * multiple events for a given pathname), an
+ * individual fsevent could be marked as both
+ * a file and directory. Add it to the queue
+ * with both spellings so that the client will
+ * know how much to invalidate/refresh.
+ */
+
+ if (event_flags[k] & (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemIsSymlink)) {
+ const char *rel = path_k +
+ state->path_worktree_watch.len + 1;
+
+ if (!batch)
+ batch = fsmonitor_batch__new();
+ my_add_path(batch, rel);
+ }
+
+ if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) {
+ const char *rel = path_k +
+ state->path_worktree_watch.len + 1;
+
+ strbuf_reset(&tmp);
+ strbuf_addstr(&tmp, rel);
+ strbuf_addch(&tmp, '/');
+
+ if (!batch)
+ batch = fsmonitor_batch__new();
+ my_add_path(batch, tmp.buf);
+ }
+
+ break;
+
+ case IS_OUTSIDE_CONE:
+ default:
+ trace_printf_key(&trace_fsmonitor,
+ "ignoring '%s'", path_k);
+ break;
+ }
+ }
+
+ free(resolved);
+ fsmonitor_publish(state, batch, &cookie_list);
+ string_list_clear(&cookie_list, 0);
+ strbuf_release(&tmp);
+ return;
+
+force_shutdown:
+ free(resolved);
+ fsmonitor_batch__free_list(batch);
+ string_list_clear(&cookie_list, 0);
+
+ pthread_mutex_lock(&data->dq_lock);
+ data->shutdown_style = FORCE_SHUTDOWN;
+ pthread_cond_broadcast(&data->dq_finished);
+ pthread_mutex_unlock(&data->dq_lock);
+
+ strbuf_release(&tmp);
+ return;
+}
+
+/*
+ * In the call to `FSEventStreamCreate()` to setup our watch, the
+ * `latency` argument determines the frequency of calls to our callback
+ * with new FS events. Too slow and events get dropped; too fast and
+ * we burn CPU unnecessarily. Since it is rather obscure, I don't
+ * think this needs to be a config setting. I've done extensive
+ * testing on my systems and chosen the value below. It gives good
+ * results and I've not seen any dropped events.
+ *
+ * With a latency of 0.1, I was seeing lots of dropped events during
+ * the "touch 100000" files test within t/perf/p7519, but with a
+ * latency of 0.001 I did not see any dropped events. So I'm going
+ * to assume that this is the "correct" value.
+ *
+ * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
+ */
+
+int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
+{
+ FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagNoDefer |
+ kFSEventStreamCreateFlagWatchRoot |
+ kFSEventStreamCreateFlagFileEvents;
+ FSEventStreamContext ctx = {
+ 0,
+ state,
+ NULL,
+ NULL,
+ NULL
+ };
+ struct fsm_listen_data *data;
+ const void *dir_array[2];
+
+ CALLOC_ARRAY(data, 1);
+ state->listen_data = data;
+
+ data->cfsr_worktree_path = CFStringCreateWithCString(
+ NULL, state->path_worktree_watch.buf, kCFStringEncodingUTF8);
+ dir_array[data->nr_paths_watching++] = data->cfsr_worktree_path;
+
+ if (state->nr_paths_watching > 1) {
+ data->cfsr_gitdir_path = CFStringCreateWithCString(
+ NULL, state->path_gitdir_watch.buf,
+ kCFStringEncodingUTF8);
+ dir_array[data->nr_paths_watching++] = data->cfsr_gitdir_path;
+ }
+
+ data->cfar_paths_to_watch = CFArrayCreate(NULL, dir_array,
+ data->nr_paths_watching,
+ NULL);
+ data->stream = FSEventStreamCreate(NULL, fsevent_callback, &ctx,
+ data->cfar_paths_to_watch,
+ kFSEventStreamEventIdSinceNow,
+ 0.001, flags);
+ if (!data->stream)
+ goto failed;
+
+ return 0;
+
+failed:
+ error(_("Unable to create FSEventStream."));
+
+ FREE_AND_NULL(state->listen_data);
+ return -1;
+}
+
+void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_listen_data *data;
+
+ if (!state || !state->listen_data)
+ return;
+
+ data = state->listen_data;
+
+ if (data->stream) {
+ if (data->stream_started)
+ FSEventStreamStop(data->stream);
+ if (data->stream_scheduled)
+ FSEventStreamInvalidate(data->stream);
+ FSEventStreamRelease(data->stream);
+ }
+
+ if (data->dq)
+ dispatch_release(data->dq);
+ pthread_cond_destroy(&data->dq_finished);
+ pthread_mutex_destroy(&data->dq_lock);
+
+ FREE_AND_NULL(state->listen_data);
+}
+
+void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_listen_data *data;
+
+ data = state->listen_data;
+
+ pthread_mutex_lock(&data->dq_lock);
+ data->shutdown_style = SHUTDOWN_EVENT;
+ pthread_cond_broadcast(&data->dq_finished);
+ pthread_mutex_unlock(&data->dq_lock);
+}
+
+void fsm_listen__loop(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_listen_data *data;
+
+ data = state->listen_data;
+
+ pthread_mutex_init(&data->dq_lock, NULL);
+ pthread_cond_init(&data->dq_finished, NULL);
+ data->dq = dispatch_queue_create("FSMonitor", NULL);
+
+ FSEventStreamSetDispatchQueue(data->stream, data->dq);
+ data->stream_scheduled = 1;
+
+ if (!FSEventStreamStart(data->stream)) {
+ error(_("Failed to start the FSEventStream"));
+ goto force_error_stop_without_loop;
+ }
+ data->stream_started = 1;
+
+ pthread_mutex_lock(&data->dq_lock);
+ pthread_cond_wait(&data->dq_finished, &data->dq_lock);
+ pthread_mutex_unlock(&data->dq_lock);
+
+ switch (data->shutdown_style) {
+ case FORCE_ERROR_STOP:
+ state->listen_error_code = -1;
+ /* fall thru */
+ case FORCE_SHUTDOWN:
+ ipc_server_stop_async(state->ipc_server_data);
+ /* fall thru */
+ case SHUTDOWN_EVENT:
+ default:
+ break;
+ }
+ return;
+
+force_error_stop_without_loop:
+ state->listen_error_code = -1;
+ ipc_server_stop_async(state->ipc_server_data);
+ return;
+}
diff --git a/compat/fsmonitor/fsm-listen-win32.c b/compat/fsmonitor/fsm-listen-win32.c
new file mode 100644
index 0000000..5a21dad
--- /dev/null
+++ b/compat/fsmonitor/fsm-listen-win32.c
@@ -0,0 +1,873 @@
+#include "git-compat-util.h"
+#include "config.h"
+#include "fsmonitor-ll.h"
+#include "fsm-listen.h"
+#include "fsmonitor--daemon.h"
+#include "gettext.h"
+#include "simple-ipc.h"
+#include "trace2.h"
+
+/*
+ * The documentation of ReadDirectoryChangesW() states that the maximum
+ * buffer size is 64K when the monitored directory is remote.
+ *
+ * Larger buffers may be used when the monitored directory is local and
+ * will help us receive events faster from the kernel and avoid dropped
+ * events.
+ *
+ * So we try to use a very large buffer and silently fallback to 64K if
+ * we get an error.
+ */
+#define MAX_RDCW_BUF_FALLBACK (65536)
+#define MAX_RDCW_BUF (65536 * 8)
+
+struct one_watch
+{
+ char buffer[MAX_RDCW_BUF];
+ DWORD buf_len;
+ DWORD count;
+
+ struct strbuf path;
+ wchar_t wpath_longname[MAX_PATH + 1];
+ DWORD wpath_longname_len;
+
+ HANDLE hDir;
+ HANDLE hEvent;
+ OVERLAPPED overlapped;
+
+ /*
+ * Is there an active ReadDirectoryChangesW() call pending. If so, we
+ * need to later call GetOverlappedResult() and possibly CancelIoEx().
+ */
+ BOOL is_active;
+
+ /*
+ * Are shortnames enabled on the containing drive? This is
+ * always true for "C:/" drives and usually never true for
+ * other drives.
+ *
+ * We only set this for the worktree because we only need to
+ * convert shortname paths to longname paths for items we send
+ * to clients. (We don't care about shortname expansion for
+ * paths inside a GITDIR because we never send them to
+ * clients.)
+ */
+ BOOL has_shortnames;
+ BOOL has_tilde;
+ wchar_t dotgit_shortname[16]; /* for 8.3 name */
+};
+
+struct fsm_listen_data
+{
+ struct one_watch *watch_worktree;
+ struct one_watch *watch_gitdir;
+
+ HANDLE hEventShutdown;
+
+ HANDLE hListener[3]; /* we don't own these handles */
+#define LISTENER_SHUTDOWN 0
+#define LISTENER_HAVE_DATA_WORKTREE 1
+#define LISTENER_HAVE_DATA_GITDIR 2
+ int nr_listener_handles;
+};
+
+/*
+ * Convert the WCHAR path from the event into UTF8 and normalize it.
+ *
+ * `wpath_len` is in WCHARS not bytes.
+ */
+static int normalize_path_in_utf8(wchar_t *wpath, DWORD wpath_len,
+ struct strbuf *normalized_path)
+{
+ int reserve;
+ int len = 0;
+
+ strbuf_reset(normalized_path);
+ if (!wpath_len)
+ goto normalize;
+
+ /*
+ * Pre-reserve enough space in the UTF8 buffer for
+ * each Unicode WCHAR character to be mapped into a
+ * sequence of 2 UTF8 characters. That should let us
+ * avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time.
+ */
+ reserve = 2 * wpath_len + 1;
+ strbuf_grow(normalized_path, reserve);
+
+ for (;;) {
+ len = WideCharToMultiByte(CP_UTF8, 0,
+ wpath, wpath_len,
+ normalized_path->buf,
+ strbuf_avail(normalized_path) - 1,
+ NULL, NULL);
+ if (len > 0)
+ goto normalize;
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ error(_("[GLE %ld] could not convert path to UTF-8: '%.*ls'"),
+ GetLastError(), (int)wpath_len, wpath);
+ return -1;
+ }
+
+ strbuf_grow(normalized_path,
+ strbuf_avail(normalized_path) + reserve);
+ }
+
+normalize:
+ strbuf_setlen(normalized_path, len);
+ return strbuf_normalize_path(normalized_path);
+}
+
+/*
+ * See if the worktree root directory has shortnames enabled.
+ * This will help us decide if we need to do an expensive shortname
+ * to longname conversion on every notification event.
+ *
+ * We do not want to create a file to test this, so we assume that the
+ * root directory contains a ".git" file or directory. (Our caller
+ * only calls us for the worktree root, so this should be fine.)
+ *
+ * Remember the spelling of the shortname for ".git" if it exists.
+ */
+static void check_for_shortnames(struct one_watch *watch)
+{
+ wchar_t buf_in[MAX_PATH + 1];
+ wchar_t buf_out[MAX_PATH + 1];
+ wchar_t *last;
+ wchar_t *p;
+
+ /* build L"<wt-root-path>/.git" */
+ swprintf(buf_in, ARRAY_SIZE(buf_in) - 1, L"%ls.git",
+ watch->wpath_longname);
+
+ if (!GetShortPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out)))
+ return;
+
+ /*
+ * Get the final filename component of the shortpath.
+ * We know that the path does not have a final slash.
+ */
+ for (last = p = buf_out; *p; p++)
+ if (*p == L'/' || *p == '\\')
+ last = p + 1;
+
+ if (!wcscmp(last, L".git"))
+ return;
+
+ watch->has_shortnames = 1;
+ wcsncpy(watch->dotgit_shortname, last,
+ ARRAY_SIZE(watch->dotgit_shortname));
+
+ /*
+ * The shortname for ".git" is usually of the form "GIT~1", so
+ * we should be able to avoid shortname to longname mapping on
+ * every notification event if the source string does not
+ * contain a "~".
+ *
+ * However, the documentation for GetLongPathNameW() says
+ * that there are filesystems that don't follow that pattern
+ * and warns against this optimization.
+ *
+ * Lets test this.
+ */
+ if (wcschr(watch->dotgit_shortname, L'~'))
+ watch->has_tilde = 1;
+}
+
+enum get_relative_result {
+ GRR_NO_CONVERSION_NEEDED,
+ GRR_HAVE_CONVERSION,
+ GRR_SHUTDOWN,
+};
+
+/*
+ * Info notification paths are relative to the root of the watch.
+ * If our CWD is still at the root, then we can use relative paths
+ * to convert from shortnames to longnames. If our process has a
+ * different CWD, then we need to construct an absolute path, do
+ * the conversion, and then return the root-relative portion.
+ *
+ * We use the longname form of the root as our basis and assume that
+ * it already has a trailing slash.
+ *
+ * `wpath_len` is in WCHARS not bytes.
+ */
+static enum get_relative_result get_relative_longname(
+ struct one_watch *watch,
+ const wchar_t *wpath, DWORD wpath_len,
+ wchar_t *wpath_longname, size_t bufsize_wpath_longname)
+{
+ wchar_t buf_in[2 * MAX_PATH + 1];
+ wchar_t buf_out[MAX_PATH + 1];
+ DWORD root_len;
+ DWORD out_len;
+
+ /*
+ * Build L"<wt-root-path>/<event-rel-path>"
+ * Note that the <event-rel-path> might not be null terminated
+ * so we avoid swprintf() constructions.
+ */
+ root_len = watch->wpath_longname_len;
+ if (root_len + wpath_len >= ARRAY_SIZE(buf_in)) {
+ /*
+ * This should not happen. We cannot append the observed
+ * relative path onto the end of the worktree root path
+ * without overflowing the buffer. Just give up.
+ */
+ return GRR_SHUTDOWN;
+ }
+ wcsncpy(buf_in, watch->wpath_longname, root_len);
+ wcsncpy(buf_in + root_len, wpath, wpath_len);
+ buf_in[root_len + wpath_len] = 0;
+
+ /*
+ * We don't actually know if the source pathname is a
+ * shortname or a longname. This Windows routine allows
+ * either to be given as input.
+ */
+ out_len = GetLongPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out));
+ if (!out_len) {
+ /*
+ * The shortname to longname conversion can fail for
+ * various reasons, for example if the file has been
+ * deleted. (That is, if we just received a
+ * delete-file notification event and the file is
+ * already gone, we can't ask the file system to
+ * lookup the longname for it. Likewise, for moves
+ * and renames where we are given the old name.)
+ *
+ * Since deleting or moving a file or directory by its
+ * shortname is rather obscure, I'm going ignore the
+ * failure and ask the caller to report the original
+ * relative path. This seems kinder than failing here
+ * and forcing a resync. Besides, forcing a resync on
+ * every file/directory delete would effectively
+ * cripple monitoring.
+ *
+ * We might revisit this in the future.
+ */
+ return GRR_NO_CONVERSION_NEEDED;
+ }
+
+ if (!wcscmp(buf_in, buf_out)) {
+ /*
+ * The path does not have a shortname alias.
+ */
+ return GRR_NO_CONVERSION_NEEDED;
+ }
+
+ if (wcsncmp(buf_in, buf_out, root_len)) {
+ /*
+ * The spelling of the root directory portion of the computed
+ * longname has changed. This should not happen. Basically,
+ * it means that we don't know where (without recomputing the
+ * longname of just the root directory) to split out the
+ * relative path. Since this should not happen, I'm just
+ * going to let this fail and force a shutdown (because all
+ * subsequent events are probably going to see the same
+ * mismatch).
+ */
+ return GRR_SHUTDOWN;
+ }
+
+ if (out_len - root_len >= bufsize_wpath_longname) {
+ /*
+ * This should not happen. We cannot copy the root-relative
+ * portion of the path into the provided buffer without an
+ * overrun. Just give up.
+ */
+ return GRR_SHUTDOWN;
+ }
+
+ /* Return the worktree root-relative portion of the longname. */
+
+ wcscpy(wpath_longname, buf_out + root_len);
+ return GRR_HAVE_CONVERSION;
+}
+
+void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
+{
+ SetEvent(state->listen_data->hListener[LISTENER_SHUTDOWN]);
+}
+
+static struct one_watch *create_watch(const char *path)
+{
+ struct one_watch *watch = NULL;
+ DWORD desired_access = FILE_LIST_DIRECTORY;
+ DWORD share_mode =
+ FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
+ HANDLE hDir;
+ DWORD len_longname;
+ wchar_t wpath[MAX_PATH + 1];
+ wchar_t wpath_longname[MAX_PATH + 1];
+
+ if (xutftowcs_path(wpath, path) < 0) {
+ error(_("could not convert to wide characters: '%s'"), path);
+ return NULL;
+ }
+
+ hDir = CreateFileW(wpath,
+ desired_access, share_mode, NULL, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
+ NULL);
+ if (hDir == INVALID_HANDLE_VALUE) {
+ error(_("[GLE %ld] could not watch '%s'"),
+ GetLastError(), path);
+ return NULL;
+ }
+
+ len_longname = GetLongPathNameW(wpath, wpath_longname,
+ ARRAY_SIZE(wpath_longname));
+ if (!len_longname) {
+ error(_("[GLE %ld] could not get longname of '%s'"),
+ GetLastError(), path);
+ CloseHandle(hDir);
+ return NULL;
+ }
+
+ if (wpath_longname[len_longname - 1] != L'/' &&
+ wpath_longname[len_longname - 1] != L'\\') {
+ wpath_longname[len_longname++] = L'/';
+ wpath_longname[len_longname] = 0;
+ }
+
+ CALLOC_ARRAY(watch, 1);
+
+ watch->buf_len = sizeof(watch->buffer); /* assume full MAX_RDCW_BUF */
+
+ strbuf_init(&watch->path, 0);
+ strbuf_addstr(&watch->path, path);
+
+ wcscpy(watch->wpath_longname, wpath_longname);
+ watch->wpath_longname_len = len_longname;
+
+ watch->hDir = hDir;
+ watch->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ return watch;
+}
+
+static void destroy_watch(struct one_watch *watch)
+{
+ if (!watch)
+ return;
+
+ strbuf_release(&watch->path);
+ if (watch->hDir != INVALID_HANDLE_VALUE)
+ CloseHandle(watch->hDir);
+ if (watch->hEvent != INVALID_HANDLE_VALUE)
+ CloseHandle(watch->hEvent);
+
+ free(watch);
+}
+
+static int start_rdcw_watch(struct one_watch *watch)
+{
+ DWORD dwNotifyFilter =
+ FILE_NOTIFY_CHANGE_FILE_NAME |
+ FILE_NOTIFY_CHANGE_DIR_NAME |
+ FILE_NOTIFY_CHANGE_ATTRIBUTES |
+ FILE_NOTIFY_CHANGE_SIZE |
+ FILE_NOTIFY_CHANGE_LAST_WRITE |
+ FILE_NOTIFY_CHANGE_CREATION;
+
+ ResetEvent(watch->hEvent);
+
+ memset(&watch->overlapped, 0, sizeof(watch->overlapped));
+ watch->overlapped.hEvent = watch->hEvent;
+
+ /*
+ * Queue an async call using Overlapped IO. This returns immediately.
+ * Our event handle will be signalled when the real result is available.
+ *
+ * The return value here just means that we successfully queued it.
+ * We won't know if the Read...() actually produces data until later.
+ */
+ watch->is_active = ReadDirectoryChangesW(
+ watch->hDir, watch->buffer, watch->buf_len, TRUE,
+ dwNotifyFilter, &watch->count, &watch->overlapped, NULL);
+
+ if (watch->is_active)
+ return 0;
+
+ error(_("ReadDirectoryChangedW failed on '%s' [GLE %ld]"),
+ watch->path.buf, GetLastError());
+ return -1;
+}
+
+static int recv_rdcw_watch(struct one_watch *watch)
+{
+ DWORD gle;
+
+ watch->is_active = FALSE;
+
+ /*
+ * The overlapped result is ready. If the Read...() was successful
+ * we finally receive the actual result into our buffer.
+ */
+ if (GetOverlappedResult(watch->hDir, &watch->overlapped, &watch->count,
+ TRUE))
+ return 0;
+
+ gle = GetLastError();
+ if (gle == ERROR_INVALID_PARAMETER &&
+ /*
+ * The kernel throws an invalid parameter error when our
+ * buffer is too big and we are pointed at a remote
+ * directory (and possibly for other reasons). Quietly
+ * set it down and try again.
+ *
+ * See note about MAX_RDCW_BUF at the top.
+ */
+ watch->buf_len > MAX_RDCW_BUF_FALLBACK) {
+ watch->buf_len = MAX_RDCW_BUF_FALLBACK;
+ return -2;
+ }
+
+ /*
+ * GetOverlappedResult() fails if the watched directory is
+ * deleted while we were waiting for an overlapped IO to
+ * complete. The documentation did not list specific errors,
+ * but I observed ERROR_ACCESS_DENIED (0x05) errors during
+ * testing.
+ *
+ * Note that we only get notificaiton events for events
+ * *within* the directory, not *on* the directory itself.
+ * (These might be properies of the parent directory, for
+ * example).
+ *
+ * NEEDSWORK: We might try to check for the deleted directory
+ * case and return a better error message, but I'm not sure it
+ * is worth it.
+ *
+ * Shutdown if we get any error.
+ */
+
+ error(_("GetOverlappedResult failed on '%s' [GLE %ld]"),
+ watch->path.buf, gle);
+ return -1;
+}
+
+static void cancel_rdcw_watch(struct one_watch *watch)
+{
+ DWORD count;
+
+ if (!watch || !watch->is_active)
+ return;
+
+ /*
+ * The calls to ReadDirectoryChangesW() and GetOverlappedResult()
+ * form a "pair" (my term) where we queue an IO and promise to
+ * hang around and wait for the kernel to give us the result.
+ *
+ * If for some reason after we queue the IO, we have to quit
+ * or otherwise not stick around for the second half, we must
+ * tell the kernel to abort the IO. This prevents the kernel
+ * from writing to our buffer and/or signalling our event
+ * after we free them.
+ *
+ * (Ask me how much fun it was to track that one down).
+ */
+ CancelIoEx(watch->hDir, &watch->overlapped);
+ GetOverlappedResult(watch->hDir, &watch->overlapped, &count, TRUE);
+ watch->is_active = FALSE;
+}
+
+/*
+ * Process a single relative pathname event.
+ * Return 1 if we should shutdown.
+ */
+static int process_1_worktree_event(
+ struct string_list *cookie_list,
+ struct fsmonitor_batch **batch,
+ const struct strbuf *path,
+ enum fsmonitor_path_type t,
+ DWORD info_action)
+{
+ const char *slash;
+
+ switch (t) {
+ case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
+ /* special case cookie files within .git */
+
+ /* Use just the filename of the cookie file. */
+ slash = find_last_dir_sep(path->buf);
+ string_list_append(cookie_list,
+ slash ? slash + 1 : path->buf);
+ break;
+
+ case IS_INSIDE_DOT_GIT:
+ /* ignore everything inside of "<worktree>/.git/" */
+ break;
+
+ case IS_DOT_GIT:
+ /* "<worktree>/.git" was deleted (or renamed away) */
+ if ((info_action == FILE_ACTION_REMOVED) ||
+ (info_action == FILE_ACTION_RENAMED_OLD_NAME)) {
+ trace2_data_string("fsmonitor", NULL,
+ "fsm-listen/dotgit",
+ "removed");
+ return 1;
+ }
+ break;
+
+ case IS_WORKDIR_PATH:
+ /* queue normal pathname */
+ if (!*batch)
+ *batch = fsmonitor_batch__new();
+ fsmonitor_batch__add_path(*batch, path->buf);
+ break;
+
+ case IS_GITDIR:
+ case IS_INSIDE_GITDIR:
+ case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
+ default:
+ BUG("unexpected path classification '%d' for '%s'",
+ t, path->buf);
+ }
+
+ return 0;
+}
+
+/*
+ * Process filesystem events that happen anywhere (recursively) under the
+ * <worktree> root directory. For a normal working directory, this includes
+ * both version controlled files and the contents of the .git/ directory.
+ *
+ * If <worktree>/.git is a file, then we only see events for the file
+ * itself.
+ */
+static int process_worktree_events(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_listen_data *data = state->listen_data;
+ struct one_watch *watch = data->watch_worktree;
+ struct strbuf path = STRBUF_INIT;
+ struct string_list cookie_list = STRING_LIST_INIT_DUP;
+ struct fsmonitor_batch *batch = NULL;
+ const char *p = watch->buffer;
+ wchar_t wpath_longname[MAX_PATH + 1];
+
+ /*
+ * If the kernel gets more events than will fit in the kernel
+ * buffer associated with our RDCW handle, it drops them and
+ * returns a count of zero.
+ *
+ * Yes, the call returns WITHOUT error and with length zero.
+ * This is the documented behavior. (My testing has confirmed
+ * that it also sets the last error to ERROR_NOTIFY_ENUM_DIR,
+ * but we do not rely on that since the function did not
+ * return an error and it is not documented.)
+ *
+ * (The "overflow" case is not ambiguous with the "no data" case
+ * because we did an INFINITE wait.)
+ *
+ * This means we have a gap in coverage. Tell the daemon layer
+ * to resync.
+ */
+ if (!watch->count) {
+ trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel",
+ "overflow");
+ fsmonitor_force_resync(state);
+ return LISTENER_HAVE_DATA_WORKTREE;
+ }
+
+ /*
+ * On Windows, `info` contains an "array" of paths that are
+ * relative to the root of whichever directory handle received
+ * the event.
+ */
+ for (;;) {
+ FILE_NOTIFY_INFORMATION *info = (void *)p;
+ wchar_t *wpath = info->FileName;
+ DWORD wpath_len = info->FileNameLength / sizeof(WCHAR);
+ enum fsmonitor_path_type t;
+ enum get_relative_result grr;
+
+ if (watch->has_shortnames) {
+ if (!wcscmp(wpath, watch->dotgit_shortname)) {
+ /*
+ * This event exactly matches the
+ * spelling of the shortname of
+ * ".git", so we can skip some steps.
+ *
+ * (This case is odd because the user
+ * can "rm -rf GIT~1" and we cannot
+ * use the filesystem to map it back
+ * to ".git".)
+ */
+ strbuf_reset(&path);
+ strbuf_addstr(&path, ".git");
+ t = IS_DOT_GIT;
+ goto process_it;
+ }
+
+ if (watch->has_tilde && !wcschr(wpath, L'~')) {
+ /*
+ * Shortnames on this filesystem have tildes
+ * and the notification path does not have
+ * one, so we assume that it is a longname.
+ */
+ goto normalize_it;
+ }
+
+ grr = get_relative_longname(watch, wpath, wpath_len,
+ wpath_longname,
+ ARRAY_SIZE(wpath_longname));
+ switch (grr) {
+ case GRR_NO_CONVERSION_NEEDED: /* use info buffer as is */
+ break;
+ case GRR_HAVE_CONVERSION:
+ wpath = wpath_longname;
+ wpath_len = wcslen(wpath);
+ break;
+ default:
+ case GRR_SHUTDOWN:
+ goto force_shutdown;
+ }
+ }
+
+normalize_it:
+ if (normalize_path_in_utf8(wpath, wpath_len, &path) == -1)
+ goto skip_this_path;
+
+ t = fsmonitor_classify_path_workdir_relative(path.buf);
+
+process_it:
+ if (process_1_worktree_event(&cookie_list, &batch, &path, t,
+ info->Action))
+ goto force_shutdown;
+
+skip_this_path:
+ if (!info->NextEntryOffset)
+ break;
+ p += info->NextEntryOffset;
+ }
+
+ fsmonitor_publish(state, batch, &cookie_list);
+ batch = NULL;
+ string_list_clear(&cookie_list, 0);
+ strbuf_release(&path);
+ return LISTENER_HAVE_DATA_WORKTREE;
+
+force_shutdown:
+ fsmonitor_batch__free_list(batch);
+ string_list_clear(&cookie_list, 0);
+ strbuf_release(&path);
+ return LISTENER_SHUTDOWN;
+}
+
+/*
+ * Process filesystem events that happened anywhere (recursively) under the
+ * external <gitdir> (such as non-primary worktrees or submodules).
+ * We only care about cookie files that our client threads created here.
+ *
+ * Note that we DO NOT get filesystem events on the external <gitdir>
+ * itself (it is not inside something that we are watching). In particular,
+ * we do not get an event if the external <gitdir> is deleted.
+ *
+ * Also, we do not care about shortnames within the external <gitdir>, since
+ * we never send these paths to clients.
+ */
+static int process_gitdir_events(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_listen_data *data = state->listen_data;
+ struct one_watch *watch = data->watch_gitdir;
+ struct strbuf path = STRBUF_INIT;
+ struct string_list cookie_list = STRING_LIST_INIT_DUP;
+ const char *p = watch->buffer;
+
+ if (!watch->count) {
+ trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel",
+ "overflow");
+ fsmonitor_force_resync(state);
+ return LISTENER_HAVE_DATA_GITDIR;
+ }
+
+ for (;;) {
+ FILE_NOTIFY_INFORMATION *info = (void *)p;
+ const char *slash;
+ enum fsmonitor_path_type t;
+
+ if (normalize_path_in_utf8(
+ info->FileName,
+ info->FileNameLength / sizeof(WCHAR),
+ &path) == -1)
+ goto skip_this_path;
+
+ t = fsmonitor_classify_path_gitdir_relative(path.buf);
+
+ switch (t) {
+ case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
+ /* special case cookie files within gitdir */
+
+ /* Use just the filename of the cookie file. */
+ slash = find_last_dir_sep(path.buf);
+ string_list_append(&cookie_list,
+ slash ? slash + 1 : path.buf);
+ break;
+
+ case IS_INSIDE_GITDIR:
+ goto skip_this_path;
+
+ default:
+ BUG("unexpected path classification '%d' for '%s'",
+ t, path.buf);
+ }
+
+skip_this_path:
+ if (!info->NextEntryOffset)
+ break;
+ p += info->NextEntryOffset;
+ }
+
+ fsmonitor_publish(state, NULL, &cookie_list);
+ string_list_clear(&cookie_list, 0);
+ strbuf_release(&path);
+ return LISTENER_HAVE_DATA_GITDIR;
+}
+
+void fsm_listen__loop(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_listen_data *data = state->listen_data;
+ DWORD dwWait;
+ int result;
+
+ state->listen_error_code = 0;
+
+ if (start_rdcw_watch(data->watch_worktree) == -1)
+ goto force_error_stop;
+
+ if (data->watch_gitdir &&
+ start_rdcw_watch(data->watch_gitdir) == -1)
+ goto force_error_stop;
+
+ for (;;) {
+ dwWait = WaitForMultipleObjects(data->nr_listener_handles,
+ data->hListener,
+ FALSE, INFINITE);
+
+ if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_WORKTREE) {
+ result = recv_rdcw_watch(data->watch_worktree);
+ if (result == -1) {
+ /* hard error */
+ goto force_error_stop;
+ }
+ if (result == -2) {
+ /* retryable error */
+ if (start_rdcw_watch(data->watch_worktree) == -1)
+ goto force_error_stop;
+ continue;
+ }
+
+ /* have data */
+ if (process_worktree_events(state) == LISTENER_SHUTDOWN)
+ goto force_shutdown;
+ if (start_rdcw_watch(data->watch_worktree) == -1)
+ goto force_error_stop;
+ continue;
+ }
+
+ if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_GITDIR) {
+ result = recv_rdcw_watch(data->watch_gitdir);
+ if (result == -1) {
+ /* hard error */
+ goto force_error_stop;
+ }
+ if (result == -2) {
+ /* retryable error */
+ if (start_rdcw_watch(data->watch_gitdir) == -1)
+ goto force_error_stop;
+ continue;
+ }
+
+ /* have data */
+ if (process_gitdir_events(state) == LISTENER_SHUTDOWN)
+ goto force_shutdown;
+ if (start_rdcw_watch(data->watch_gitdir) == -1)
+ goto force_error_stop;
+ continue;
+ }
+
+ if (dwWait == WAIT_OBJECT_0 + LISTENER_SHUTDOWN)
+ goto clean_shutdown;
+
+ error(_("could not read directory changes [GLE %ld]"),
+ GetLastError());
+ goto force_error_stop;
+ }
+
+force_error_stop:
+ state->listen_error_code = -1;
+
+force_shutdown:
+ /*
+ * Tell the IPC thead pool to stop (which completes the await
+ * in the main thread (which will also signal this thread (if
+ * we are still alive))).
+ */
+ ipc_server_stop_async(state->ipc_server_data);
+
+clean_shutdown:
+ cancel_rdcw_watch(data->watch_worktree);
+ cancel_rdcw_watch(data->watch_gitdir);
+}
+
+int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_listen_data *data;
+
+ CALLOC_ARRAY(data, 1);
+
+ data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ data->watch_worktree = create_watch(state->path_worktree_watch.buf);
+ if (!data->watch_worktree)
+ goto failed;
+
+ check_for_shortnames(data->watch_worktree);
+
+ if (state->nr_paths_watching > 1) {
+ data->watch_gitdir = create_watch(state->path_gitdir_watch.buf);
+ if (!data->watch_gitdir)
+ goto failed;
+ }
+
+ data->hListener[LISTENER_SHUTDOWN] = data->hEventShutdown;
+ data->nr_listener_handles++;
+
+ data->hListener[LISTENER_HAVE_DATA_WORKTREE] =
+ data->watch_worktree->hEvent;
+ data->nr_listener_handles++;
+
+ if (data->watch_gitdir) {
+ data->hListener[LISTENER_HAVE_DATA_GITDIR] =
+ data->watch_gitdir->hEvent;
+ data->nr_listener_handles++;
+ }
+
+ state->listen_data = data;
+ return 0;
+
+failed:
+ CloseHandle(data->hEventShutdown);
+ destroy_watch(data->watch_worktree);
+ destroy_watch(data->watch_gitdir);
+
+ return -1;
+}
+
+void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
+{
+ struct fsm_listen_data *data;
+
+ if (!state || !state->listen_data)
+ return;
+
+ data = state->listen_data;
+
+ CloseHandle(data->hEventShutdown);
+ destroy_watch(data->watch_worktree);
+ destroy_watch(data->watch_gitdir);
+
+ FREE_AND_NULL(state->listen_data);
+}
diff --git a/compat/fsmonitor/fsm-listen.h b/compat/fsmonitor/fsm-listen.h
new file mode 100644
index 0000000..41650bf
--- /dev/null
+++ b/compat/fsmonitor/fsm-listen.h
@@ -0,0 +1,49 @@
+#ifndef FSM_LISTEN_H
+#define FSM_LISTEN_H
+
+/* This needs to be implemented by each backend */
+
+#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
+
+struct fsmonitor_daemon_state;
+
+/*
+ * Initialize platform-specific data for the fsmonitor listener thread.
+ * This will be called from the main thread PRIOR to staring the
+ * fsmonitor_fs_listener thread.
+ *
+ * Returns 0 if successful.
+ * Returns -1 otherwise.
+ */
+int fsm_listen__ctor(struct fsmonitor_daemon_state *state);
+
+/*
+ * Cleanup platform-specific data for the fsmonitor listener thread.
+ * This will be called from the main thread AFTER joining the listener.
+ */
+void fsm_listen__dtor(struct fsmonitor_daemon_state *state);
+
+/*
+ * The main body of the platform-specific event loop to watch for
+ * filesystem events. This will run in the fsmonitor_fs_listen thread.
+ *
+ * It should call `ipc_server_stop_async()` if the listener thread
+ * prematurely terminates (because of a filesystem error or if it
+ * detects that the .git directory has been deleted). (It should NOT
+ * do so if the listener thread receives a normal shutdown signal from
+ * the IPC layer.)
+ *
+ * It should set `state->listen_error_code` to -1 if the daemon should exit
+ * with an error.
+ */
+void fsm_listen__loop(struct fsmonitor_daemon_state *state);
+
+/*
+ * Gently request that the fsmonitor listener thread shutdown.
+ * It does not wait for it to stop. The caller should do a JOIN
+ * to wait for it.
+ */
+void fsm_listen__stop_async(struct fsmonitor_daemon_state *state);
+
+#endif /* HAVE_FSMONITOR_DAEMON_BACKEND */
+#endif /* FSM_LISTEN_H */
diff --git a/compat/fsmonitor/fsm-path-utils-darwin.c b/compat/fsmonitor/fsm-path-utils-darwin.c
new file mode 100644
index 0000000..049f97e
--- /dev/null
+++ b/compat/fsmonitor/fsm-path-utils-darwin.c
@@ -0,0 +1,138 @@
+#include "git-compat-util.h"
+#include "fsmonitor-ll.h"
+#include "fsmonitor-path-utils.h"
+#include "gettext.h"
+#include "trace.h"
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+
+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)
+{
+ struct statfs fs;
+ if (statfs(path, &fs) == -1) {
+ int saved_errno = errno;
+ trace_printf_key(&trace_fsmonitor, "statfs('%s') failed: %s",
+ path, strerror(saved_errno));
+ errno = saved_errno;
+ return -1;
+ }
+
+ trace_printf_key(&trace_fsmonitor,
+ "statfs('%s') [type 0x%08x][flags 0x%08x] '%s'",
+ path, fs.f_type, fs.f_flags, fs.f_fstypename);
+
+ if (!(fs.f_flags & MNT_LOCAL))
+ fs_info->is_remote = 1;
+ else
+ fs_info->is_remote = 0;
+
+ fs_info->typename = xstrdup(fs.f_fstypename);
+
+ trace_printf_key(&trace_fsmonitor,
+ "'%s' is_remote: %d",
+ path, fs_info->is_remote);
+ return 0;
+}
+
+int fsmonitor__is_fs_remote(const char *path)
+{
+ struct fs_info fs;
+ if (fsmonitor__get_fs_info(path, &fs))
+ return -1;
+
+ free(fs.typename);
+
+ return fs.is_remote;
+}
+
+/*
+ * Scan the root directory for synthetic firmlinks that when resolved
+ * are a prefix of the path, stopping at the first one found.
+ *
+ * Some information about firmlinks and synthetic firmlinks:
+ * https://eclecticlight.co/2020/01/23/catalina-boot-volumes/
+ *
+ * macOS no longer allows symlinks in the root directory; any link found
+ * there is therefore a synthetic firmlink.
+ *
+ * If this function gets called often, will want to cache all the firmlink
+ * information, but for now there is only one caller of this function.
+ *
+ * If there is more than one alias for the path, that is another
+ * matter altogether.
+ */
+int fsmonitor__get_alias(const char *path, struct alias_info *info)
+{
+ DIR *dir;
+ int retval = -1;
+ const char *const root = "/";
+ struct stat st;
+ struct dirent *de;
+ struct strbuf alias;
+ struct strbuf points_to = STRBUF_INIT;
+
+ dir = opendir(root);
+ if (!dir)
+ return error_errno(_("opendir('%s') failed"), root);
+
+ strbuf_init(&alias, 256);
+
+ while ((de = readdir(dir)) != NULL) {
+ strbuf_reset(&alias);
+ strbuf_addf(&alias, "%s%s", root, de->d_name);
+
+ if (lstat(alias.buf, &st) < 0) {
+ error_errno(_("lstat('%s') failed"), alias.buf);
+ goto done;
+ }
+
+ if (!S_ISLNK(st.st_mode))
+ continue;
+
+ if (strbuf_readlink(&points_to, alias.buf, st.st_size) < 0) {
+ error_errno(_("strbuf_readlink('%s') failed"), alias.buf);
+ goto done;
+ }
+
+ if (!strncmp(points_to.buf, path, points_to.len) &&
+ (path[points_to.len] == '/')) {
+ strbuf_addbuf(&info->alias, &alias);
+ strbuf_addbuf(&info->points_to, &points_to);
+ trace_printf_key(&trace_fsmonitor,
+ "Found alias for '%s' : '%s' -> '%s'",
+ path, info->alias.buf, info->points_to.buf);
+ retval = 0;
+ goto done;
+ }
+ }
+ retval = 0; /* no alias */
+
+done:
+ strbuf_release(&alias);
+ strbuf_release(&points_to);
+ if (closedir(dir) < 0)
+ return error_errno(_("closedir('%s') failed"), root);
+ return retval;
+}
+
+char *fsmonitor__resolve_alias(const char *path,
+ const struct alias_info *info)
+{
+ if (!info->alias.len)
+ return NULL;
+
+ if ((!strncmp(info->alias.buf, path, info->alias.len))
+ && path[info->alias.len] == '/') {
+ struct strbuf tmp = STRBUF_INIT;
+ const char *remainder = path + info->alias.len;
+
+ strbuf_addbuf(&tmp, &info->points_to);
+ strbuf_add(&tmp, remainder, strlen(remainder));
+ return strbuf_detach(&tmp, NULL);
+ }
+
+ return NULL;
+}
diff --git a/compat/fsmonitor/fsm-path-utils-win32.c b/compat/fsmonitor/fsm-path-utils-win32.c
new file mode 100644
index 0000000..f4f9cc1
--- /dev/null
+++ b/compat/fsmonitor/fsm-path-utils-win32.c
@@ -0,0 +1,148 @@
+#include "git-compat-util.h"
+#include "fsmonitor-ll.h"
+#include "fsmonitor-path-utils.h"
+#include "gettext.h"
+#include "trace.h"
+
+/*
+ * Check remote working directory protocol.
+ *
+ * Return -1 if client machine cannot get remote protocol information.
+ */
+static int check_remote_protocol(wchar_t *wpath)
+{
+ HANDLE h;
+ FILE_REMOTE_PROTOCOL_INFO proto_info;
+
+ h = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (h == INVALID_HANDLE_VALUE) {
+ error(_("[GLE %ld] unable to open for read '%ls'"),
+ GetLastError(), wpath);
+ return -1;
+ }
+
+ if (!GetFileInformationByHandleEx(h, FileRemoteProtocolInfo,
+ &proto_info, sizeof(proto_info))) {
+ error(_("[GLE %ld] unable to get protocol information for '%ls'"),
+ GetLastError(), wpath);
+ CloseHandle(h);
+ return -1;
+ }
+
+ CloseHandle(h);
+
+ trace_printf_key(&trace_fsmonitor,
+ "check_remote_protocol('%ls') remote protocol %#8.8lx",
+ wpath, proto_info.Protocol);
+
+ return 0;
+}
+
+/*
+ * Notes for testing:
+ *
+ * (a) Windows allows a network share to be mapped to a drive letter.
+ * (This is the normal method to access it.)
+ *
+ * $ NET USE Z: \\server\share
+ * $ git -C Z:/repo status
+ *
+ * (b) Windows allows a network share to be referenced WITHOUT mapping
+ * it to drive letter.
+ *
+ * $ NET USE \\server\share\dir
+ * $ git -C //server/share/repo status
+ *
+ * (c) Windows allows "SUBST" to create a fake drive mapping to an
+ * arbitrary path (which may be remote)
+ *
+ * $ SUBST Q: Z:\repo
+ * $ git -C Q:/ status
+ *
+ * (d) Windows allows a directory symlink to be created on a local
+ * file system that points to a remote repo.
+ *
+ * $ mklink /d ./link //server/share/repo
+ * $ git -C ./link status
+ */
+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)
+{
+ wchar_t wpath[MAX_PATH];
+ wchar_t wfullpath[MAX_PATH];
+ size_t wlen;
+ UINT driveType;
+
+ /*
+ * Do everything in wide chars because the drive letter might be
+ * a multi-byte sequence. See win32_has_dos_drive_prefix().
+ */
+ if (xutftowcs_path(wpath, path) < 0) {
+ return -1;
+ }
+
+ /*
+ * GetDriveTypeW() requires a final slash. We assume that the
+ * worktree pathname points to an actual directory.
+ */
+ wlen = wcslen(wpath);
+ if (wpath[wlen - 1] != L'\\' && wpath[wlen - 1] != L'/') {
+ wpath[wlen++] = L'\\';
+ wpath[wlen] = 0;
+ }
+
+ /*
+ * Normalize the path. If nothing else, this converts forward
+ * slashes to backslashes. This is essential to get GetDriveTypeW()
+ * correctly handle some UNC "\\server\share\..." paths.
+ */
+ if (!GetFullPathNameW(wpath, MAX_PATH, wfullpath, NULL)) {
+ return -1;
+ }
+
+ driveType = GetDriveTypeW(wfullpath);
+ trace_printf_key(&trace_fsmonitor,
+ "DriveType '%s' L'%ls' (%u)",
+ path, wfullpath, driveType);
+
+ if (driveType == DRIVE_REMOTE) {
+ fs_info->is_remote = 1;
+ if (check_remote_protocol(wfullpath) < 0)
+ return -1;
+ } else {
+ fs_info->is_remote = 0;
+ }
+
+ trace_printf_key(&trace_fsmonitor,
+ "'%s' is_remote: %d",
+ path, fs_info->is_remote);
+
+ return 0;
+}
+
+int fsmonitor__is_fs_remote(const char *path)
+{
+ struct fs_info fs;
+ if (fsmonitor__get_fs_info(path, &fs))
+ return -1;
+ return fs.is_remote;
+}
+
+/*
+ * No-op for now.
+ */
+int fsmonitor__get_alias(const char *path UNUSED,
+ struct alias_info *info UNUSED)
+{
+ return 0;
+}
+
+/*
+ * No-op for now.
+ */
+char *fsmonitor__resolve_alias(const char *path UNUSED,
+ const struct alias_info *info UNUSED)
+{
+ return NULL;
+}
diff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c
new file mode 100644
index 0000000..a382590
--- /dev/null
+++ b/compat/fsmonitor/fsm-settings-darwin.c
@@ -0,0 +1,63 @@
+#include "git-compat-util.h"
+#include "config.h"
+#include "fsmonitor-ll.h"
+#include "fsmonitor-ipc.h"
+#include "fsmonitor-settings.h"
+#include "fsmonitor-path-utils.h"
+
+ /*
+ * For the builtin FSMonitor, we create the Unix domain socket for the
+ * IPC in the .git directory. If the working directory is remote,
+ * then the socket will be created on the remote file system. This
+ * can fail if the remote file system does not support UDS file types
+ * (e.g. smbfs to a Windows server) or if the remote kernel does not
+ * allow a non-local process to bind() the socket. (These problems
+ * could be fixed by moving the UDS out of the .git directory and to a
+ * well-known local directory on the client machine, but care should
+ * be taken to ensure that $HOME is actually local and not a managed
+ * file share.)
+ *
+ * FAT32 and NTFS working directories are problematic too.
+ *
+ * The builtin FSMonitor uses a Unix domain socket in the .git
+ * directory for IPC. These Windows drive formats do not support
+ * Unix domain sockets, so mark them as incompatible for the daemon.
+ *
+ */
+static enum fsmonitor_reason check_uds_volume(struct repository *r)
+{
+ struct fs_info fs;
+ const char *ipc_path = fsmonitor_ipc__get_path(r);
+ struct strbuf path = STRBUF_INIT;
+ strbuf_add(&path, ipc_path, strlen(ipc_path));
+
+ if (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {
+ strbuf_release(&path);
+ return FSMONITOR_REASON_ERROR;
+ }
+
+ strbuf_release(&path);
+
+ if (fs.is_remote ||
+ !strcmp(fs.typename, "msdos") ||
+ !strcmp(fs.typename, "ntfs")) {
+ free(fs.typename);
+ return FSMONITOR_REASON_NOSOCKETS;
+ }
+
+ free(fs.typename);
+ return FSMONITOR_REASON_OK;
+}
+
+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)
+{
+ enum fsmonitor_reason reason;
+
+ if (ipc) {
+ reason = check_uds_volume(r);
+ if (reason != FSMONITOR_REASON_OK)
+ return reason;
+ }
+
+ return FSMONITOR_REASON_OK;
+}
diff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c
new file mode 100644
index 0000000..0f2aa32
--- /dev/null
+++ b/compat/fsmonitor/fsm-settings-win32.c
@@ -0,0 +1,37 @@
+#include "git-compat-util.h"
+#include "config.h"
+#include "repository.h"
+#include "fsmonitor-ll.h"
+#include "fsmonitor-settings.h"
+#include "fsmonitor-path-utils.h"
+
+/*
+ * VFS for Git is incompatible with FSMonitor.
+ *
+ * Granted, core Git does not know anything about VFS for Git and we
+ * shouldn't make assumptions about a downstream feature, but users
+ * can install both versions. And this can lead to incorrect results
+ * from core Git commands. So, without bringing in any of the VFS for
+ * Git code, do a simple config test for a published config setting.
+ * (We do not look at the various *_TEST_* environment variables.)
+ */
+static enum fsmonitor_reason check_vfs4git(struct repository *r)
+{
+ const char *const_str;
+
+ if (!repo_config_get_value(r, "core.virtualfilesystem", &const_str))
+ return FSMONITOR_REASON_VFS4GIT;
+
+ return FSMONITOR_REASON_OK;
+}
+
+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc UNUSED)
+{
+ enum fsmonitor_reason reason;
+
+ reason = check_vfs4git(r);
+ if (reason != FSMONITOR_REASON_OK)
+ return reason;
+
+ return FSMONITOR_REASON_OK;
+}
diff --git a/compat/linux/procinfo.c b/compat/linux/procinfo.c
new file mode 100644
index 0000000..4bb2d66
--- /dev/null
+++ b/compat/linux/procinfo.c
@@ -0,0 +1,176 @@
+#include "git-compat-util.h"
+
+#include "strbuf.h"
+#include "strvec.h"
+#include "trace2.h"
+
+/*
+ * We need more complex parsing in stat_parent_pid() and
+ * parse_proc_stat() below than a dumb fscanf(). That's because while
+ * the statcomm field is surrounded by parentheses, the process itself
+ * is free to insert any arbitrary byte sequence its its name. That
+ * can include newlines, spaces, closing parentheses etc.
+ *
+ * See do_task_stat() in fs/proc/array.c in linux.git, this is in
+ * contrast with the escaped version of the name found in
+ * /proc/%d/status.
+ *
+ * So instead of using fscanf() we'll read N bytes from it, look for
+ * the first "(", and then the last ")", anything in-between is our
+ * process name.
+ *
+ * How much N do we need? On Linux /proc/sys/kernel/pid_max is 2^15 by
+ * default, but it can be raised set to values of up to 2^22. So
+ * that's 7 digits for a PID. We have 2 PIDs in the first four fields
+ * we're interested in, so 2 * 7 = 14.
+ *
+ * We then have 3 spaces between those four values, and we'd like to
+ * get to the space between the 4th and the 5th (the "pgrp" field) to
+ * make sure we read the entire "ppid" field. So that brings us up to
+ * 14 + 3 + 1 = 18. Add the two parentheses around the "comm" value
+ * and it's 20. The "state" value itself is then one character (now at
+ * 21).
+ *
+ * Finally the maximum length of the "comm" name itself is 15
+ * characters, e.g. a setting of "123456789abcdefg" will be truncated
+ * to "123456789abcdef". See PR_SET_NAME in prctl(2). So all in all
+ * we'd need to read 21 + 15 = 36 bytes.
+ *
+ * Let's just read 2^6 (64) instead for good measure. If PID_MAX ever
+ * grows past 2^22 we'll be future-proof. We'll then anchor at the
+ * last ")" we find to locate the parent PID.
+ */
+#define STAT_PARENT_PID_READ_N 64
+
+static int parse_proc_stat(struct strbuf *sb, struct strbuf *name,
+ int *statppid)
+{
+ const char *comm_lhs = strchr(sb->buf, '(');
+ const char *comm_rhs = strrchr(sb->buf, ')');
+ const char *ppid_lhs, *ppid_rhs;
+ char *p;
+ pid_t ppid;
+
+ if (!comm_lhs || !comm_rhs)
+ goto bad_kernel;
+
+ /*
+ * We're at the ")", that's followed by " X ", where X is a
+ * single "state" character. So advance by 4 bytes.
+ */
+ ppid_lhs = comm_rhs + 4;
+
+ /*
+ * Read until the space between the "ppid" and "pgrp" fields
+ * to make sure we're anchored after the untruncated "ppid"
+ * field..
+ */
+ ppid_rhs = strchr(ppid_lhs, ' ');
+ if (!ppid_rhs)
+ goto bad_kernel;
+
+ ppid = strtol(ppid_lhs, &p, 10);
+ if (ppid_rhs == p) {
+ const char *comm = comm_lhs + 1;
+ size_t commlen = comm_rhs - comm;
+
+ strbuf_add(name, comm, commlen);
+ *statppid = ppid;
+
+ return 0;
+ }
+
+bad_kernel:
+ /*
+ * We were able to read our STAT_PARENT_PID_READ_N bytes from
+ * /proc/%d/stat, but the content is bad. Broken kernel?
+ * Should not happen, but handle it gracefully.
+ */
+ return -1;
+}
+
+static int stat_parent_pid(pid_t pid, struct strbuf *name, int *statppid)
+{
+ struct strbuf procfs_path = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ FILE *fp;
+ int ret = -1;
+
+ /* try to use procfs if it's present. */
+ strbuf_addf(&procfs_path, "/proc/%d/stat", pid);
+ fp = fopen(procfs_path.buf, "r");
+ if (!fp)
+ goto cleanup;
+
+ /*
+ * We could be more strict here and assert that we read at
+ * least STAT_PARENT_PID_READ_N. My reading of procfs(5) is
+ * that on any modern kernel (at least since 2.6.0 released in
+ * 2003) even if all the mandatory numeric fields were zero'd
+ * out we'd get at least 100 bytes, but let's just check that
+ * we got anything at all and trust the parse_proc_stat()
+ * function to handle its "Bad Kernel?" error checking.
+ */
+ if (!strbuf_fread(&sb, STAT_PARENT_PID_READ_N, fp))
+ goto cleanup;
+ if (parse_proc_stat(&sb, name, statppid) < 0)
+ goto cleanup;
+
+ ret = 0;
+cleanup:
+ if (fp)
+ fclose(fp);
+ strbuf_release(&procfs_path);
+ strbuf_release(&sb);
+
+ return ret;
+}
+
+static void push_ancestry_name(struct strvec *names, pid_t pid)
+{
+ struct strbuf name = STRBUF_INIT;
+ int ppid;
+
+ if (stat_parent_pid(pid, &name, &ppid) < 0)
+ goto cleanup;
+
+ strvec_push(names, name.buf);
+
+ /*
+ * Both errors and reaching the end of the process chain are
+ * reported as fields of 0 by proc(5)
+ */
+ if (ppid)
+ push_ancestry_name(names, ppid);
+cleanup:
+ strbuf_release(&name);
+
+ return;
+}
+
+void trace2_collect_process_info(enum trace2_process_info_reason reason)
+{
+ struct strvec names = STRVEC_INIT;
+
+ if (!trace2_is_enabled())
+ return;
+
+ switch (reason) {
+ case TRACE2_PROCESS_INFO_EXIT:
+ /*
+ * The Windows version of this calls its
+ * get_peak_memory_info() here. We may want to insert
+ * similar process-end statistics here in the future.
+ */
+ break;
+ case TRACE2_PROCESS_INFO_STARTUP:
+ push_ancestry_name(&names, getppid());
+
+ if (names.nr)
+ trace2_cmd_ancestry(names.v);
+ strvec_clear(&names);
+ break;
+ }
+
+ return;
+}
diff --git a/compat/mingw.c b/compat/mingw.c
index a00f331..4876344 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1,13 +1,23 @@
#include "../git-compat-util.h"
#include "win32.h"
+#include <aclapi.h>
+#include <sddl.h>
#include <conio.h>
#include <wchar.h>
#include "../strbuf.h"
#include "../run-command.h"
-#include "../cache.h"
+#include "../abspath.h"
+#include "../alloc.h"
#include "win32/lazyload.h"
#include "../config.h"
+#include "../environment.h"
+#include "../trace2.h"
+#include "../symlinks.h"
+#include "../wrapper.h"
#include "dir.h"
+#include "gettext.h"
+#define SECURITY_WIN32
+#include <sspi.h>
#define HCAST(type, handle) ((type)(intptr_t)handle)
@@ -192,16 +202,19 @@ static int read_yes_no_answer(void)
static int ask_yes_no_if_possible(const char *format, ...)
{
char question[4096];
- const char *retry_hook[] = { NULL, NULL, NULL };
+ const char *retry_hook;
va_list args;
va_start(args, format);
vsnprintf(question, sizeof(question), format, args);
va_end(args);
- if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) {
- retry_hook[1] = question;
- return !run_command_v_opt(retry_hook, 0);
+ retry_hook = mingw_getenv("GIT_ASK_YESNO");
+ if (retry_hook) {
+ struct child_process cmd = CHILD_PROCESS_INIT;
+
+ strvec_pushl(&cmd.args, retry_hook, question, NULL);
+ return !run_command(&cmd);
}
if (!isatty(_fileno(stdin)) || !isatty(_fileno(stderr)))
@@ -230,7 +243,8 @@ static int core_restrict_inherited_handles = -1;
static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
static char *unset_environment_variables;
-int mingw_core_config(const char *var, const char *value, void *cb)
+int mingw_core_config(const char *var, const char *value,
+ const struct config_context *ctx, void *cb)
{
if (!strcmp(var, "core.hidedotfiles")) {
if (value && !strcasecmp(value, "dotgitonly"))
@@ -241,6 +255,8 @@ int mingw_core_config(const char *var, const char *value, void *cb)
}
if (!strcmp(var, "core.unsetenvvars")) {
+ if (!value)
+ return config_error_nonbool(var);
free(unset_environment_variables);
unset_environment_variables = xstrdup(value);
return 0;
@@ -341,6 +357,27 @@ int mingw_rmdir(const char *pathname)
{
int ret, tries = 0;
wchar_t wpathname[MAX_PATH];
+ struct stat st;
+
+ /*
+ * Contrary to Linux' `rmdir()`, Windows' _wrmdir() and _rmdir()
+ * (and `RemoveDirectoryW()`) will attempt to remove the target of a
+ * symbolic link (if it points to a directory).
+ *
+ * This behavior breaks the assumption of e.g. `remove_path()` which
+ * upon successful deletion of a file will attempt to remove its parent
+ * directories recursively until failure (which usually happens when
+ * the directory is not empty).
+ *
+ * Therefore, before calling `_wrmdir()`, we first check if the path is
+ * a symbolic link. If it is, we exit and return the same error as
+ * Linux' `rmdir()` would, i.e. `ENOTDIR`.
+ */
+ if (!mingw_lstat(pathname, &st) && S_ISLNK(st.st_mode)) {
+ errno = ENOTDIR;
+ return -1;
+ }
+
if (xutftowcs_path(wpathname, pathname) < 0)
return -1;
@@ -367,6 +404,8 @@ int mingw_rmdir(const char *pathname)
ask_yes_no_if_possible("Deletion of directory '%s' failed. "
"Should I try again?", pathname))
ret = _wrmdir(wpathname);
+ if (!ret)
+ invalidate_lstat_cache();
return ret;
}
@@ -668,13 +707,24 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
{
ssize_t result = write(fd, buf, len);
- if (result < 0 && errno == EINVAL && buf) {
+ if (result < 0 && (errno == EINVAL || errno == ENOSPC) && buf) {
+ int orig = errno;
+
/* check if fd is a pipe */
HANDLE h = (HANDLE) _get_osfhandle(fd);
- if (GetFileType(h) == FILE_TYPE_PIPE)
+ if (GetFileType(h) != FILE_TYPE_PIPE)
+ errno = orig;
+ else if (orig == EINVAL)
errno = EPIPE;
- else
- errno = EINVAL;
+ else {
+ DWORD buf_size;
+
+ if (!GetNamedPipeInfo(h, NULL, NULL, &buf_size, NULL))
+ buf_size = 4096;
+ if (len > buf_size)
+ return write(fd, buf, buf_size);
+ errno = orig;
+ }
}
return result;
@@ -683,6 +733,8 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
int mingw_access(const char *filename, int mode)
{
wchar_t wfilename[MAX_PATH];
+ if (!strcmp("nul", filename) || !strcmp("/dev/null", filename))
+ return 0;
if (xutftowcs_path(wfilename, filename) < 0)
return -1;
/* X_OK is not supported by the MSVCRT version */
@@ -740,8 +792,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
wfilename[n] = L'\0';
attributes = GetFileAttributesW(wfilename);
wfilename[n] = c;
- if (attributes == FILE_ATTRIBUTE_DIRECTORY ||
- attributes == FILE_ATTRIBUTE_DEVICE)
+ if (attributes &
+ (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE))
return 1;
if (attributes == INVALID_FILE_ATTRIBUTES)
switch (GetLastError()) {
@@ -934,9 +986,11 @@ static inline void time_t_to_filetime(time_t t, FILETIME *ft)
int mingw_utime (const char *file_name, const struct utimbuf *times)
{
FILETIME mft, aft;
- int fh, rc;
+ int rc;
DWORD attrs;
wchar_t wfilename[MAX_PATH];
+ HANDLE osfilehandle;
+
if (xutftowcs_path(wfilename, file_name) < 0)
return -1;
@@ -948,7 +1002,17 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
SetFileAttributesW(wfilename, attrs & ~FILE_ATTRIBUTE_READONLY);
}
- if ((fh = _wopen(wfilename, O_RDWR | O_BINARY)) < 0) {
+ osfilehandle = CreateFileW(wfilename,
+ FILE_WRITE_ATTRIBUTES,
+ 0 /*FileShare.None*/,
+ NULL,
+ OPEN_EXISTING,
+ (attrs != INVALID_FILE_ATTRIBUTES &&
+ (attrs & FILE_ATTRIBUTE_DIRECTORY)) ?
+ FILE_FLAG_BACKUP_SEMANTICS : 0,
+ NULL);
+ if (osfilehandle == INVALID_HANDLE_VALUE) {
+ errno = err_win_to_posix(GetLastError());
rc = -1;
goto revert_attrs;
}
@@ -960,12 +1024,15 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
GetSystemTimeAsFileTime(&mft);
aft = mft;
}
- if (!SetFileTime((HANDLE)_get_osfhandle(fh), NULL, &aft, &mft)) {
+
+ if (!SetFileTime(osfilehandle, NULL, &aft, &mft)) {
errno = EINVAL;
rc = -1;
} else
rc = 0;
- close(fh);
+
+ if (osfilehandle != INVALID_HANDLE_VALUE)
+ CloseHandle(osfilehandle);
revert_attrs:
if (attrs != INVALID_FILE_ATTRIBUTES &&
@@ -983,7 +1050,7 @@ size_t mingw_strftime(char *s, size_t max,
/* a pointer to the original strftime in case we can't find the UCRT version */
static size_t (*fallback)(char *, size_t, const char *, const struct tm *) = strftime;
size_t ret;
- DECLARE_PROC_ADDR(ucrtbase.dll, size_t, strftime, char *, size_t,
+ DECLARE_PROC_ADDR(ucrtbase.dll, size_t, __cdecl, strftime, char *, size_t,
const char *, const struct tm *);
if (INIT_PROC_ADDR(strftime))
@@ -1016,10 +1083,7 @@ char *mingw_mktemp(char *template)
int mkstemp(char *template)
{
- char *filename = mktemp(template);
- if (filename == NULL)
- return -1;
- return open(filename, O_RDWR | O_CREAT, 0600);
+ return git_mkstemp_mode(template, 0600);
}
int gettimeofday(struct timeval *tv, void *tz)
@@ -1058,6 +1122,7 @@ int pipe(int filedes[2])
return 0;
}
+#ifndef __MINGW64__
struct tm *gmtime_r(const time_t *timep, struct tm *result)
{
if (gmtime_s(result, timep) == 0)
@@ -1071,6 +1136,7 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
return result;
return NULL;
}
+#endif
char *mingw_getcwd(char *pointer, int len)
{
@@ -1098,6 +1164,10 @@ char *mingw_getcwd(char *pointer, int len)
}
if (!ret || ret >= ARRAY_SIZE(wpointer))
return NULL;
+ if (GetFileAttributesW(wpointer) == INVALID_FILE_ATTRIBUTES) {
+ errno = ENOENT;
+ return NULL;
+ }
if (xwcstoutf(pointer, wpointer, len) < 0)
return NULL;
convert_slashes(pointer);
@@ -1290,6 +1360,11 @@ static char *path_lookup(const char *cmd, int exe_only)
return prog;
}
+char *mingw_locate_in_PATH(const char *cmd)
+{
+ return path_lookup(cmd, 0);
+}
+
static const wchar_t *wcschrnul(const wchar_t *s, wchar_t c)
{
while (*s && *s != c)
@@ -1346,8 +1421,7 @@ static wchar_t *make_environment_block(char **deltaenv)
p += s;
}
- ALLOC_ARRAY(result, size);
- COPY_ARRAY(result, wenv, size);
+ DUP_ARRAY(result, wenv, size);
FreeEnvironmentStringsW(wenv);
return result;
}
@@ -1789,16 +1863,13 @@ static int try_shell_exec(const char *cmd, char *const *argv)
if (prog) {
int exec_id;
int argc = 0;
-#ifndef _MSC_VER
- const
-#endif
char **argv2;
while (argv[argc]) argc++;
ALLOC_ARRAY(argv2, argc + 1);
argv2[0] = (char *)cmd; /* full path to the script file */
COPY_ARRAY(&argv2[1], &argv[1], argc);
- exec_id = trace2_exec(prog, argv2);
- pid = mingw_spawnv(prog, argv2, 1);
+ exec_id = trace2_exec(prog, (const char **)argv2);
+ pid = mingw_spawnv(prog, (const char **)argv2, 1);
if (pid >= 0) {
int status;
if (waitpid(pid, &status, 0) < 0)
@@ -2158,7 +2229,7 @@ enum EXTENDED_NAME_FORMAT {
static char *get_extended_user_info(enum EXTENDED_NAME_FORMAT type)
{
- DECLARE_PROC_ADDR(secur32.dll, BOOL, GetUserNameExW,
+ DECLARE_PROC_ADDR(secur32.dll, BOOL, SEC_ENTRY, GetUserNameExW,
enum EXTENDED_NAME_FORMAT, LPCWSTR, PULONG);
static wchar_t wbuffer[1024];
DWORD len;
@@ -2283,7 +2354,7 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out)
static const struct timeval zero;
static int atexit_done;
- if (out != NULL)
+ if (out)
return errno = EINVAL,
error("setitimer param 3 != NULL not implemented");
if (!is_timeval_eq(&in->it_interval, &zero) &&
@@ -2312,7 +2383,7 @@ int sigaction(int sig, struct sigaction *in, struct sigaction *out)
if (sig != SIGALRM)
return errno = EINVAL,
error("sigaction only implemented for SIGALRM");
- if (out != NULL)
+ if (out)
return errno = EINVAL,
error("sigaction: param 3 != NULL not implemented");
@@ -2597,6 +2668,192 @@ static void setup_windows_environment(void)
}
}
+static PSID get_current_user_sid(void)
+{
+ HANDLE token;
+ DWORD len = 0;
+ PSID result = NULL;
+
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
+ return NULL;
+
+ if (!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
+ TOKEN_USER *info = xmalloc((size_t)len);
+ if (GetTokenInformation(token, TokenUser, info, len, &len)) {
+ len = GetLengthSid(info->User.Sid);
+ result = xmalloc(len);
+ if (!CopySid(len, result, info->User.Sid)) {
+ error(_("failed to copy SID (%ld)"),
+ GetLastError());
+ FREE_AND_NULL(result);
+ }
+ }
+ FREE_AND_NULL(info);
+ }
+ CloseHandle(token);
+
+ return result;
+}
+
+static BOOL user_sid_to_user_name(PSID sid, LPSTR *str)
+{
+ SID_NAME_USE pe_use;
+ DWORD len_user = 0, len_domain = 0;
+ BOOL translate_sid_to_user;
+
+ /*
+ * returns only FALSE, because the string pointers are NULL
+ */
+ LookupAccountSidA(NULL, sid, NULL, &len_user, NULL, &len_domain,
+ &pe_use);
+ /*
+ * Alloc needed space of the strings
+ */
+ ALLOC_ARRAY((*str), (size_t)len_domain + (size_t)len_user);
+ translate_sid_to_user = LookupAccountSidA(NULL, sid,
+ (*str) + len_domain, &len_user, *str, &len_domain, &pe_use);
+ if (!translate_sid_to_user)
+ FREE_AND_NULL(*str);
+ else
+ (*str)[len_domain] = '/';
+ return translate_sid_to_user;
+}
+
+static int acls_supported(const char *path)
+{
+ size_t offset = offset_1st_component(path);
+ WCHAR wroot[MAX_PATH];
+ DWORD file_system_flags;
+
+ if (offset &&
+ xutftowcsn(wroot, path, MAX_PATH, offset) > 0 &&
+ GetVolumeInformationW(wroot, NULL, 0, NULL, NULL,
+ &file_system_flags, NULL, 0))
+ return !!(file_system_flags & FILE_PERSISTENT_ACLS);
+
+ return 0;
+}
+
+int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
+{
+ WCHAR wpath[MAX_PATH];
+ PSID sid = NULL;
+ PSECURITY_DESCRIPTOR descriptor = NULL;
+ DWORD err;
+
+ static wchar_t home[MAX_PATH];
+
+ int result = 0;
+
+ if (xutftowcs_path(wpath, path) < 0)
+ return 0;
+
+ /*
+ * On Windows, the home directory is owned by the administrator, but for
+ * all practical purposes, it belongs to the user. Do pretend that it is
+ * owned by the user.
+ */
+ if (!*home) {
+ DWORD size = ARRAY_SIZE(home);
+ DWORD len = GetEnvironmentVariableW(L"HOME", home, size);
+ if (!len || len > size)
+ wcscpy(home, L"::N/A::");
+ }
+ if (!wcsicmp(wpath, home))
+ return 1;
+
+ /* Get the owner SID */
+ err = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT,
+ OWNER_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION,
+ &sid, NULL, NULL, NULL, &descriptor);
+
+ if (err != ERROR_SUCCESS)
+ error(_("failed to get owner for '%s' (%ld)"), path, err);
+ else if (sid && IsValidSid(sid)) {
+ /* Now, verify that the SID matches the current user's */
+ static PSID current_user_sid;
+ BOOL is_member;
+
+ if (!current_user_sid)
+ current_user_sid = get_current_user_sid();
+
+ if (current_user_sid &&
+ IsValidSid(current_user_sid) &&
+ EqualSid(sid, current_user_sid))
+ result = 1;
+ else if (IsWellKnownSid(sid, WinBuiltinAdministratorsSid) &&
+ CheckTokenMembership(NULL, sid, &is_member) &&
+ is_member)
+ /*
+ * If owned by the Administrators group, and the
+ * current user is an administrator, we consider that
+ * okay, too.
+ */
+ result = 1;
+ else if (report &&
+ IsWellKnownSid(sid, WinWorldSid) &&
+ !acls_supported(path)) {
+ /*
+ * On FAT32 volumes, ownership is not actually recorded.
+ */
+ strbuf_addf(report, "'%s' is on a file system that does "
+ "not record ownership\n", path);
+ } else if (report) {
+ LPSTR str1, str2, str3, str4, to_free1 = NULL,
+ to_free3 = NULL, to_local_free2 = NULL,
+ to_local_free4 = NULL;
+
+ if (user_sid_to_user_name(sid, &str1))
+ to_free1 = str1;
+ else
+ str1 = "(inconvertible)";
+ if (ConvertSidToStringSidA(sid, &str2))
+ to_local_free2 = str2;
+ else
+ str2 = "(inconvertible)";
+
+ if (!current_user_sid) {
+ str3 = "(none)";
+ str4 = "(none)";
+ }
+ else if (!IsValidSid(current_user_sid)) {
+ str3 = "(invalid)";
+ str4 = "(invalid)";
+ } else {
+ if (user_sid_to_user_name(current_user_sid,
+ &str3))
+ to_free3 = str3;
+ else
+ str3 = "(inconvertible)";
+ if (ConvertSidToStringSidA(current_user_sid,
+ &str4))
+ to_local_free4 = str4;
+ else
+ str4 = "(inconvertible)";
+ }
+ strbuf_addf(report,
+ "'%s' is owned by:\n"
+ "\t%s (%s)\nbut the current user is:\n"
+ "\t%s (%s)\n",
+ path, str1, str2, str3, str4);
+ free(to_free1);
+ LocalFree(to_local_free2);
+ free(to_free3);
+ LocalFree(to_local_free4);
+ }
+ }
+
+ /*
+ * We can release the security descriptor struct only now because `sid`
+ * actually points into this struct.
+ */
+ if (descriptor)
+ LocalFree(descriptor);
+
+ return result;
+}
+
int is_valid_win32_path(const char *path, int allow_literal_nul)
{
const char *p = path;
@@ -2695,7 +2952,7 @@ not_a_reserved_name:
}
c = path[i];
- if (c && c != '.' && c != ':' && c != '/' && c != '\\')
+ if (c && c != '.' && c != ':' && !is_xplatform_dir_sep(c))
goto not_a_reserved_name;
/* contains reserved name */
@@ -2901,3 +3158,22 @@ int uname(struct utsname *buf)
"%u", (v >> 16) & 0x7fff);
return 0;
}
+
+int mingw_have_unix_sockets(void)
+{
+ SC_HANDLE scm, srvc;
+ SERVICE_STATUS_PROCESS status;
+ DWORD bytes;
+ int ret = 0;
+ scm = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
+ if (scm) {
+ srvc = OpenServiceA(scm, "afunix", SERVICE_QUERY_STATUS);
+ if (srvc) {
+ if(QueryServiceStatusEx(srvc, SC_STATUS_PROCESS_INFO, (LPBYTE)&status, sizeof(SERVICE_STATUS_PROCESS), &bytes))
+ ret = status.dwCurrentState == SERVICE_RUNNING;
+ CloseServiceHandle(srvc);
+ }
+ CloseServiceHandle(scm);
+ }
+ return ret;
+}
diff --git a/compat/mingw.h b/compat/mingw.h
index af8eddd..27b6128 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -11,7 +11,9 @@ typedef _sigset_t sigset_t;
#undef _POSIX_THREAD_SAFE_FUNCTIONS
#endif
-int mingw_core_config(const char *var, const char *value, void *cb);
+struct config_context;
+int mingw_core_config(const char *var, const char *value,
+ const struct config_context *ctx, void *cb);
#define platform_core_config mingw_core_config
/*
@@ -175,6 +177,9 @@ pid_t waitpid(pid_t pid, int *status, int options);
#define kill mingw_kill
int mingw_kill(pid_t pid, int sig);
+#define locate_in_PATH mingw_locate_in_PATH
+char *mingw_locate_in_PATH(const char *cmd);
+
#ifndef NO_OPENSSL
#include <openssl/ssl.h>
static inline int mingw_SSL_set_fd(SSL *ssl, int fd)
@@ -227,6 +232,7 @@ int mingw_rmdir(const char *path);
int mingw_open (const char *filename, int oflags, ...);
#define open mingw_open
+#undef OPEN_RETURNS_EINTR
int mingw_fgetc(FILE *stream);
#define fgetc mingw_fgetc
@@ -328,6 +334,12 @@ int mingw_getpagesize(void);
#define getpagesize mingw_getpagesize
#endif
+int win32_fsync_no_flush(int fd);
+#define fsync_no_flush win32_fsync_no_flush
+
+#define FSYNC_COMPONENTS_PLATFORM_DEFAULT (FSYNC_COMPONENTS_DEFAULT | FSYNC_COMPONENT_LOOSE_OBJECT)
+#define FSYNC_METHOD_DEFAULT (FSYNC_METHOD_BATCH)
+
struct rlimit {
unsigned int rlim_cur;
};
@@ -453,6 +465,13 @@ char *mingw_query_user_email(void);
#endif
/**
+ * Verifies that the specified path is owned by the user running the
+ * current process.
+ */
+int is_path_owned_by_current_sid(const char *path, struct strbuf *report);
+#define is_path_owned_by_current_user is_path_owned_by_current_sid
+
+/**
* Verifies that the given path is a valid one on Windows.
*
* In particular, path segments are disallowed which
@@ -612,3 +631,9 @@ void open_in_gdb(void);
* Used by Pthread API implementation for Windows
*/
int err_win_to_posix(DWORD winerr);
+
+#ifndef NO_UNIX_SOCKETS
+int mingw_have_unix_sockets(void);
+#undef have_unix_sockets
+#define have_unix_sockets mingw_have_unix_sockets
+#endif
diff --git a/compat/mkdir.c b/compat/mkdir.c
index 9e253fb..02aea3b 100644
--- a/compat/mkdir.c
+++ b/compat/mkdir.c
@@ -9,7 +9,7 @@ int compat_mkdir_wo_trailing_slash(const char *dir, mode_t mode)
size_t len = strlen(dir);
if (len && dir[len-1] == '/') {
- if ((tmp_dir = strdup(dir)) == NULL)
+ if (!(tmp_dir = strdup(dir)))
return -1;
tmp_dir[len-1] = '\0';
}
diff --git a/compat/mmap.c b/compat/mmap.c
index 14d3101..2fe1c77 100644
--- a/compat/mmap.c
+++ b/compat/mmap.c
@@ -7,8 +7,13 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
if (start != NULL || flags != MAP_PRIVATE || prot != PROT_READ)
die("Invalid usage of mmap when built with NO_MMAP");
- start = xmalloc(length);
- if (start == NULL) {
+ if (length == 0) {
+ errno = EINVAL;
+ return MAP_FAILED;
+ }
+
+ start = malloc(length);
+ if (!start) {
errno = ENOMEM;
return MAP_FAILED;
}
diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c
index 1cc31c3..2c0ace7 100644
--- a/compat/nedmalloc/nedmalloc.c
+++ b/compat/nedmalloc/nedmalloc.c
@@ -323,7 +323,6 @@ static NOINLINE void RemoveCacheEntries(nedpool *p, threadcache *tc, unsigned in
}
static void DestroyCaches(nedpool *p) THROWSPEC
{
- if(p->caches)
{
threadcache *tc;
int n;
@@ -510,7 +509,7 @@ static void threadcache_free(nedpool *p, threadcache *tc, int mymspace, void *me
assert(idx<=THREADCACHEMAXBINS);
if(tck==*binsptr)
{
- fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", tck);
+ fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", (void *)tck);
abort();
}
#ifdef FULLSANITYCHECKS
diff --git a/compat/nonblock.c b/compat/nonblock.c
new file mode 100644
index 0000000..5b51195
--- /dev/null
+++ b/compat/nonblock.c
@@ -0,0 +1,50 @@
+#include "git-compat-util.h"
+#include "nonblock.h"
+
+#ifdef O_NONBLOCK
+
+int enable_pipe_nonblock(int fd)
+{
+ int flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return -1;
+ flags |= O_NONBLOCK;
+ return fcntl(fd, F_SETFL, flags);
+}
+
+#elif defined(GIT_WINDOWS_NATIVE)
+
+#include "win32.h"
+
+int enable_pipe_nonblock(int fd)
+{
+ HANDLE h = (HANDLE)_get_osfhandle(fd);
+ DWORD mode;
+ DWORD type = GetFileType(h);
+ if (type == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR) {
+ errno = EBADF;
+ return -1;
+ }
+ if (type != FILE_TYPE_PIPE)
+ BUG("unsupported file type: %lu", type);
+ if (!GetNamedPipeHandleState(h, &mode, NULL, NULL, NULL, NULL, 0)) {
+ errno = err_win_to_posix(GetLastError());
+ return -1;
+ }
+ mode |= PIPE_NOWAIT;
+ if (!SetNamedPipeHandleState(h, &mode, NULL, NULL)) {
+ errno = err_win_to_posix(GetLastError());
+ return -1;
+ }
+ return 0;
+}
+
+#else
+
+int enable_pipe_nonblock(int fd UNUSED)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+#endif
diff --git a/compat/nonblock.h b/compat/nonblock.h
new file mode 100644
index 0000000..af1a331
--- /dev/null
+++ b/compat/nonblock.h
@@ -0,0 +1,9 @@
+#ifndef COMPAT_NONBLOCK_H
+#define COMPAT_NONBLOCK_H
+
+/*
+ * Enable non-blocking I/O for the pipe specified by the passed-in descriptor.
+ */
+int enable_pipe_nonblock(int fd);
+
+#endif
diff --git a/compat/open.c b/compat/open.c
new file mode 100644
index 0000000..eb3754a
--- /dev/null
+++ b/compat/open.c
@@ -0,0 +1,25 @@
+#include "git-compat-util.h"
+
+#undef open
+int git_open_with_retry(const char *path, int flags, ...)
+{
+ mode_t mode = 0;
+ int ret;
+
+ /*
+ * Also O_TMPFILE would take a mode, but it isn't defined everywhere.
+ * And anyway, we don't use it in our code base.
+ */
+ if (flags & O_CREAT) {
+ va_list ap;
+ va_start(ap, flags);
+ mode = va_arg(ap, int);
+ va_end(ap);
+ }
+
+ do {
+ ret = open(path, flags, mode);
+ } while (ret < 0 && errno == EINTR);
+
+ return ret;
+}
diff --git a/compat/pread.c b/compat/pread.c
index 978cac4..484e6d4 100644
--- a/compat/pread.c
+++ b/compat/pread.c
@@ -1,4 +1,5 @@
#include "../git-compat-util.h"
+#include "../wrapper.h"
ssize_t git_pread(int fd, void *buf, size_t count, off_t offset)
{
diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c
index 136250f..0bd5c24 100644
--- a/compat/precompose_utf8.c
+++ b/compat/precompose_utf8.c
@@ -5,8 +5,12 @@
#define PRECOMPOSE_UNICODE_C
-#include "cache.h"
+#include "git-compat-util.h"
#include "config.h"
+#include "environment.h"
+#include "gettext.h"
+#include "path.h"
+#include "strbuf.h"
#include "utf8.h"
#include "precompose_utf8.h"
@@ -60,32 +64,45 @@ void probe_utf8_pathname_composition(void)
strbuf_release(&path);
}
-
-void precompose_argv(int argc, const char **argv)
+const char *precompose_string_if_needed(const char *in)
{
- int i = 0;
- const char *oldarg;
- char *newarg;
- iconv_t ic_precompose;
+ size_t inlen;
+ size_t outlen;
+ if (!in)
+ return NULL;
+ if (has_non_ascii(in, (size_t)-1, &inlen)) {
+ iconv_t ic_prec;
+ char *out;
+ if (precomposed_unicode < 0)
+ git_config_get_bool("core.precomposeunicode", &precomposed_unicode);
+ if (precomposed_unicode != 1)
+ return in;
+ ic_prec = iconv_open(repo_encoding, path_encoding);
+ if (ic_prec == (iconv_t) -1)
+ return in;
+
+ out = reencode_string_iconv(in, inlen, ic_prec, 0, &outlen);
+ if (out) {
+ if (outlen == inlen && !memcmp(in, out, outlen))
+ free(out); /* no need to return indentical */
+ else
+ in = out;
+ }
+ iconv_close(ic_prec);
- if (precomposed_unicode != 1)
- return;
+ }
+ return in;
+}
- ic_precompose = iconv_open(repo_encoding, path_encoding);
- if (ic_precompose == (iconv_t) -1)
- return;
+const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix)
+{
+ int i = 0;
while (i < argc) {
- size_t namelen;
- oldarg = argv[i];
- if (has_non_ascii(oldarg, (size_t)-1, &namelen)) {
- newarg = reencode_string_iconv(oldarg, namelen, ic_precompose, 0, NULL);
- if (newarg)
- argv[i] = newarg;
- }
+ argv[i] = precompose_string_if_needed(argv[i]);
i++;
}
- iconv_close(ic_precompose);
+ return precompose_string_if_needed(prefix);
}
diff --git a/compat/precompose_utf8.h b/compat/precompose_utf8.h
index 6f843d3..fea06cf 100644
--- a/compat/precompose_utf8.h
+++ b/compat/precompose_utf8.h
@@ -28,7 +28,8 @@ typedef struct {
struct dirent_prec_psx *dirent_nfc;
} PREC_DIR;
-void precompose_argv(int argc, const char **argv);
+const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix);
+const char *precompose_string_if_needed(const char *in);
void probe_utf8_pathname_composition(void);
PREC_DIR *precompose_utf8_opendir(const char *dirname);
diff --git a/compat/qsort_s.c b/compat/qsort_s.c
index 52d1f0a..0f7ff30 100644
--- a/compat/qsort_s.c
+++ b/compat/qsort_s.c
@@ -49,21 +49,15 @@ int git_qsort_s(void *b, size_t n, size_t s,
int (*cmp)(const void *, const void *, void *), void *ctx)
{
const size_t size = st_mult(n, s);
- char buf[1024];
+ char *tmp;
if (!n)
return 0;
if (!b || !cmp)
return -1;
- if (size < sizeof(buf)) {
- /* The temporary array fits on the small on-stack buffer. */
- msort_with_tmp(b, n, s, cmp, buf, ctx);
- } else {
- /* It's somewhat large, so malloc it. */
- char *tmp = xmalloc(size);
- msort_with_tmp(b, n, s, cmp, tmp, ctx);
- free(tmp);
- }
+ tmp = xmalloc(size);
+ msort_with_tmp(b, n, s, cmp, tmp, ctx);
+ free(tmp);
return 0;
}
diff --git a/compat/regcomp_enhanced.c b/compat/regcomp_enhanced.c
new file mode 100644
index 0000000..84193ce
--- /dev/null
+++ b/compat/regcomp_enhanced.c
@@ -0,0 +1,9 @@
+#include "../git-compat-util.h"
+#undef regcomp
+
+int git_regcomp(regex_t *preg, const char *pattern, int cflags)
+{
+ if (!(cflags & REG_EXTENDED))
+ cflags |= REG_ENHANCED;
+ return regcomp(preg, pattern, cflags);
+}
diff --git a/compat/sha1-chunked.c b/compat/sha1-chunked.c
index 6adfcfd..a4a6f93 100644
--- a/compat/sha1-chunked.c
+++ b/compat/sha1-chunked.c
@@ -1,4 +1,5 @@
-#include "cache.h"
+#include "git-compat-util.h"
+#include "hash-ll.h"
int git_SHA1_Update_Chunked(platform_SHA_CTX *c, const void *data, size_t len)
{
diff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c
new file mode 100644
index 0000000..cb176d9
--- /dev/null
+++ b/compat/simple-ipc/ipc-shared.c
@@ -0,0 +1,29 @@
+#include "git-compat-util.h"
+#include "simple-ipc.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
+
+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,
+ ipc_server_application_cb *application_cb,
+ void *application_data)
+{
+ struct ipc_server_data *server_data = NULL;
+ int ret;
+
+ ret = ipc_server_run_async(&server_data, path, opts,
+ application_cb, application_data);
+ if (ret)
+ return ret;
+
+ ret = ipc_server_await(server_data);
+
+ ipc_server_free(server_data);
+
+ return ret;
+}
diff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c
new file mode 100644
index 0000000..9b3f2cd
--- /dev/null
+++ b/compat/simple-ipc/ipc-unix-socket.c
@@ -0,0 +1,1030 @@
+#include "git-compat-util.h"
+#include "gettext.h"
+#include "simple-ipc.h"
+#include "strbuf.h"
+#include "thread-utils.h"
+#include "trace2.h"
+#include "unix-socket.h"
+#include "unix-stream-server.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
+
+enum ipc_active_state ipc_get_active_state(const char *path)
+{
+ enum ipc_active_state state = IPC_STATE__OTHER_ERROR;
+ struct ipc_client_connect_options options
+ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
+ struct stat st;
+ struct ipc_client_connection *connection_test = NULL;
+
+ options.wait_if_busy = 0;
+ options.wait_if_not_found = 0;
+
+ if (lstat(path, &st) == -1) {
+ switch (errno) {
+ case ENOENT:
+ case ENOTDIR:
+ return IPC_STATE__NOT_LISTENING;
+ default:
+ return IPC_STATE__INVALID_PATH;
+ }
+ }
+
+#ifdef __CYGWIN__
+ /*
+ * Cygwin emulates Unix sockets by writing special-crafted files whose
+ * `system` bit is set.
+ *
+ * If we are too fast, Cygwin might still be in the process of marking
+ * the underlying file as a system file. Until then, we will not see a
+ * Unix socket here, but a plain file instead. Just in case that this
+ * is happening, wait a little and try again.
+ */
+ {
+ static const int delay[] = { 1, 10, 20, 40, -1 };
+ int i;
+
+ for (i = 0; S_ISREG(st.st_mode) && delay[i] > 0; i++) {
+ sleep_millisec(delay[i]);
+ if (lstat(path, &st) == -1)
+ return IPC_STATE__INVALID_PATH;
+ }
+ }
+#endif
+
+ /* also complain if a plain file is in the way */
+ if ((st.st_mode & S_IFMT) != S_IFSOCK)
+ return IPC_STATE__INVALID_PATH;
+
+ /*
+ * Just because the filesystem has a S_IFSOCK type inode
+ * at `path`, doesn't mean it that there is a server listening.
+ * Ping it to be sure.
+ */
+ state = ipc_client_try_connect(path, &options, &connection_test);
+ ipc_client_close_connection(connection_test);
+
+ return state;
+}
+
+/*
+ * Retry frequency when trying to connect to a server.
+ *
+ * This value should be short enough that we don't seriously delay our
+ * caller, but not fast enough that our spinning puts pressure on the
+ * system.
+ */
+#define WAIT_STEP_MS (50)
+
+/*
+ * Try to connect to the server. If the server is just starting up or
+ * is very busy, we may not get a connection the first time.
+ */
+static enum ipc_active_state connect_to_server(
+ const char *path,
+ int timeout_ms,
+ const struct ipc_client_connect_options *options,
+ int *pfd)
+{
+ int k;
+
+ *pfd = -1;
+
+ for (k = 0; k < timeout_ms; k += WAIT_STEP_MS) {
+ int fd = unix_stream_connect(path, options->uds_disallow_chdir);
+
+ if (fd != -1) {
+ *pfd = fd;
+ return IPC_STATE__LISTENING;
+ }
+
+ if (errno == ENOENT) {
+ if (!options->wait_if_not_found)
+ return IPC_STATE__PATH_NOT_FOUND;
+
+ goto sleep_and_try_again;
+ }
+
+ if (errno == ETIMEDOUT) {
+ if (!options->wait_if_busy)
+ return IPC_STATE__NOT_LISTENING;
+
+ goto sleep_and_try_again;
+ }
+
+ if (errno == ECONNREFUSED) {
+ if (!options->wait_if_busy)
+ return IPC_STATE__NOT_LISTENING;
+
+ goto sleep_and_try_again;
+ }
+
+ return IPC_STATE__OTHER_ERROR;
+
+ sleep_and_try_again:
+ sleep_millisec(WAIT_STEP_MS);
+ }
+
+ return IPC_STATE__NOT_LISTENING;
+}
+
+/*
+ * The total amount of time that we are willing to wait when trying to
+ * connect to a server.
+ *
+ * When the server is first started, it might take a little while for
+ * it to become ready to service requests. Likewise, the server may
+ * be very (temporarily) busy and not respond to our connections.
+ *
+ * We should gracefully and silently handle those conditions and try
+ * again for a reasonable time period.
+ *
+ * The value chosen here should be long enough for the server
+ * to reliably heal from the above conditions.
+ */
+#define MY_CONNECTION_TIMEOUT_MS (1000)
+
+enum ipc_active_state ipc_client_try_connect(
+ const char *path,
+ const struct ipc_client_connect_options *options,
+ struct ipc_client_connection **p_connection)
+{
+ 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);
+
+ state = connect_to_server(path, MY_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;
+ }
+
+ 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 *answer)
+{
+ 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,
+ answer);
+
+ ipc_client_close_connection(connection);
+
+ return ret;
+}
+
+static int set_socket_blocking_flag(int fd, int make_nonblocking)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL, NULL);
+
+ if (flags < 0)
+ return -1;
+
+ if (make_nonblocking)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ return fcntl(fd, F_SETFL, flags);
+}
+
+/*
+ * 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_WORKER_THREAD_DATA,
+ MAGIC_ACCEPT_THREAD_DATA,
+ MAGIC_SERVER_DATA,
+};
+
+struct ipc_server_reply_data {
+ enum magic magic;
+ int fd;
+ struct ipc_worker_thread_data *worker_thread_data;
+};
+
+struct ipc_worker_thread_data {
+ enum magic magic;
+ struct ipc_worker_thread_data *next_thread;
+ struct ipc_server_data *server_data;
+ pthread_t pthread_id;
+};
+
+struct ipc_accept_thread_data {
+ enum magic magic;
+ struct ipc_server_data *server_data;
+
+ struct unix_ss_socket *server_socket;
+
+ int fd_send_shutdown;
+ int fd_wait_shutdown;
+ pthread_t pthread_id;
+};
+
+/*
+ * With unix-sockets, the conceptual "ipc-server" is implemented as a single
+ * controller "accept-thread" thread and a pool of "worker-thread" threads.
+ * The former does the usual `accept()` loop and dispatches connections
+ * to an idle worker thread. The worker threads wait in an idle loop for
+ * a new connection, communicate with the client and relay data to/from
+ * the `application_cb` and then wait for another connection from the
+ * server thread. This avoids the overhead of constantly creating and
+ * destroying threads.
+ */
+struct ipc_server_data {
+ enum magic magic;
+ ipc_server_application_cb *application_cb;
+ void *application_data;
+ struct strbuf buf_path;
+
+ struct ipc_accept_thread_data *accept_thread;
+ struct ipc_worker_thread_data *worker_thread_list;
+
+ pthread_mutex_t work_available_mutex;
+ pthread_cond_t work_available_cond;
+
+ /*
+ * Accepted but not yet processed client connections are kept
+ * in a circular buffer FIFO. The queue is empty when the
+ * positions are equal.
+ */
+ int *fifo_fds;
+ int queue_size;
+ int back_pos;
+ int front_pos;
+
+ int shutdown_requested;
+ int is_stopped;
+};
+
+/*
+ * Remove and return the oldest queued connection.
+ *
+ * Returns -1 if empty.
+ */
+static int fifo_dequeue(struct ipc_server_data *server_data)
+{
+ /* ASSERT holding mutex */
+
+ int fd;
+
+ if (server_data->back_pos == server_data->front_pos)
+ return -1;
+
+ fd = server_data->fifo_fds[server_data->front_pos];
+ server_data->fifo_fds[server_data->front_pos] = -1;
+
+ server_data->front_pos++;
+ if (server_data->front_pos == server_data->queue_size)
+ server_data->front_pos = 0;
+
+ return fd;
+}
+
+/*
+ * Push a new fd onto the back of the queue.
+ *
+ * Drop it and return -1 if queue is already full.
+ */
+static int fifo_enqueue(struct ipc_server_data *server_data, int fd)
+{
+ /* ASSERT holding mutex */
+
+ int next_back_pos;
+
+ next_back_pos = server_data->back_pos + 1;
+ if (next_back_pos == server_data->queue_size)
+ next_back_pos = 0;
+
+ if (next_back_pos == server_data->front_pos) {
+ /* Queue is full. Just drop it. */
+ close(fd);
+ return -1;
+ }
+
+ server_data->fifo_fds[server_data->back_pos] = fd;
+ server_data->back_pos = next_back_pos;
+
+ return fd;
+}
+
+/*
+ * Wait for a connection to be queued to the FIFO and return it.
+ *
+ * Returns -1 if someone has already requested a shutdown.
+ */
+static int worker_thread__wait_for_connection(
+ struct ipc_worker_thread_data *worker_thread_data)
+{
+ /* ASSERT NOT holding mutex */
+
+ struct ipc_server_data *server_data = worker_thread_data->server_data;
+ int fd = -1;
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+ for (;;) {
+ if (server_data->shutdown_requested)
+ break;
+
+ fd = fifo_dequeue(server_data);
+ if (fd >= 0)
+ break;
+
+ pthread_cond_wait(&server_data->work_available_cond,
+ &server_data->work_available_mutex);
+ }
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+
+ return fd;
+}
+
+/*
+ * 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);
+}
+
+/* A randomly chosen value. */
+#define MY_WAIT_POLL_TIMEOUT_MS (10)
+
+/*
+ * If the client hangs up without sending any data on the wire, just
+ * quietly close the socket and ignore this client.
+ *
+ * This worker thread is committed to reading the IPC request data
+ * from the client at the other end of this fd. Wait here for the
+ * client to actually put something on the wire -- because if the
+ * client just does a ping (connect and hangup without sending any
+ * data), our use of the pkt-line read routines will spew an error
+ * message.
+ *
+ * Return -1 if the client hung up.
+ * Return 0 if data (possibly incomplete) is ready.
+ */
+static int worker_thread__wait_for_io_start(
+ struct ipc_worker_thread_data *worker_thread_data,
+ int fd)
+{
+ struct ipc_server_data *server_data = worker_thread_data->server_data;
+ struct pollfd pollfd[1];
+ int result;
+
+ for (;;) {
+ pollfd[0].fd = fd;
+ pollfd[0].events = POLLIN;
+
+ result = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS);
+ if (result < 0) {
+ if (errno == EINTR)
+ continue;
+ goto cleanup;
+ }
+
+ if (result == 0) {
+ /* a timeout */
+
+ int in_shutdown;
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+ in_shutdown = server_data->shutdown_requested;
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+
+ /*
+ * If a shutdown is already in progress and this
+ * client has not started talking yet, just drop it.
+ */
+ if (in_shutdown)
+ goto cleanup;
+ continue;
+ }
+
+ if (pollfd[0].revents & POLLHUP)
+ goto cleanup;
+
+ if (pollfd[0].revents & POLLIN)
+ return 0;
+
+ goto cleanup;
+ }
+
+cleanup:
+ close(fd);
+ return -1;
+}
+
+/*
+ * 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.
+ */
+static int worker_thread__do_io(
+ struct ipc_worker_thread_data *worker_thread_data,
+ int fd)
+{
+ /* ASSERT NOT holding lock */
+
+ struct strbuf buf = STRBUF_INIT;
+ struct ipc_server_reply_data reply_data;
+ int ret = 0;
+
+ reply_data.magic = MAGIC_SERVER_REPLY_DATA;
+ reply_data.worker_thread_data = worker_thread_data;
+
+ reply_data.fd = fd;
+
+ ret = read_packetized_to_strbuf(
+ reply_data.fd, &buf,
+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);
+ if (ret >= 0) {
+ ret = worker_thread_data->server_data->application_cb(
+ worker_thread_data->server_data->application_data,
+ buf.buf, buf.len, do_io_reply_callback, &reply_data);
+
+ packet_flush_gently(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;
+}
+
+/*
+ * Block SIGPIPE on the current thread (so that we get EPIPE from
+ * write() rather than an actual signal).
+ *
+ * Note that using sigchain_push() and _pop() to control SIGPIPE
+ * around our IO calls is not thread safe:
+ * [] It uses a global stack of handler frames.
+ * [] It uses ALLOC_GROW() to resize it.
+ * [] Finally, according to the `signal(2)` man-page:
+ * "The effects of `signal()` in a multithreaded process are unspecified."
+ */
+static void thread_block_sigpipe(sigset_t *old_set)
+{
+ sigset_t new_set;
+
+ sigemptyset(&new_set);
+ sigaddset(&new_set, SIGPIPE);
+
+ sigemptyset(old_set);
+ pthread_sigmask(SIG_BLOCK, &new_set, old_set);
+}
+
+/*
+ * Thread proc for an IPC worker thread. It handles a series of
+ * connections from clients. It pulls the next fd from the queue
+ * processes it, and then waits for the next client.
+ *
+ * Block SIGPIPE in this worker thread for the life of the thread.
+ * This avoids stray (and sometimes delayed) SIGPIPE signals caused
+ * by client errors and/or when we are under extremely heavy IO load.
+ *
+ * This means that the application callback will have SIGPIPE blocked.
+ * The callback should not change it.
+ */
+static void *worker_thread_proc(void *_worker_thread_data)
+{
+ struct ipc_worker_thread_data *worker_thread_data = _worker_thread_data;
+ struct ipc_server_data *server_data = worker_thread_data->server_data;
+ sigset_t old_set;
+ int fd, io;
+ int ret;
+
+ trace2_thread_start("ipc-worker");
+
+ thread_block_sigpipe(&old_set);
+
+ for (;;) {
+ fd = worker_thread__wait_for_connection(worker_thread_data);
+ if (fd == -1)
+ break; /* in shutdown */
+
+ io = worker_thread__wait_for_io_start(worker_thread_data, fd);
+ if (io == -1)
+ continue; /* client hung up without sending anything */
+
+ ret = worker_thread__do_io(worker_thread_data, fd);
+
+ if (ret == SIMPLE_IPC_QUIT) {
+ trace2_data_string("ipc-worker", NULL, "queue_stop_async",
+ "application_quit");
+ /*
+ * The application layer is telling the ipc-server
+ * layer to shutdown.
+ *
+ * We DO NOT have a response to send to the client.
+ *
+ * Queue an async stop (to stop the other threads) and
+ * allow this worker thread to exit now (no sense waiting
+ * for the thread-pool shutdown signal).
+ *
+ * Other non-idle worker threads are allowed to finish
+ * responding to their current clients.
+ */
+ ipc_server_stop_async(server_data);
+ break;
+ }
+ }
+
+ trace2_thread_exit();
+ return NULL;
+}
+
+/* A randomly chosen value. */
+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)
+
+/*
+ * Accept a new client connection on our socket. This uses non-blocking
+ * IO so that we can also wait for shutdown requests on our socket-pair
+ * without actually spinning on a fast timeout.
+ */
+static int accept_thread__wait_for_connection(
+ struct ipc_accept_thread_data *accept_thread_data)
+{
+ struct pollfd pollfd[2];
+ int result;
+
+ for (;;) {
+ pollfd[0].fd = accept_thread_data->fd_wait_shutdown;
+ pollfd[0].events = POLLIN;
+
+ pollfd[1].fd = accept_thread_data->server_socket->fd_socket;
+ pollfd[1].events = POLLIN;
+
+ result = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);
+ if (result < 0) {
+ if (errno == EINTR)
+ continue;
+ return result;
+ }
+
+ if (result == 0) {
+ /* a timeout */
+
+ /*
+ * If someone deletes or force-creates a new unix
+ * domain socket at our path, all future clients
+ * will be routed elsewhere and we silently starve.
+ * If that happens, just queue a shutdown.
+ */
+ if (unix_ss_was_stolen(
+ accept_thread_data->server_socket)) {
+ trace2_data_string("ipc-accept", NULL,
+ "queue_stop_async",
+ "socket_stolen");
+ ipc_server_stop_async(
+ accept_thread_data->server_data);
+ }
+ continue;
+ }
+
+ if (pollfd[0].revents & POLLIN) {
+ /* shutdown message queued to socketpair */
+ return -1;
+ }
+
+ if (pollfd[1].revents & POLLIN) {
+ /* a connection is available on server_socket */
+
+ int client_fd =
+ accept(accept_thread_data->server_socket->fd_socket,
+ NULL, NULL);
+ if (client_fd >= 0)
+ return client_fd;
+
+ /*
+ * An error here is unlikely -- it probably
+ * indicates that the connecting process has
+ * already dropped the connection.
+ */
+ continue;
+ }
+
+ BUG("unandled poll result errno=%d r[0]=%d r[1]=%d",
+ errno, pollfd[0].revents, pollfd[1].revents);
+ }
+}
+
+/*
+ * Thread proc for the IPC server "accept thread". This waits for
+ * an incoming socket connection, appends it to the queue of available
+ * connections, and notifies a worker thread to process it.
+ *
+ * Block SIGPIPE in this thread for the life of the thread. This
+ * avoids any stray SIGPIPE signals when closing pipe fds under
+ * extremely heavy loads (such as when the fifo queue is full and we
+ * drop incomming connections).
+ */
+static void *accept_thread_proc(void *_accept_thread_data)
+{
+ struct ipc_accept_thread_data *accept_thread_data = _accept_thread_data;
+ struct ipc_server_data *server_data = accept_thread_data->server_data;
+ sigset_t old_set;
+
+ trace2_thread_start("ipc-accept");
+
+ thread_block_sigpipe(&old_set);
+
+ for (;;) {
+ int client_fd = accept_thread__wait_for_connection(
+ accept_thread_data);
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+ if (server_data->shutdown_requested) {
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+ if (client_fd >= 0)
+ close(client_fd);
+ break;
+ }
+
+ if (client_fd < 0) {
+ /* ignore transient accept() errors */
+ }
+ else {
+ fifo_enqueue(server_data, client_fd);
+ pthread_cond_broadcast(&server_data->work_available_cond);
+ }
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+ }
+
+ trace2_thread_exit();
+ return NULL;
+}
+
+/*
+ * We can't predict the connection arrival rate relative to the worker
+ * processing rate, therefore we allow the "accept-thread" to queue up
+ * a generous number of connections, since we'd rather have the client
+ * not unnecessarily timeout if we can avoid it. (The assumption is
+ * that this will be used for FSMonitor and a few second wait on a
+ * connection is better than having the client timeout and do the full
+ * computation itself.)
+ *
+ * The FIFO queue size is set to a multiple of the worker pool size.
+ * This value chosen at random.
+ */
+#define FIFO_SCALE (100)
+
+/*
+ * The backlog value for `listen(2)`. This doesn't need to huge,
+ * rather just large enough for our "accept-thread" to wake up and
+ * queue incoming connections onto the FIFO without the kernel
+ * dropping any.
+ *
+ * This value chosen at random.
+ */
+#define LISTEN_BACKLOG (50)
+
+static int create_listener_socket(
+ const char *path,
+ const struct ipc_server_opts *ipc_opts,
+ struct unix_ss_socket **new_server_socket)
+{
+ struct unix_ss_socket *server_socket = NULL;
+ struct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;
+ int ret;
+
+ uslg_opts.listen_backlog_size = LISTEN_BACKLOG;
+ uslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;
+
+ ret = unix_ss_create(path, &uslg_opts, -1, &server_socket);
+ if (ret)
+ return ret;
+
+ if (set_socket_blocking_flag(server_socket->fd_socket, 1)) {
+ int saved_errno = errno;
+ unix_ss_free(server_socket);
+ errno = saved_errno;
+ return -1;
+ }
+
+ *new_server_socket = server_socket;
+
+ trace2_data_string("ipc-server", NULL, "listen-with-lock", path);
+ return 0;
+}
+
+static int setup_listener_socket(
+ const char *path,
+ const struct ipc_server_opts *ipc_opts,
+ struct unix_ss_socket **new_server_socket)
+{
+ int ret, saved_errno;
+
+ trace2_region_enter("ipc-server", "create-listener_socket", NULL);
+
+ ret = create_listener_socket(path, ipc_opts, new_server_socket);
+
+ saved_errno = errno;
+ trace2_region_leave("ipc-server", "create-listener_socket", NULL);
+ errno = saved_errno;
+
+ return ret;
+}
+
+/*
+ * Start IPC server in a pool of background threads.
+ */
+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 unix_ss_socket *server_socket = NULL;
+ struct ipc_server_data *server_data;
+ int sv[2];
+ int k;
+ int ret;
+ int nr_threads = opts->nr_threads;
+
+ *returned_server_data = NULL;
+
+ /*
+ * Create a socketpair and set sv[1] to non-blocking. This
+ * will used to send a shutdown message to the accept-thread
+ * and allows the accept-thread to wait on EITHER a client
+ * connection or a shutdown request without spinning.
+ */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)
+ return -1;
+
+ if (set_socket_blocking_flag(sv[1], 1)) {
+ int saved_errno = errno;
+ close(sv[0]);
+ close(sv[1]);
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = setup_listener_socket(path, opts, &server_socket);
+ if (ret) {
+ int saved_errno = errno;
+ close(sv[0]);
+ close(sv[1]);
+ errno = saved_errno;
+ return ret;
+ }
+
+ 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;
+ strbuf_init(&server_data->buf_path, 0);
+ strbuf_addstr(&server_data->buf_path, path);
+
+ if (nr_threads < 1)
+ nr_threads = 1;
+
+ pthread_mutex_init(&server_data->work_available_mutex, NULL);
+ pthread_cond_init(&server_data->work_available_cond, NULL);
+
+ server_data->queue_size = nr_threads * FIFO_SCALE;
+ CALLOC_ARRAY(server_data->fifo_fds, server_data->queue_size);
+
+ server_data->accept_thread =
+ xcalloc(1, sizeof(*server_data->accept_thread));
+ server_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;
+ server_data->accept_thread->server_data = server_data;
+ server_data->accept_thread->server_socket = server_socket;
+ server_data->accept_thread->fd_send_shutdown = sv[0];
+ server_data->accept_thread->fd_wait_shutdown = sv[1];
+
+ if (pthread_create(&server_data->accept_thread->pthread_id, NULL,
+ accept_thread_proc, server_data->accept_thread))
+ die_errno(_("could not start accept_thread '%s'"), path);
+
+ for (k = 0; k < nr_threads; k++) {
+ struct ipc_worker_thread_data *wtd;
+
+ wtd = xcalloc(1, sizeof(*wtd));
+ wtd->magic = MAGIC_WORKER_THREAD_DATA;
+ wtd->server_data = server_data;
+
+ if (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc,
+ wtd)) {
+ if (k == 0)
+ die(_("could not start worker[0] for '%s'"),
+ path);
+ /*
+ * Limp along with the thread pool that we have.
+ */
+ break;
+ }
+
+ wtd->next_thread = server_data->worker_thread_list;
+ server_data->worker_thread_list = wtd;
+ }
+
+ *returned_server_data = server_data;
+ return 0;
+}
+
+/*
+ * Gently tell the IPC server treads to shutdown.
+ * Can be run on any thread.
+ */
+int ipc_server_stop_async(struct ipc_server_data *server_data)
+{
+ /* ASSERT NOT holding mutex */
+
+ int fd;
+
+ if (!server_data)
+ return 0;
+
+ trace2_region_enter("ipc-server", "server-stop-async", NULL);
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+
+ server_data->shutdown_requested = 1;
+
+ /*
+ * Write a byte to the shutdown socket pair to wake up the
+ * accept-thread.
+ */
+ if (write(server_data->accept_thread->fd_send_shutdown, "Q", 1) < 0)
+ error_errno("could not write to fd_send_shutdown");
+
+ /*
+ * Drain the queue of existing connections.
+ */
+ while ((fd = fifo_dequeue(server_data)) != -1)
+ close(fd);
+
+ /*
+ * Gently tell worker threads to stop processing new connections
+ * and exit. (This does not abort in-process conversations.)
+ */
+ pthread_cond_broadcast(&server_data->work_available_cond);
+
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+
+ trace2_region_leave("ipc-server", "server-stop-async", NULL);
+
+ return 0;
+}
+
+/*
+ * Wait for all IPC server threads to stop.
+ */
+int ipc_server_await(struct ipc_server_data *server_data)
+{
+ pthread_join(server_data->accept_thread->pthread_id, NULL);
+
+ if (!server_data->shutdown_requested)
+ BUG("ipc-server: accept-thread stopped for '%s'",
+ server_data->buf_path.buf);
+
+ while (server_data->worker_thread_list) {
+ struct ipc_worker_thread_data *wtd =
+ server_data->worker_thread_list;
+
+ pthread_join(wtd->pthread_id, NULL);
+
+ server_data->worker_thread_list = wtd->next_thread;
+ free(wtd);
+ }
+
+ server_data->is_stopped = 1;
+
+ return 0;
+}
+
+void ipc_server_free(struct ipc_server_data *server_data)
+{
+ struct ipc_accept_thread_data * accept_thread_data;
+
+ if (!server_data)
+ return;
+
+ if (!server_data->is_stopped)
+ BUG("cannot free ipc-server while running for '%s'",
+ server_data->buf_path.buf);
+
+ accept_thread_data = server_data->accept_thread;
+ if (accept_thread_data) {
+ unix_ss_free(accept_thread_data->server_socket);
+
+ if (accept_thread_data->fd_send_shutdown != -1)
+ close(accept_thread_data->fd_send_shutdown);
+ if (accept_thread_data->fd_wait_shutdown != -1)
+ close(accept_thread_data->fd_wait_shutdown);
+
+ free(server_data->accept_thread);
+ }
+
+ while (server_data->worker_thread_list) {
+ struct ipc_worker_thread_data *wtd =
+ server_data->worker_thread_list;
+
+ server_data->worker_thread_list = wtd->next_thread;
+ free(wtd);
+ }
+
+ pthread_cond_destroy(&server_data->work_available_cond);
+ pthread_mutex_destroy(&server_data->work_available_mutex);
+
+ strbuf_release(&server_data->buf_path);
+
+ free(server_data->fifo_fds);
+ free(server_data);
+}
diff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c
new file mode 100644
index 0000000..8bfe512
--- /dev/null
+++ b/compat/simple-ipc/ipc-win32.c
@@ -0,0 +1,904 @@
+#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);
+}
diff --git a/compat/stub/procinfo.c b/compat/stub/procinfo.c
new file mode 100644
index 0000000..12c0a23
--- /dev/null
+++ b/compat/stub/procinfo.c
@@ -0,0 +1,11 @@
+#include "git-compat-util.h"
+
+#include "trace2.h"
+
+/*
+ * Stub. See sample implementations in compat/linux/procinfo.c and
+ * compat/win32/trace2_win32_process_info.c.
+ */
+void trace2_collect_process_info(enum trace2_process_info_reason reason)
+{
+}
diff --git a/compat/terminal.c b/compat/terminal.c
index 43b73dd..0afda73 100644
--- a/compat/terminal.c
+++ b/compat/terminal.c
@@ -1,5 +1,6 @@
#include "git-compat-util.h"
#include "compat/terminal.h"
+#include "gettext.h"
#include "sigchain.h"
#include "strbuf.h"
#include "run-command.h"
@@ -8,12 +9,10 @@
#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
-static void restore_term(void);
-
static void restore_term_on_signal(int sig)
{
restore_term();
- sigchain_pop(sig);
+ /* restore_term calls sigchain_pop_common */
raise(sig);
}
@@ -22,48 +21,227 @@ static void restore_term_on_signal(int sig)
#define INPUT_PATH "/dev/tty"
#define OUTPUT_PATH "/dev/tty"
+static volatile sig_atomic_t term_fd_needs_closing;
static int term_fd = -1;
static struct termios old_term;
-static void restore_term(void)
+static const char *background_resume_msg;
+static const char *restore_error_msg;
+static volatile sig_atomic_t ttou_received;
+
+/* async safe error function for use by signal handlers. */
+static void write_err(const char *msg)
+{
+ write_in_full(2, "error: ", strlen("error: "));
+ write_in_full(2, msg, strlen(msg));
+ write_in_full(2, "\n", 1);
+}
+
+static void print_background_resume_msg(int signo)
+{
+ int saved_errno = errno;
+ sigset_t mask;
+ struct sigaction old_sa;
+ struct sigaction sa = { .sa_handler = SIG_DFL };
+
+ ttou_received = 1;
+ write_err(background_resume_msg);
+ sigaction(signo, &sa, &old_sa);
+ raise(signo);
+ sigemptyset(&mask);
+ sigaddset(&mask, signo);
+ sigprocmask(SIG_UNBLOCK, &mask, NULL);
+ /* Stopped here */
+ sigprocmask(SIG_BLOCK, &mask, NULL);
+ sigaction(signo, &old_sa, NULL);
+ errno = saved_errno;
+}
+
+static void restore_terminal_on_suspend(int signo)
+{
+ int saved_errno = errno;
+ int res;
+ struct termios t;
+ sigset_t mask;
+ struct sigaction old_sa;
+ struct sigaction sa = { .sa_handler = SIG_DFL };
+ int can_restore = 1;
+
+ if (tcgetattr(term_fd, &t) < 0)
+ can_restore = 0;
+
+ if (tcsetattr(term_fd, TCSAFLUSH, &old_term) < 0)
+ write_err(restore_error_msg);
+
+ sigaction(signo, &sa, &old_sa);
+ raise(signo);
+ sigemptyset(&mask);
+ sigaddset(&mask, signo);
+ sigprocmask(SIG_UNBLOCK, &mask, NULL);
+ /* Stopped here */
+ sigprocmask(SIG_BLOCK, &mask, NULL);
+ sigaction(signo, &old_sa, NULL);
+ if (!can_restore) {
+ write_err(restore_error_msg);
+ goto out;
+ }
+ /*
+ * If we resume in the background then we receive SIGTTOU when calling
+ * tcsetattr() below. Set up a handler to print an error message in that
+ * case.
+ */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGTTOU);
+ sa.sa_mask = old_sa.sa_mask;
+ sa.sa_handler = print_background_resume_msg;
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGTTOU, &sa, &old_sa);
+ again:
+ ttou_received = 0;
+ sigprocmask(SIG_UNBLOCK, &mask, NULL);
+ res = tcsetattr(term_fd, TCSAFLUSH, &t);
+ sigprocmask(SIG_BLOCK, &mask, NULL);
+ if (ttou_received)
+ goto again;
+ else if (res < 0)
+ write_err(restore_error_msg);
+ sigaction(SIGTTOU, &old_sa, NULL);
+ out:
+ errno = saved_errno;
+}
+
+static void reset_job_signals(void)
+{
+ if (restore_error_msg) {
+ signal(SIGTTIN, SIG_DFL);
+ signal(SIGTTOU, SIG_DFL);
+ signal(SIGTSTP, SIG_DFL);
+ restore_error_msg = NULL;
+ background_resume_msg = NULL;
+ }
+}
+
+static void close_term_fd(void)
+{
+ if (term_fd_needs_closing)
+ close(term_fd);
+ term_fd_needs_closing = 0;
+ term_fd = -1;
+}
+
+void restore_term(void)
{
if (term_fd < 0)
return;
tcsetattr(term_fd, TCSAFLUSH, &old_term);
- close(term_fd);
- term_fd = -1;
+ close_term_fd();
+ sigchain_pop_common();
+ reset_job_signals();
}
-static int disable_bits(tcflag_t bits)
+int save_term(enum save_term_flags flags)
+{
+ struct sigaction sa;
+
+ if (term_fd < 0)
+ term_fd = ((flags & SAVE_TERM_STDIN)
+ ? 0
+ : open("/dev/tty", O_RDWR));
+ if (term_fd < 0)
+ return -1;
+ term_fd_needs_closing = !(flags & SAVE_TERM_STDIN);
+ if (tcgetattr(term_fd, &old_term) < 0) {
+ close_term_fd();
+ return -1;
+ }
+ sigchain_push_common(restore_term_on_signal);
+ /*
+ * If job control is disabled then the shell will have set the
+ * disposition of SIGTSTP to SIG_IGN.
+ */
+ sigaction(SIGTSTP, NULL, &sa);
+ if (sa.sa_handler == SIG_IGN)
+ return 0;
+
+ /* avoid calling gettext() from signal handler */
+ background_resume_msg = _("cannot resume in the background, please use 'fg' to resume");
+ restore_error_msg = _("cannot restore terminal settings");
+ sa.sa_handler = restore_terminal_on_suspend;
+ sa.sa_flags = SA_RESTART;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGTSTP);
+ sigaddset(&sa.sa_mask, SIGTTIN);
+ sigaddset(&sa.sa_mask, SIGTTOU);
+ sigaction(SIGTSTP, &sa, NULL);
+ sigaction(SIGTTIN, &sa, NULL);
+ sigaction(SIGTTOU, &sa, NULL);
+
+ return 0;
+}
+
+static int disable_bits(enum save_term_flags flags, tcflag_t bits)
{
struct termios t;
- term_fd = open("/dev/tty", O_RDWR);
- if (tcgetattr(term_fd, &t) < 0)
- goto error;
+ if (save_term(flags) < 0)
+ return -1;
- old_term = t;
- sigchain_push_common(restore_term_on_signal);
+ t = old_term;
t.c_lflag &= ~bits;
+ if (bits & ICANON) {
+ t.c_cc[VMIN] = 1;
+ t.c_cc[VTIME] = 0;
+ }
if (!tcsetattr(term_fd, TCSAFLUSH, &t))
return 0;
-error:
- close(term_fd);
- term_fd = -1;
+ sigchain_pop_common();
+ reset_job_signals();
+ close_term_fd();
return -1;
}
-static int disable_echo(void)
+static int disable_echo(enum save_term_flags flags)
{
- return disable_bits(ECHO);
+ return disable_bits(flags, ECHO);
}
-static int enable_non_canonical(void)
+static int enable_non_canonical(enum save_term_flags flags)
{
- return disable_bits(ICANON | ECHO);
+ return disable_bits(flags, ICANON | ECHO);
+}
+
+/*
+ * On macos it is not possible to use poll() with a terminal so use select
+ * instead.
+ */
+static int getchar_with_timeout(int timeout)
+{
+ struct timeval tv, *tvp = NULL;
+ fd_set readfds;
+ int res;
+
+ again:
+ if (timeout >= 0) {
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ tvp = &tv;
+ }
+
+ FD_ZERO(&readfds);
+ FD_SET(0, &readfds);
+ res = select(1, &readfds, NULL, NULL, tvp);
+ if (!res)
+ return EOF;
+ if (res < 0) {
+ if (errno == EINTR)
+ goto again;
+ else
+ return EOF;
+ }
+ return getchar();
}
#elif defined(GIT_WINDOWS_NATIVE)
@@ -75,9 +253,10 @@ static int enable_non_canonical(void)
static int use_stty = 1;
static struct string_list stty_restore = STRING_LIST_INIT_DUP;
static HANDLE hconin = INVALID_HANDLE_VALUE;
-static DWORD cmode;
+static HANDLE hconout = INVALID_HANDLE_VALUE;
+static DWORD cmode_in, cmode_out;
-static void restore_term(void)
+void restore_term(void)
{
if (use_stty) {
int i;
@@ -94,15 +273,51 @@ static void restore_term(void)
return;
}
+ sigchain_pop_common();
+
if (hconin == INVALID_HANDLE_VALUE)
return;
- SetConsoleMode(hconin, cmode);
+ SetConsoleMode(hconin, cmode_in);
+ CloseHandle(hconin);
+ if (cmode_out) {
+ assert(hconout != INVALID_HANDLE_VALUE);
+ SetConsoleMode(hconout, cmode_out);
+ CloseHandle(hconout);
+ }
+
+ hconin = hconout = INVALID_HANDLE_VALUE;
+}
+
+int save_term(enum save_term_flags flags)
+{
+ hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hconin == INVALID_HANDLE_VALUE)
+ return -1;
+
+ if (flags & SAVE_TERM_DUPLEX) {
+ hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hconout == INVALID_HANDLE_VALUE)
+ goto error;
+
+ GetConsoleMode(hconout, &cmode_out);
+ }
+
+ GetConsoleMode(hconin, &cmode_in);
+ use_stty = 0;
+ sigchain_push_common(restore_term_on_signal);
+ return 0;
+error:
CloseHandle(hconin);
hconin = INVALID_HANDLE_VALUE;
+ return -1;
}
-static int disable_bits(DWORD bits)
+static int disable_bits(enum save_term_flags flags, DWORD bits)
{
if (use_stty) {
struct child_process cp = CHILD_PROCESS_INIT;
@@ -111,7 +326,11 @@ static int disable_bits(DWORD bits)
if (bits & ENABLE_LINE_INPUT) {
string_list_append(&stty_restore, "icanon");
- strvec_push(&cp.args, "-icanon");
+ /*
+ * POSIX allows VMIN and VTIME to overlap with VEOF and
+ * VEOL - let's hope that is not the case on windows.
+ */
+ strvec_pushl(&cp.args, "-icanon", "min", "1", "time", "0", NULL);
}
if (bits & ENABLE_ECHO_INPUT) {
@@ -135,31 +354,28 @@ static int disable_bits(DWORD bits)
use_stty = 0;
}
- hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ, NULL, OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL, NULL);
- if (hconin == INVALID_HANDLE_VALUE)
+ if (save_term(flags) < 0)
return -1;
- GetConsoleMode(hconin, &cmode);
- sigchain_push_common(restore_term_on_signal);
- if (!SetConsoleMode(hconin, cmode & ~bits)) {
+ if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
CloseHandle(hconin);
hconin = INVALID_HANDLE_VALUE;
+ sigchain_pop_common();
return -1;
}
return 0;
}
-static int disable_echo(void)
+static int disable_echo(enum save_term_flags flags)
{
- return disable_bits(ENABLE_ECHO_INPUT);
+ return disable_bits(flags, ENABLE_ECHO_INPUT);
}
-static int enable_non_canonical(void)
+static int enable_non_canonical(enum save_term_flags flags)
{
- return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
+ return disable_bits(flags,
+ ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
}
/*
@@ -193,6 +409,16 @@ static int mingw_getchar(void)
}
#define getchar mingw_getchar
+static int getchar_with_timeout(int timeout)
+{
+ struct pollfd pfd = { .fd = 0, .events = POLLIN };
+
+ if (poll(&pfd, 1, timeout) < 1)
+ return EOF;
+
+ return getchar();
+}
+
#endif
#ifndef FORCE_TEXT
@@ -215,7 +441,7 @@ char *git_terminal_prompt(const char *prompt, int echo)
return NULL;
}
- if (!echo && disable_echo()) {
+ if (!echo && disable_echo(0)) {
fclose(input_fh);
fclose(output_fh);
return NULL;
@@ -252,11 +478,14 @@ struct escape_sequence_entry {
char sequence[FLEX_ARRAY];
};
-static int sequence_entry_cmp(const void *hashmap_cmp_fn_data,
- const struct escape_sequence_entry *e1,
- const struct escape_sequence_entry *e2,
+static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED,
+ const struct hashmap_entry *he1,
+ const struct hashmap_entry *he2,
const void *keydata)
{
+ const struct escape_sequence_entry
+ *e1 = container_of(he1, const struct escape_sequence_entry, entry),
+ *e2 = container_of(he2, const struct escape_sequence_entry, entry);
return strcmp(e1->sequence, keydata ? keydata : e2->sequence);
}
@@ -270,8 +499,7 @@ static int is_known_escape_sequence(const char *sequence)
struct strbuf buf = STRBUF_INIT;
char *p, *eol;
- hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp,
- NULL, 0);
+ hashmap_init(&sequences, sequence_entry_cmp, NULL, 0);
strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL);
if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0))
@@ -309,7 +537,7 @@ int read_key_without_echo(struct strbuf *buf)
static int warning_displayed;
int ch;
- if (warning_displayed || enable_non_canonical() < 0) {
+ if (warning_displayed || enable_non_canonical(SAVE_TERM_STDIN) < 0) {
if (!warning_displayed) {
warning("reading single keystrokes not supported on "
"this platform; reading line instead");
@@ -343,14 +571,9 @@ int read_key_without_echo(struct strbuf *buf)
* half a second when we know that the sequence is complete.
*/
while (!is_known_escape_sequence(buf->buf)) {
- struct pollfd pfd = { .fd = 0, .events = POLLIN };
-
- if (poll(&pfd, 1, 500) < 1)
- break;
-
- ch = getchar();
+ ch = getchar_with_timeout(500);
if (ch == EOF)
- return 0;
+ break;
strbuf_addch(buf, ch);
}
}
@@ -361,6 +584,16 @@ int read_key_without_echo(struct strbuf *buf)
#else
+int save_term(enum save_term_flags flags)
+{
+ /* no duplex support available */
+ return -!!(flags & SAVE_TERM_DUPLEX);
+}
+
+void restore_term(void)
+{
+}
+
char *git_terminal_prompt(const char *prompt, int echo)
{
return getpass(prompt);
diff --git a/compat/terminal.h b/compat/terminal.h
index a9d52b8..79ed00c 100644
--- a/compat/terminal.h
+++ b/compat/terminal.h
@@ -1,6 +1,24 @@
#ifndef COMPAT_TERMINAL_H
#define COMPAT_TERMINAL_H
+enum save_term_flags {
+ /* Save input and output settings */
+ SAVE_TERM_DUPLEX = 1 << 0,
+ /* Save stdin rather than /dev/tty (fails if stdin is not a terminal) */
+ SAVE_TERM_STDIN = 1 << 1,
+};
+
+/*
+ * Save the terminal attributes so they can be restored later by a
+ * call to restore_term(). Note that every successful call to
+ * save_term() must be matched by a call to restore_term() even if the
+ * attributes have not been changed. Returns 0 on success, -1 on
+ * failure.
+ */
+int save_term(enum save_term_flags flags);
+/* Restore the terminal attributes that were saved with save_term() */
+void restore_term(void);
+
char *git_terminal_prompt(const char *prompt, int echo);
/* Read a single keystroke, without echoing it to the terminal */
diff --git a/compat/unsetenv.c b/compat/unsetenv.c
index bf5fd70..b9d34af 100644
--- a/compat/unsetenv.c
+++ b/compat/unsetenv.c
@@ -1,6 +1,6 @@
#include "../git-compat-util.h"
-void gitunsetenv (const char *name)
+int gitunsetenv(const char *name)
{
#if !defined(__MINGW32__)
extern char **environ;
@@ -24,4 +24,6 @@ void gitunsetenv (const char *name)
++dst;
}
environ[dst] = NULL;
+
+ return 0;
}
diff --git a/compat/vcbuild/README b/compat/vcbuild/README
index 51fb083..29ec1d0 100644
--- a/compat/vcbuild/README
+++ b/compat/vcbuild/README
@@ -92,7 +92,7 @@ The Steps of Build Git with VS2008
the git operations.
3. Inside Git's directory run the command:
- make command-list.h config-list.h
+ make generated-hdrs
to generate the header file needed to compile git.
4. Then either build Git with the GNU Make Makefile in the Git projects
diff --git a/compat/win32/flush.c b/compat/win32/flush.c
new file mode 100644
index 0000000..291f90e
--- /dev/null
+++ b/compat/win32/flush.c
@@ -0,0 +1,28 @@
+#include "git-compat-util.h"
+#include <winternl.h>
+#include "lazyload.h"
+
+int win32_fsync_no_flush(int fd)
+{
+ IO_STATUS_BLOCK io_status;
+
+#define FLUSH_FLAGS_FILE_DATA_ONLY 1
+
+ DECLARE_PROC_ADDR(ntdll.dll, NTSTATUS, NTAPI, NtFlushBuffersFileEx,
+ HANDLE FileHandle, ULONG Flags, PVOID Parameters, ULONG ParameterSize,
+ PIO_STATUS_BLOCK IoStatusBlock);
+
+ if (!INIT_PROC_ADDR(NtFlushBuffersFileEx)) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ memset(&io_status, 0, sizeof(io_status));
+ if (NtFlushBuffersFileEx((HANDLE)_get_osfhandle(fd), FLUSH_FLAGS_FILE_DATA_ONLY,
+ NULL, 0, &io_status)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/compat/win32/headless.c b/compat/win32/headless.c
new file mode 100644
index 0000000..8b00dfe
--- /dev/null
+++ b/compat/win32/headless.c
@@ -0,0 +1,115 @@
+/*
+ * headless Git - run Git without opening a console window on Windows
+ */
+
+#define STRICT
+#define WIN32_LEAN_AND_MEAN
+#define UNICODE
+#define _UNICODE
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+/*
+ * If `dir` contains the path to a Git exec directory, extend `PATH` to
+ * include the corresponding `bin/` directory (which is where all those
+ * `.dll` files needed by `git.exe` are, on Windows).
+ */
+static int extend_path(wchar_t *dir, size_t dir_len)
+{
+ const wchar_t *suffix = L"\\libexec\\git-core";
+ size_t suffix_len = wcslen(suffix);
+ wchar_t *env;
+ DWORD len;
+
+ if (dir_len < suffix_len)
+ return 0;
+
+ dir_len -= suffix_len;
+ if (memcmp(dir + dir_len, suffix, suffix_len * sizeof(wchar_t)))
+ return 0;
+
+ len = GetEnvironmentVariableW(L"PATH", NULL, 0);
+ if (!len)
+ return 0;
+
+ env = _alloca((dir_len + 5 + len) * sizeof(wchar_t));
+ wcsncpy(env, dir, dir_len);
+ wcscpy(env + dir_len, L"\\bin;");
+ if (!GetEnvironmentVariableW(L"PATH", env + dir_len + 5, len))
+ return 0;
+
+ SetEnvironmentVariableW(L"PATH", env);
+ return 1;
+}
+
+int WINAPI wWinMain(_In_ HINSTANCE instance,
+ _In_opt_ HINSTANCE previous_instance,
+ _In_ LPWSTR command_line, _In_ int show)
+{
+ wchar_t git_command_line[32768];
+ size_t size = sizeof(git_command_line) / sizeof(wchar_t);
+ const wchar_t *needs_quotes = L"";
+ int slash = 0, i;
+
+ STARTUPINFO startup_info = {
+ .cb = sizeof(STARTUPINFO),
+ .dwFlags = STARTF_USESHOWWINDOW,
+ .wShowWindow = SW_HIDE,
+ };
+ PROCESS_INFORMATION process_info = { 0 };
+ DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT |
+ CREATE_NEW_CONSOLE | CREATE_NO_WINDOW;
+ DWORD exit_code;
+
+ /* First, determine the full path of argv[0] */
+ for (i = 0; _wpgmptr[i]; i++)
+ if (_wpgmptr[i] == L' ')
+ needs_quotes = L"\"";
+ else if (_wpgmptr[i] == L'\\')
+ slash = i;
+
+ if (slash >= size - 11)
+ return 127; /* Too long path */
+
+ /* If it is in Git's exec path, add the bin/ directory to the PATH */
+ extend_path(_wpgmptr, slash);
+
+ /* Then, add the full path of `git.exe` as argv[0] */
+ i = swprintf_s(git_command_line, size, L"%ls%.*ls\\git.exe%ls",
+ needs_quotes, slash, _wpgmptr, needs_quotes);
+ if (i < 0)
+ return 127; /* Too long path */
+
+ if (*command_line) {
+ /* Now, append the command-line arguments */
+ i = swprintf_s(git_command_line + i, size - i,
+ L" %ls", command_line);
+ if (i < 0)
+ return 127;
+ }
+
+ startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+ startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+
+ if (!CreateProcess(NULL, /* infer argv[0] from the command line */
+ git_command_line, /* modified command line */
+ NULL, /* inherit process handles? */
+ NULL, /* inherit thread handles? */
+ FALSE, /* handles inheritable? */
+ creation_flags,
+ NULL, /* use this process' environment */
+ NULL, /* use this process' working directory */
+ &startup_info, &process_info))
+ return 129; /* could not start */
+ WaitForSingleObject(process_info.hProcess, INFINITE);
+ if (!GetExitCodeProcess(process_info.hProcess, &exit_code))
+ exit_code = 130; /* Could not determine exit code? */
+
+ CloseHandle(process_info.hProcess);
+ CloseHandle(process_info.hThread);
+
+ return (int)exit_code;
+}
diff --git a/compat/win32/lazyload.h b/compat/win32/lazyload.h
index 9e631c8..f2bb96c 100644
--- a/compat/win32/lazyload.h
+++ b/compat/win32/lazyload.h
@@ -4,7 +4,7 @@
/*
* A pair of macros to simplify loading of DLL functions. Example:
*
- * DECLARE_PROC_ADDR(kernel32.dll, BOOL, CreateHardLinkW,
+ * DECLARE_PROC_ADDR(kernel32.dll, BOOL, WINAPI, CreateHardLinkW,
* LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
*
* if (!INIT_PROC_ADDR(CreateHardLinkW))
@@ -15,18 +15,21 @@
* source, target);
*/
+typedef void (*FARVOIDPROC)(void);
+
struct proc_addr {
const char *const dll;
const char *const function;
- FARPROC pfunction;
+ FARVOIDPROC pfunction;
unsigned initialized : 1;
};
/* Declares a function to be loaded dynamically from a DLL. */
-#define DECLARE_PROC_ADDR(dll, rettype, function, ...) \
+#define DECLARE_PROC_ADDR(dll, rettype, convention, function, ...) \
static struct proc_addr proc_addr_##function = \
{ #dll, #function, NULL, 0 }; \
- static rettype (WINAPI *function)(__VA_ARGS__)
+ typedef rettype (convention *proc_type_##function)(__VA_ARGS__); \
+ static proc_type_##function function
/*
* Loads a function from a DLL (once-only).
@@ -35,9 +38,9 @@ struct proc_addr {
* This function is not thread-safe.
*/
#define INIT_PROC_ADDR(function) \
- (function = get_proc_addr(&proc_addr_##function))
+ (function = (proc_type_##function)get_proc_addr(&proc_addr_##function))
-static inline void *get_proc_addr(struct proc_addr *proc)
+static inline FARVOIDPROC get_proc_addr(struct proc_addr *proc)
{
/* only do this once */
if (!proc->initialized) {
@@ -46,7 +49,8 @@ static inline void *get_proc_addr(struct proc_addr *proc)
hnd = LoadLibraryExA(proc->dll, NULL,
LOAD_LIBRARY_SEARCH_SYSTEM32);
if (hnd)
- proc->pfunction = GetProcAddress(hnd, proc->function);
+ proc->pfunction = (FARVOIDPROC)GetProcAddress(hnd,
+ proc->function);
}
/* set ENOSYS if DLL or function was not found */
if (!proc->pfunction)
diff --git a/compat/win32/path-utils.h b/compat/win32/path-utils.h
index bba2b64..65fa3b9 100644
--- a/compat/win32/path-utils.h
+++ b/compat/win32/path-utils.h
@@ -6,11 +6,7 @@ int win32_has_dos_drive_prefix(const char *path);
int win32_skip_dos_drive_prefix(char **path);
#define skip_dos_drive_prefix win32_skip_dos_drive_prefix
-static inline int win32_is_dir_sep(int c)
-{
- return c == '/' || c == '\\';
-}
-#define is_dir_sep win32_is_dir_sep
+#define is_dir_sep is_xplatform_dir_sep
static inline char *win32_find_last_dir_sep(const char *path)
{
char *ret = NULL;
diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c
index 2e7eead..85f8f79 100644
--- a/compat/win32/pthread.c
+++ b/compat/win32/pthread.c
@@ -22,12 +22,12 @@ static unsigned __stdcall win32_start_routine(void *arg)
}
int pthread_create(pthread_t *thread, const void *unused,
- void *(*start_routine)(void*), void *arg)
+ void *(*start_routine)(void *), void *arg)
{
thread->arg = arg;
thread->start_routine = start_routine;
- thread->handle = (HANDLE)
- _beginthreadex(NULL, 0, win32_start_routine, thread, 0, NULL);
+ thread->handle = (HANDLE)_beginthreadex(NULL, 0, win32_start_routine,
+ thread, 0, NULL);
if (!thread->handle)
return errno;
@@ -39,14 +39,17 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr)
{
DWORD result = WaitForSingleObject(thread->handle, INFINITE);
switch (result) {
- case WAIT_OBJECT_0:
- if (value_ptr)
- *value_ptr = thread->arg;
- return 0;
- case WAIT_ABANDONED:
- return EINVAL;
- default:
- return err_win_to_posix(GetLastError());
+ case WAIT_OBJECT_0:
+ if (value_ptr)
+ *value_ptr = thread->arg;
+ CloseHandle(thread->handle);
+ return 0;
+ case WAIT_ABANDONED:
+ CloseHandle(thread->handle);
+ return EINVAL;
+ default:
+ /* the wait failed, so do not detach */
+ return err_win_to_posix(GetLastError());
}
}
diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h
index 737983d..cc3221c 100644
--- a/compat/win32/pthread.h
+++ b/compat/win32/pthread.h
@@ -66,7 +66,7 @@ pthread_t pthread_self(void);
static inline void NORETURN pthread_exit(void *ret)
{
- ExitThread((DWORD)(intptr_t)ret);
+ _endthreadex((unsigned)(uintptr_t)ret);
}
typedef DWORD pthread_key_t;
diff --git a/compat/win32/syslog.c b/compat/win32/syslog.c
index 161978d..0af18d8 100644
--- a/compat/win32/syslog.c
+++ b/compat/win32/syslog.c
@@ -43,6 +43,8 @@ void syslog(int priority, const char *fmt, ...)
va_end(ap);
while ((pos = strstr(str, "%1")) != NULL) {
+ size_t offset = pos - str;
+ char *new_pos;
char *oldstr = str;
str = realloc(str, st_add(++str_len, 1));
if (!str) {
@@ -50,8 +52,9 @@ void syslog(int priority, const char *fmt, ...)
warning_errno("realloc failed");
return;
}
- memmove(pos + 2, pos + 1, strlen(pos));
- pos[1] = ' ';
+ new_pos = str + offset;
+ memmove(new_pos + 2, new_pos + 1, strlen(new_pos));
+ new_pos[1] = ' ';
}
switch (priority) {
diff --git a/compat/win32/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c
index 8ccbd1c..3ef0936 100644
--- a/compat/win32/trace2_win32_process_info.c
+++ b/compat/win32/trace2_win32_process_info.c
@@ -1,8 +1,10 @@
-#include "../../cache.h"
+#include "../../git-compat-util.h"
#include "../../json-writer.h"
+#include "../../repository.h"
+#include "../../trace2.h"
#include "lazyload.h"
-#include <Psapi.h>
-#include <tlHelp32.h>
+#include <psapi.h>
+#include <tlhelp32.h>
/*
* An arbitrarily chosen value to limit the size of the ancestor
@@ -143,8 +145,8 @@ static void get_is_being_debugged(void)
*/
static void get_peak_memory_info(void)
{
- DECLARE_PROC_ADDR(psapi.dll, BOOL, GetProcessMemoryInfo, HANDLE,
- PPROCESS_MEMORY_COUNTERS, DWORD);
+ DECLARE_PROC_ADDR(psapi.dll, BOOL, WINAPI, GetProcessMemoryInfo,
+ HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD);
if (INIT_PROC_ADDR(GetProcessMemoryInfo)) {
PROCESS_MEMORY_COUNTERS pmc;
diff --git a/compat/winansi.c b/compat/winansi.c
index c27b20a..f83610f 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -3,6 +3,7 @@
*/
#undef NOGDI
+
#include "../git-compat-util.h"
#include <wingdi.h>
#include <winreg.h>
@@ -45,8 +46,9 @@ typedef struct _CONSOLE_FONT_INFOEX {
static void warn_if_raster_font(void)
{
DWORD fontFamily = 0;
- DECLARE_PROC_ADDR(kernel32.dll, BOOL, GetCurrentConsoleFontEx,
- HANDLE, BOOL, PCONSOLE_FONT_INFOEX);
+ DECLARE_PROC_ADDR(kernel32.dll, BOOL, WINAPI,
+ GetCurrentConsoleFontEx, HANDLE, BOOL,
+ PCONSOLE_FONT_INFOEX);
/* don't bother if output was ascii only */
if (!non_ascii_used)
@@ -642,7 +644,7 @@ void winansi_init(void)
/* start console spool thread on the pipe's read end */
hthread = CreateThread(NULL, 0, console_thread, NULL, 0, NULL);
- if (hthread == INVALID_HANDLE_VALUE)
+ if (!hthread)
die_lasterr("CreateThread(console_thread) failed");
/* schedule cleanup routine */
diff --git a/compat/zlib-uncompress2.c b/compat/zlib-uncompress2.c
new file mode 100644
index 0000000..77a1b08
--- /dev/null
+++ b/compat/zlib-uncompress2.c
@@ -0,0 +1,96 @@
+#include "git-compat-util.h"
+
+#if ZLIB_VERNUM < 0x1290
+/* taken from zlib's uncompr.c
+
+ commit cacf7f1d4e3d44d871b605da3b647f07d718623f
+ Author: Mark Adler <madler@alumni.caltech.edu>
+ Date: Sun Jan 15 09:18:46 2017 -0800
+
+ zlib 1.2.11
+
+*/
+
+/*
+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* clang-format off */
+
+/* ===========================================================================
+ Decompresses the source buffer into the destination buffer. *sourceLen is
+ the byte length of the source buffer. Upon entry, *destLen is the total size
+ of the destination buffer, which must be large enough to hold the entire
+ uncompressed data. (The size of the uncompressed data must have been saved
+ previously by the compressor and transmitted to the decompressor by some
+ mechanism outside the scope of this compression library.) Upon exit,
+ *destLen is the size of the decompressed data and *sourceLen is the number
+ of source bytes consumed. Upon return, source + *sourceLen points to the
+ first unused input byte.
+
+ uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_BUF_ERROR if there was not enough room in the output buffer, or
+ Z_DATA_ERROR if the input data was corrupted, including if the input data is
+ an incomplete zlib stream.
+*/
+int ZEXPORT uncompress2 (
+ Bytef *dest,
+ uLongf *destLen,
+ const Bytef *source,
+ uLong *sourceLen) {
+ z_stream stream;
+ int err;
+ const uInt max = (uInt)-1;
+ uLong len, left;
+ Byte buf[1]; /* for detection of incomplete stream when *destLen == 0 */
+
+ len = *sourceLen;
+ if (*destLen) {
+ left = *destLen;
+ *destLen = 0;
+ }
+ else {
+ left = 1;
+ dest = buf;
+ }
+
+ stream.next_in = (z_const Bytef *)source;
+ stream.avail_in = 0;
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+ stream.opaque = (voidpf)0;
+
+ err = inflateInit(&stream);
+ if (err != Z_OK) return err;
+
+ stream.next_out = dest;
+ stream.avail_out = 0;
+
+ do {
+ if (stream.avail_out == 0) {
+ stream.avail_out = left > (uLong)max ? max : (uInt)left;
+ left -= stream.avail_out;
+ }
+ if (stream.avail_in == 0) {
+ stream.avail_in = len > (uLong)max ? max : (uInt)len;
+ len -= stream.avail_in;
+ }
+ err = inflate(&stream, Z_NO_FLUSH);
+ } while (err == Z_OK);
+
+ *sourceLen -= len + stream.avail_in;
+ if (dest != buf)
+ *destLen = stream.total_out;
+ else if (stream.total_out && err == Z_BUF_ERROR)
+ left = 1;
+
+ inflateEnd(&stream);
+ return err == Z_STREAM_END ? Z_OK :
+ err == Z_NEED_DICT ? Z_DATA_ERROR :
+ err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
+ err;
+}
+#else
+static void *dummy_variable = &dummy_variable;
+#endif