Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.kernel.org/pub/scm/git/git.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2022-10-18 00:56:31 +0300
committerJunio C Hamano <gitster@pobox.com>2022-10-18 00:56:31 +0300
commit7b8cfe34d9b36a08214ea7e3ae82e9563188de5e (patch)
tree3e93bd39e629cadfecec9d3aa0e8955e40159826
parentd420dda0576340909c3faff364cfbd1485f70376 (diff)
parentc4f94907902f18d80a78ee19983d0d02932154d7 (diff)
Merge branch 'ed/fsmonitor-on-networked-macos'
By default, use of fsmonitor on a repository on networked filesystem is disabled. Add knobs to make it workable on macOS. * ed/fsmonitor-on-networked-macos: fsmonitor: fix leak of warning message fsmonitor: add documentation for allowRemote and socketDir options fsmonitor: check for compatability before communicating with fsmonitor fsmonitor: deal with synthetic firmlinks on macOS fsmonitor: avoid socket location check if using hook fsmonitor: relocate socket file if .git directory is remote fsmonitor: refactor filesystem checks to common interface
-rw-r--r--Documentation/config.txt2
-rw-r--r--Documentation/config/fsmonitor--daemon.txt11
-rw-r--r--Documentation/git-fsmonitor--daemon.txt37
-rw-r--r--Makefile2
-rw-r--r--builtin/fsmonitor--daemon.c11
-rw-r--r--compat/fsmonitor/fsm-ipc-darwin.c52
-rw-r--r--compat/fsmonitor/fsm-ipc-win32.c9
-rw-r--r--compat/fsmonitor/fsm-listen-darwin.c14
-rw-r--r--compat/fsmonitor/fsm-path-utils-darwin.c135
-rw-r--r--compat/fsmonitor/fsm-path-utils-win32.c145
-rw-r--r--compat/fsmonitor/fsm-settings-darwin.c77
-rw-r--r--compat/fsmonitor/fsm-settings-win32.c174
-rw-r--r--contrib/buildsystems/CMakeLists.txt4
-rw-r--r--fsmonitor--daemon.h3
-rw-r--r--fsmonitor-ipc.c18
-rw-r--r--fsmonitor-ipc.h4
-rw-r--r--fsmonitor-path-utils.h60
-rw-r--r--fsmonitor-settings.c68
-rw-r--r--fsmonitor-settings.h4
-rw-r--r--fsmonitor.c9
20 files changed, 590 insertions, 249 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 5b5b976569..1e20583165 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -423,6 +423,8 @@ include::config/filter.txt[]
include::config/fsck.txt[]
+include::config/fsmonitor--daemon.txt[]
+
include::config/gc.txt[]
include::config/gitcvs.txt[]
diff --git a/Documentation/config/fsmonitor--daemon.txt b/Documentation/config/fsmonitor--daemon.txt
new file mode 100644
index 0000000000..c225c6c9e7
--- /dev/null
+++ b/Documentation/config/fsmonitor--daemon.txt
@@ -0,0 +1,11 @@
+fsmonitor.allowRemote::
+ By default, the fsmonitor daemon refuses to work against network-mounted
+ repositories. Setting `fsmonitor.allowRemote` to `true` overrides this
+ behavior. Only respected when `core.fsmonitor` is set to `true`.
+
+fsmonitor.socketDir::
+ This Mac OS-specific option, if set, specifies the directory in
+ which to create the Unix domain socket used for communication
+ between the fsmonitor daemon and various Git commands. The directory must
+ reside on a native Mac OS filesystem. Only respected when `core.fsmonitor`
+ is set to `true`.
diff --git a/Documentation/git-fsmonitor--daemon.txt b/Documentation/git-fsmonitor--daemon.txt
index cc142fb861..8238eadb0e 100644
--- a/Documentation/git-fsmonitor--daemon.txt
+++ b/Documentation/git-fsmonitor--daemon.txt
@@ -3,7 +3,7 @@ git-fsmonitor{litdd}daemon(1)
NAME
----
-git-fsmonitor--daemon - A Built-in File System Monitor
+git-fsmonitor--daemon - A Built-in Filesystem Monitor
SYNOPSIS
--------
@@ -17,7 +17,7 @@ DESCRIPTION
-----------
A daemon to watch the working directory for file and directory
-changes using platform-specific file system notification facilities.
+changes using platform-specific filesystem notification facilities.
This daemon communicates directly with commands like `git status`
using the link:technical/api-simple-ipc.html[simple IPC] interface
@@ -63,13 +63,44 @@ CAVEATS
-------
The fsmonitor daemon does not currently know about submodules and does
-not know to filter out file system events that happen within a
+not know to filter out filesystem events that happen within a
submodule. If fsmonitor daemon is watching a super repo and a file is
modified within the working directory of a submodule, it will report
the change (as happening against the super repo). However, the client
will properly ignore these extra events, so performance may be affected
but it will not cause an incorrect result.
+By default, the fsmonitor daemon refuses to work against network-mounted
+repositories; this may be overridden by setting `fsmonitor.allowRemote` to
+`true`. Note, however, that the fsmonitor daemon is not guaranteed to work
+correctly with all network-mounted repositories and such use is considered
+experimental.
+
+On Mac OS, the inter-process communication (IPC) between various Git
+commands and the fsmonitor daemon is done via a Unix domain socket (UDS) -- a
+special type of file -- which is supported by native Mac OS filesystems,
+but not on network-mounted filesystems, NTFS, or FAT32. Other filesystems
+may or may not have the needed support; the fsmonitor daemon is not guaranteed
+to work with these filesystems and such use is considered experimental.
+
+By default, the socket is created in the `.git` directory, however, if the
+`.git` directory is on a network-mounted filesystem, it will be instead be
+created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a
+network-mounted filesystem in which case you must set the configuration
+variable `fsmonitor.socketDir` to the path of a directory on a Mac OS native
+filesystem in which to create the socket file.
+
+If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`)
+is on a native Mac OS file filesystem the fsmonitor daemon will report an
+error that will cause the daemon and the currently running command to exit.
+
+CONFIGURATION
+-------------
+
+include::includes/cmd-config-section-all.txt[]
+
+include::config/fsmonitor--daemon.txt[]
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 6bfb62cbe9..9553d59082 100644
--- a/Makefile
+++ b/Makefile
@@ -2038,11 +2038,13 @@ ifdef FSMONITOR_DAEMON_BACKEND
COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o
COMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o
+ COMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o
endif
ifdef FSMONITOR_OS_SETTINGS
COMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS
COMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o
+ COMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o
endif
ifeq ($(TCLTK_PATH),)
diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c
index c69da93ece..6f30a4f93a 100644
--- a/builtin/fsmonitor--daemon.c
+++ b/builtin/fsmonitor--daemon.c
@@ -3,6 +3,7 @@
#include "parse-options.h"
#include "fsmonitor.h"
#include "fsmonitor-ipc.h"
+#include "fsmonitor-path-utils.h"
#include "compat/fsmonitor/fsm-health.h"
#include "compat/fsmonitor/fsm-listen.h"
#include "fsmonitor--daemon.h"
@@ -1282,6 +1283,11 @@ static int fsmonitor_run_daemon(void)
strbuf_addstr(&state.path_worktree_watch, absolute_path(get_git_work_tree()));
state.nr_paths_watching = 1;
+ strbuf_init(&state.alias.alias, 0);
+ strbuf_init(&state.alias.points_to, 0);
+ if ((err = fsmonitor__get_alias(state.path_worktree_watch.buf, &state.alias)))
+ goto done;
+
/*
* We create and delete cookie files somewhere inside the .git
* directory to help us keep sync with the file system. If
@@ -1343,7 +1349,8 @@ static int fsmonitor_run_daemon(void)
* directory.)
*/
strbuf_init(&state.path_ipc, 0);
- strbuf_addstr(&state.path_ipc, absolute_path(fsmonitor_ipc__get_path()));
+ strbuf_addstr(&state.path_ipc,
+ absolute_path(fsmonitor_ipc__get_path(the_repository)));
/*
* Confirm that we can create platform-specific resources for the
@@ -1390,6 +1397,8 @@ done:
strbuf_release(&state.path_gitdir_watch);
strbuf_release(&state.path_cookie_prefix);
strbuf_release(&state.path_ipc);
+ strbuf_release(&state.alias.alias);
+ strbuf_release(&state.alias.points_to);
return err;
}
diff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-darwin.c
new file mode 100644
index 0000000000..ce843d6334
--- /dev/null
+++ b/compat/fsmonitor/fsm-ipc-darwin.c
@@ -0,0 +1,52 @@
+#include "cache.h"
+#include "config.h"
+#include "strbuf.h"
+#include "fsmonitor.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;
+ SHA_CTX sha1ctx;
+ char *sock_dir = NULL;
+ struct strbuf ipc_file = STRBUF_INIT;
+ unsigned char hash[SHA_DIGEST_LENGTH];
+
+ 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;
+ }
+
+ SHA1_Init(&sha1ctx);
+ SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));
+ 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 0000000000..e08c505c14
--- /dev/null
+++ b/compat/fsmonitor/fsm-ipc-win32.c
@@ -0,0 +1,9 @@
+#include "config.h"
+#include "fsmonitor-ipc.h"
+
+const char *fsmonitor_ipc__get_path(struct repository *r) {
+ static char *ret;
+ if (!ret)
+ ret = git_pathdup("fsmonitor--daemon.ipc");
+ return ret;
+}
diff --git a/compat/fsmonitor/fsm-listen-darwin.c b/compat/fsmonitor/fsm-listen-darwin.c
index 8e208e8289..daeee4e465 100644
--- a/compat/fsmonitor/fsm-listen-darwin.c
+++ b/compat/fsmonitor/fsm-listen-darwin.c
@@ -26,6 +26,7 @@
#include "fsmonitor.h"
#include "fsm-listen.h"
#include "fsmonitor--daemon.h"
+#include "fsmonitor-path-utils.h"
struct fsm_listen_data
{
@@ -198,8 +199,9 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
struct string_list cookie_list = STRING_LIST_INIT_DUP;
const char *path_k;
const char *slash;
- int k;
+ char *resolved = NULL;
struct strbuf tmp = STRBUF_INIT;
+ int k;
/*
* Build a list of all filesystem changes into a private/local
@@ -209,7 +211,12 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
/*
* On Mac, we receive an array of absolute paths.
*/
- path_k = paths[k];
+ 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.
@@ -238,6 +245,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
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
@@ -360,12 +368,14 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
}
}
+ 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);
diff --git a/compat/fsmonitor/fsm-path-utils-darwin.c b/compat/fsmonitor/fsm-path-utils-darwin.c
new file mode 100644
index 0000000000..ce5a8febe0
--- /dev/null
+++ b/compat/fsmonitor/fsm-path-utils-darwin.c
@@ -0,0 +1,135 @@
+#include "fsmonitor.h"
+#include "fsmonitor-path-utils.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 0000000000..0d95bbb416
--- /dev/null
+++ b/compat/fsmonitor/fsm-path-utils-win32.c
@@ -0,0 +1,145 @@
+#include "cache.h"
+#include "fsmonitor.h"
+#include "fsmonitor-path-utils.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, struct alias_info *info)
+{
+ return 0;
+}
+
+/*
+ * No-op for now.
+ */
+char *fsmonitor__resolve_alias(const char *path,
+ const struct alias_info *info)
+{
+ return NULL;
+}
diff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c
index efc732c0f3..6abbc7af3a 100644
--- a/compat/fsmonitor/fsm-settings-darwin.c
+++ b/compat/fsmonitor/fsm-settings-darwin.c
@@ -1,32 +1,10 @@
-#include "cache.h"
#include "config.h"
-#include "repository.h"
-#include "fsmonitor-settings.h"
#include "fsmonitor.h"
-#include <sys/param.h>
-#include <sys/mount.h>
+#include "fsmonitor-ipc.h"
+#include "fsmonitor-settings.h"
+#include "fsmonitor-path-utils.h"
-/*
- * [1] Remote working directories are problematic for FSMonitor.
- *
- * The underlying file system on the server machine and/or the remote
- * mount type (NFS, SAMBA, etc.) dictates whether notification events
- * are available at all to remote client machines.
- *
- * Kernel differences between the server and client machines also
- * dictate the how (buffering, frequency, de-dup) the events are
- * delivered to client machine processes.
- *
- * A client machine (such as a laptop) may choose to suspend/resume
- * and it is unclear (without lots of testing) whether the watcher can
- * resync after a resume. We might be able to treat this as a normal
- * "events were dropped by the kernel" event and do our normal "flush
- * and resync" --or-- we might need to close the existing (zombie?)
- * notification fd and create a new one.
- *
- * In theory, the above issues need to be addressed whether we are
- * using the Hook or IPC API.
- *
+ /*
* 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
@@ -38,52 +16,47 @@
* be taken to ensure that $HOME is actually local and not a managed
* file share.)
*
- * So (for now at least), mark remote working directories as
- * incompatible.
- *
- *
- * [2] FAT32 and NTFS working directories are problematic too.
+ * 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_volume(struct repository *r)
+static enum fsmonitor_reason check_uds_volume(struct repository *r)
{
- struct statfs fs;
+ 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 (statfs(r->worktree, &fs) == -1) {
- int saved_errno = errno;
- trace_printf_key(&trace_fsmonitor, "statfs('%s') failed: %s",
- r->worktree, strerror(saved_errno));
- errno = saved_errno;
+ if (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {
+ strbuf_release(&path);
return FSMONITOR_REASON_ERROR;
}
- trace_printf_key(&trace_fsmonitor,
- "statfs('%s') [type 0x%08x][flags 0x%08x] '%s'",
- r->worktree, fs.f_type, fs.f_flags, fs.f_fstypename);
+ strbuf_release(&path);
- if (!(fs.f_flags & MNT_LOCAL))
- return FSMONITOR_REASON_REMOTE;
-
- if (!strcmp(fs.f_fstypename, "msdos")) /* aka FAT32 */
- return FSMONITOR_REASON_NOSOCKETS;
-
- if (!strcmp(fs.f_fstypename, "ntfs"))
+ 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)
+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)
{
enum fsmonitor_reason reason;
- reason = check_volume(r);
- if (reason != FSMONITOR_REASON_OK)
- return 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
index e5ec5b0a9f..a8af31b71d 100644
--- a/compat/fsmonitor/fsm-settings-win32.c
+++ b/compat/fsmonitor/fsm-settings-win32.c
@@ -1,8 +1,9 @@
#include "cache.h"
#include "config.h"
#include "repository.h"
-#include "fsmonitor-settings.h"
#include "fsmonitor.h"
+#include "fsmonitor-settings.h"
+#include "fsmonitor-path-utils.h"
/*
* VFS for Git is incompatible with FSMonitor.
@@ -24,172 +25,7 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r)
return FSMONITOR_REASON_OK;
}
-/*
- * Check if monitoring remote working directories is allowed.
- *
- * By default, monitoring remote working directories is
- * disabled. Users may override this behavior in enviroments where
- * they have proper support.
- */
-static int check_config_allowremote(struct repository *r)
-{
- int allow;
-
- if (!repo_config_get_bool(r, "fsmonitor.allowremote", &allow))
- return allow;
-
- return -1; /* fsmonitor.allowremote not set */
-}
-
-/*
- * Check remote working directory protocol.
- *
- * Error 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;
-}
-
-/*
- * Remote working directories are problematic for FSMonitor.
- *
- * The underlying file system on the server machine and/or the remote
- * mount type dictates whether notification events are available at
- * all to remote client machines.
- *
- * Kernel differences between the server and client machines also
- * dictate the how (buffering, frequency, de-dup) the events are
- * delivered to client machine processes.
- *
- * A client machine (such as a laptop) may choose to suspend/resume
- * and it is unclear (without lots of testing) whether the watcher can
- * resync after a resume. We might be able to treat this as a normal
- * "events were dropped by the kernel" event and do our normal "flush
- * and resync" --or-- we might need to close the existing (zombie?)
- * notification fd and create a new one.
- *
- * In theory, the above issues need to be addressed whether we are
- * using the Hook or IPC API.
- *
- * So (for now at least), mark remote working directories as
- * incompatible.
- *
- * 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
- */
-static enum fsmonitor_reason check_remote(struct repository *r)
-{
- int ret;
- 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, r->worktree) < 0)
- return FSMONITOR_REASON_ERROR;
-
- /*
- * 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 FSMONITOR_REASON_ERROR;
-
- driveType = GetDriveTypeW(wfullpath);
- trace_printf_key(&trace_fsmonitor,
- "DriveType '%s' L'%ls' (%u)",
- r->worktree, wfullpath, driveType);
-
- if (driveType == DRIVE_REMOTE) {
- trace_printf_key(&trace_fsmonitor,
- "check_remote('%s') true",
- r->worktree);
-
- ret = check_remote_protocol(wfullpath);
- if (ret < 0)
- return FSMONITOR_REASON_ERROR;
-
- switch (check_config_allowremote(r)) {
- case 0: /* config overrides and disables */
- return FSMONITOR_REASON_REMOTE;
- case 1: /* config overrides and enables */
- return FSMONITOR_REASON_OK;
- default:
- break; /* config has no opinion */
- }
-
- return FSMONITOR_REASON_REMOTE;
- }
-
- return FSMONITOR_REASON_OK;
-}
-
-enum fsmonitor_reason fsm_os__incompatible(struct repository *r)
+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)
{
enum fsmonitor_reason reason;
@@ -197,9 +33,5 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r)
if (reason != FSMONITOR_REASON_OK)
return reason;
- reason = check_remote(r);
- if (reason != FSMONITOR_REASON_OK)
- return reason;
-
return FSMONITOR_REASON_OK;
}
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index ea2a531be8..787738e6fa 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -308,6 +308,8 @@ if(SUPPORTS_SIMPLE_IPC)
add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)
+ list(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-win32.c)
+ list(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-win32.c)
add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c)
@@ -315,6 +317,8 @@ if(SUPPORTS_SIMPLE_IPC)
add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)
+ list(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-darwin.c)
+ list(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c)
add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)
diff --git a/fsmonitor--daemon.h b/fsmonitor--daemon.h
index 2102a5c9ff..e24838f9a8 100644
--- a/fsmonitor--daemon.h
+++ b/fsmonitor--daemon.h
@@ -8,6 +8,7 @@
#include "run-command.h"
#include "simple-ipc.h"
#include "thread-utils.h"
+#include "fsmonitor-path-utils.h"
struct fsmonitor_batch;
struct fsmonitor_token_data;
@@ -43,6 +44,7 @@ struct fsmonitor_daemon_state {
struct strbuf path_worktree_watch;
struct strbuf path_gitdir_watch;
+ struct alias_info alias;
int nr_paths_watching;
struct fsmonitor_token_data *current_token_data;
@@ -59,6 +61,7 @@ struct fsmonitor_daemon_state {
struct ipc_server_data *ipc_server_data;
struct strbuf path_ipc;
+
};
/*
diff --git a/fsmonitor-ipc.c b/fsmonitor-ipc.c
index 789e7397ba..c0f42301c8 100644
--- a/fsmonitor-ipc.c
+++ b/fsmonitor-ipc.c
@@ -18,7 +18,7 @@ int fsmonitor_ipc__is_supported(void)
return 0;
}
-const char *fsmonitor_ipc__get_path(void)
+const char *fsmonitor_ipc__get_path(struct repository *r)
{
return NULL;
}
@@ -47,11 +47,9 @@ int fsmonitor_ipc__is_supported(void)
return 1;
}
-GIT_PATH_FUNC(fsmonitor_ipc__get_path, "fsmonitor--daemon.ipc")
-
enum ipc_active_state fsmonitor_ipc__get_state(void)
{
- return ipc_get_active_state(fsmonitor_ipc__get_path());
+ return ipc_get_active_state(fsmonitor_ipc__get_path(the_repository));
}
static int spawn_daemon(void)
@@ -81,8 +79,8 @@ int fsmonitor_ipc__send_query(const char *since_token,
trace2_data_string("fsm_client", NULL, "query/command", tok);
try_again:
- state = ipc_client_try_connect(fsmonitor_ipc__get_path(), &options,
- &connection);
+ state = ipc_client_try_connect(fsmonitor_ipc__get_path(the_repository),
+ &options, &connection);
switch (state) {
case IPC_STATE__LISTENING:
@@ -117,13 +115,13 @@ try_again:
case IPC_STATE__INVALID_PATH:
ret = error(_("fsmonitor_ipc__send_query: invalid path '%s'"),
- fsmonitor_ipc__get_path());
+ fsmonitor_ipc__get_path(the_repository));
goto done;
case IPC_STATE__OTHER_ERROR:
default:
ret = error(_("fsmonitor_ipc__send_query: unspecified error on '%s'"),
- fsmonitor_ipc__get_path());
+ fsmonitor_ipc__get_path(the_repository));
goto done;
}
@@ -149,8 +147,8 @@ int fsmonitor_ipc__send_command(const char *command,
options.wait_if_busy = 1;
options.wait_if_not_found = 0;
- state = ipc_client_try_connect(fsmonitor_ipc__get_path(), &options,
- &connection);
+ state = ipc_client_try_connect(fsmonitor_ipc__get_path(the_repository),
+ &options, &connection);
if (state != IPC_STATE__LISTENING) {
die(_("fsmonitor--daemon is not running"));
return -1;
diff --git a/fsmonitor-ipc.h b/fsmonitor-ipc.h
index b6a7067c3a..8b489da762 100644
--- a/fsmonitor-ipc.h
+++ b/fsmonitor-ipc.h
@@ -3,6 +3,8 @@
#include "simple-ipc.h"
+struct repository;
+
/*
* Returns true if built-in file system monitor daemon is defined
* for this platform.
@@ -16,7 +18,7 @@ int fsmonitor_ipc__is_supported(void);
*
* Returns NULL if the daemon is not supported on this platform.
*/
-const char *fsmonitor_ipc__get_path(void);
+const char *fsmonitor_ipc__get_path(struct repository *r);
/*
* Try to determine whether there is a `git-fsmonitor--daemon` process
diff --git a/fsmonitor-path-utils.h b/fsmonitor-path-utils.h
new file mode 100644
index 0000000000..5bfdfb81c1
--- /dev/null
+++ b/fsmonitor-path-utils.h
@@ -0,0 +1,60 @@
+#ifndef FSM_PATH_UTILS_H
+#define FSM_PATH_UTILS_H
+
+#include "strbuf.h"
+
+struct alias_info
+{
+ struct strbuf alias;
+ struct strbuf points_to;
+};
+
+struct fs_info {
+ int is_remote;
+ char *typename;
+};
+
+/*
+ * Get some basic filesystem information for the given path
+ *
+ * The caller owns the storage that is occupied by fs_info and
+ * is responsible for releasing it.
+ *
+ * Returns -1 on error, zero otherwise.
+ */
+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info);
+
+/*
+ * Determines if the filesystem that path resides on is remote.
+ *
+ * Returns -1 on error, 0 if not remote, 1 if remote.
+ */
+int fsmonitor__is_fs_remote(const char *path);
+
+/*
+ * Get the alias in given path, if any.
+ *
+ * Sets alias to the first alias that matches any part of the path.
+ *
+ * If an alias is found, info.alias and info.points_to are set to the
+ * found mapping.
+ *
+ * Returns -1 on error, 0 otherwise.
+ *
+ * The caller owns the storage that is occupied by info.alias and
+ * info.points_to and is responsible for releasing it.
+ */
+int fsmonitor__get_alias(const char *path, struct alias_info *info);
+
+/*
+ * Resolve the path against the given alias.
+ *
+ * Returns the resolved path if there is one, NULL otherwise.
+ *
+ * The caller owns the storage that the returned string occupies and
+ * is responsible for releasing it.
+ */
+char *fsmonitor__resolve_alias(const char *path,
+ const struct alias_info *info);
+
+#endif
diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c
index 464424a1e9..ee63a97dc5 100644
--- a/fsmonitor-settings.c
+++ b/fsmonitor-settings.c
@@ -1,7 +1,9 @@
#include "cache.h"
#include "config.h"
#include "repository.h"
+#include "fsmonitor-ipc.h"
#include "fsmonitor-settings.h"
+#include "fsmonitor-path-utils.h"
/*
* We keep this structure defintion private and have getters
@@ -13,7 +15,53 @@ struct fsmonitor_settings {
char *hook_path;
};
-static enum fsmonitor_reason check_for_incompatible(struct repository *r)
+/*
+ * Remote working directories are problematic for FSMonitor.
+ *
+ * The underlying file system on the server machine and/or the remote
+ * mount type dictates whether notification events are available at
+ * all to remote client machines.
+ *
+ * Kernel differences between the server and client machines also
+ * dictate the how (buffering, frequency, de-dup) the events are
+ * delivered to client machine processes.
+ *
+ * A client machine (such as a laptop) may choose to suspend/resume
+ * and it is unclear (without lots of testing) whether the watcher can
+ * resync after a resume. We might be able to treat this as a normal
+ * "events were dropped by the kernel" event and do our normal "flush
+ * and resync" --or-- we might need to close the existing (zombie?)
+ * notification fd and create a new one.
+ *
+ * In theory, the above issues need to be addressed whether we are
+ * using the Hook or IPC API.
+ *
+ * So (for now at least), mark remote working directories as
+ * incompatible unless 'fsmonitor.allowRemote' is true.
+ *
+ */
+#ifdef HAVE_FSMONITOR_OS_SETTINGS
+static enum fsmonitor_reason check_remote(struct repository *r)
+{
+ int allow_remote = -1; /* -1 unset, 0 not allowed, 1 allowed */
+ int is_remote = fsmonitor__is_fs_remote(r->worktree);
+
+ switch (is_remote) {
+ case 0:
+ return FSMONITOR_REASON_OK;
+ case 1:
+ repo_config_get_bool(r, "fsmonitor.allowremote", &allow_remote);
+ if (allow_remote < 1)
+ return FSMONITOR_REASON_REMOTE;
+ else
+ return FSMONITOR_REASON_OK;
+ default:
+ return FSMONITOR_REASON_ERROR;
+ }
+}
+#endif
+
+static enum fsmonitor_reason check_for_incompatible(struct repository *r, int ipc)
{
if (!r->worktree) {
/*
@@ -27,7 +75,10 @@ static enum fsmonitor_reason check_for_incompatible(struct repository *r)
{
enum fsmonitor_reason reason;
- reason = fsm_os__incompatible(r);
+ reason = check_remote(r);
+ if (reason != FSMONITOR_REASON_OK)
+ return reason;
+ reason = fsm_os__incompatible(r, ipc);
if (reason != FSMONITOR_REASON_OK)
return reason;
}
@@ -112,7 +163,7 @@ const char *fsm_settings__get_hook_path(struct repository *r)
void fsm_settings__set_ipc(struct repository *r)
{
- enum fsmonitor_reason reason = check_for_incompatible(r);
+ enum fsmonitor_reason reason = check_for_incompatible(r, 1);
if (reason != FSMONITOR_REASON_OK) {
fsm_settings__set_incompatible(r, reason);
@@ -135,7 +186,7 @@ void fsm_settings__set_ipc(struct repository *r)
void fsm_settings__set_hook(struct repository *r, const char *path)
{
- enum fsmonitor_reason reason = check_for_incompatible(r);
+ enum fsmonitor_reason reason = check_for_incompatible(r, 0);
if (reason != FSMONITOR_REASON_OK) {
fsm_settings__set_incompatible(r, reason);
@@ -192,10 +243,11 @@ enum fsmonitor_reason fsm_settings__get_reason(struct repository *r)
return r->settings.fsmonitor->reason;
}
-char *fsm_settings__get_incompatible_msg(const struct repository *r,
+char *fsm_settings__get_incompatible_msg(struct repository *r,
enum fsmonitor_reason reason)
{
struct strbuf msg = STRBUF_INIT;
+ const char *socket_dir;
switch (reason) {
case FSMONITOR_REASON_UNTESTED:
@@ -231,9 +283,11 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,
goto done;
case FSMONITOR_REASON_NOSOCKETS:
+ socket_dir = dirname((char *)fsmonitor_ipc__get_path(r));
strbuf_addf(&msg,
- _("repository '%s' is incompatible with fsmonitor due to lack of Unix sockets"),
- r->worktree);
+ _("socket directory '%s' is incompatible with fsmonitor due"
+ " to lack of Unix sockets support"),
+ socket_dir);
goto done;
}
diff --git a/fsmonitor-settings.h b/fsmonitor-settings.h
index d9c2605197..ab02e3995e 100644
--- a/fsmonitor-settings.h
+++ b/fsmonitor-settings.h
@@ -33,7 +33,7 @@ enum fsmonitor_mode fsm_settings__get_mode(struct repository *r);
const char *fsm_settings__get_hook_path(struct repository *r);
enum fsmonitor_reason fsm_settings__get_reason(struct repository *r);
-char *fsm_settings__get_incompatible_msg(const struct repository *r,
+char *fsm_settings__get_incompatible_msg(struct repository *r,
enum fsmonitor_reason reason);
struct fsmonitor_settings;
@@ -48,7 +48,7 @@ struct fsmonitor_settings;
* fsm_os__* routines should considered private to fsm_settings__
* routines.
*/
-enum fsmonitor_reason fsm_os__incompatible(struct repository *r);
+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc);
#endif /* HAVE_FSMONITOR_OS_SETTINGS */
#endif /* FSMONITOR_SETTINGS_H */
diff --git a/fsmonitor.c b/fsmonitor.c
index 57d6a483be..08af00c738 100644
--- a/fsmonitor.c
+++ b/fsmonitor.c
@@ -295,6 +295,7 @@ static int fsmonitor_force_update_threshold = 100;
void refresh_fsmonitor(struct index_state *istate)
{
+ static int warn_once = 0;
struct strbuf query_result = STRBUF_INIT;
int query_success = 0, hook_version = -1;
size_t bol = 0; /* beginning of line */
@@ -305,6 +306,14 @@ void refresh_fsmonitor(struct index_state *istate)
int is_trivial = 0;
struct repository *r = istate->repo ? istate->repo : the_repository;
enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);
+ enum fsmonitor_reason reason = fsm_settings__get_reason(r);
+
+ if (!warn_once && reason > FSMONITOR_REASON_OK) {
+ char *msg = fsm_settings__get_incompatible_msg(r, reason);
+ warn_once = 1;
+ warning("%s", msg);
+ free(msg);
+ }
if (fsm_mode <= FSMONITOR_MODE_DISABLED ||
istate->fsmonitor_has_run_once)