{"thread":{"id":"58588","subject":"[PATCH 00/12] fsmonitor: Implement fsmonitor for Linux","startedAt":"2022-10-09T14:37:44Z","lastAt":"2023-04-12T05:43:06Z","messageCount":89,"participants":["Eric DeCosta via GitGitGadget","Junio C Hamano","Eric Sunshine","Ævar Arnfjörð Bjarmason","Jeff Hostetler","Eric DeCosta","Glen Choo","Johannes Schindelin","Taylor Blau"],"isPatch":true,"patchVersion":1,"patchTotal":12},"messages":[{"id":"464444","messageId":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":null,"subject":"[PATCH 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:26Z","receivedAt":"2022-10-09T14:37:44Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\nWindows and Mac OS.\n\nThis patch set builds upon previous work for done for Windows and Mac OS\n(first 6 patches) to implement a fsmonitor back-end for Linux based on the\nLinux inotify API. inotify differs significantly from the equivalent Windows\nand Mac OS APIs in that a watch must be registered for every directory of\ninterest (rather than a singular watch at the root of the directory tree)\nand special care must be taken to handle directory renames correctly.\n\nMore information about inotify:\nhttps://man7.org/linux/man-pages/man7/inotify.7.html\n\nEric DeCosta (12):\n  fsmonitor: refactor filesystem checks to common interface\n  fsmonitor: relocate socket file if .git directory is remote\n  fsmonitor: avoid socket location check if using hook\n  fsmonitor: deal with synthetic firmlinks on macOS\n  fsmonitor: check for compatability before communicating with fsmonitor\n  fsmonitor: add documentation for allowRemote and socketDir options\n  fsmonitor: prepare to share code between Mac OS and Linux\n  fsmonitor: determine if filesystem is local or remote\n  fsmonitor: implement filesystem change listener for Linux\n  fsmonitor: enable fsmonitor for Linux\n  fsmonitor: test updates\n  fsmonitor: update doc for Linux\n\n Documentation/config.txt                      |   2 +\n Documentation/config/fsmonitor--daemon.txt    |  11 +\n Documentation/git-fsmonitor--daemon.txt       |  45 +-\n Makefile                                      |   6 +-\n builtin/fsmonitor--daemon.c                   |  11 +-\n ...{fsm-health-darwin.c => fsm-health-unix.c} |   0\n compat/fsmonitor/fsm-ipc-unix.c               |  52 ++\n compat/fsmonitor/fsm-ipc-win32.c              |   9 +\n compat/fsmonitor/fsm-listen-darwin.c          |  14 +-\n compat/fsmonitor/fsm-listen-linux.c           | 664 ++++++++++++++++++\n compat/fsmonitor/fsm-path-utils-darwin.c      | 135 ++++\n compat/fsmonitor/fsm-path-utils-linux.c       | 163 +++++\n compat/fsmonitor/fsm-path-utils-win32.c       | 145 ++++\n compat/fsmonitor/fsm-settings-darwin.c        |  89 ---\n compat/fsmonitor/fsm-settings-unix.c          |  62 ++\n compat/fsmonitor/fsm-settings-win32.c         | 174 +----\n config.mak.uname                              |  13 +\n contrib/buildsystems/CMakeLists.txt           |  19 +-\n fsmonitor--daemon.h                           |   3 +\n fsmonitor-ipc.c                               |  18 +-\n fsmonitor-ipc.h                               |   4 +-\n fsmonitor-path-utils.h                        |  60 ++\n fsmonitor-settings.c                          |  68 +-\n fsmonitor-settings.h                          |   4 +-\n fsmonitor.c                                   |   7 +\n t/t7527-builtin-fsmonitor.sh                  |  58 +-\n 26 files changed, 1531 insertions(+), 305 deletions(-)\n create mode 100644 Documentation/config/fsmonitor--daemon.txt\n rename compat/fsmonitor/{fsm-health-darwin.c => fsm-health-unix.c} (100%)\n create mode 100644 compat/fsmonitor/fsm-ipc-unix.c\n create mode 100644 compat/fsmonitor/fsm-ipc-win32.c\n create mode 100644 compat/fsmonitor/fsm-listen-linux.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-darwin.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-win32.c\n delete mode 100644 compat/fsmonitor/fsm-settings-darwin.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.c\n create mode 100644 fsmonitor-path-utils.h\n\n\nbase-commit: 3dcec76d9df911ed8321007b1d197c1a206dc164\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1352%2Fedecosta-mw%2Ffsmonitor_linux-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1352/edecosta-mw/fsmonitor_linux-v1\nPull-Request: https://github.com/git/git/pull/1352\n-- \ngitgitgadget\n"},{"id":"464445","messageId":"7bf1cdfe3b259f7135f3a50dcdd653563d5e19f6.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 02/12] fsmonitor: relocate socket file if .git directory is remote","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:28Z","receivedAt":"2022-10-09T14:37:52Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nIf the .git directory is on a remote filesystem, create the socket\nfile in 'fsmonitor.socketDir' if it is defined, else create it in $HOME.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Makefile                               |  1 +\n builtin/fsmonitor--daemon.c            |  3 +-\n compat/fsmonitor/fsm-ipc-darwin.c      | 52 ++++++++++++++++++++++++++\n compat/fsmonitor/fsm-ipc-win32.c       |  9 +++++\n compat/fsmonitor/fsm-settings-darwin.c |  2 +-\n contrib/buildsystems/CMakeLists.txt    |  2 +\n fsmonitor-ipc.c                        | 18 ++++-----\n fsmonitor-ipc.h                        |  4 +-\n 8 files changed, 78 insertions(+), 13 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-ipc-darwin.c\n create mode 100644 compat/fsmonitor/fsm-ipc-win32.c\n\ndiff --git a/Makefile b/Makefile\nindex ffab427ea5b..feb675a6959 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2039,6 +2039,7 @@ ifdef FSMONITOR_DAEMON_BACKEND\n \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o\n+\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n endif\n \n ifdef FSMONITOR_OS_SETTINGS\ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex 2c109cf8b37..0123fc33ed2 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -1343,7 +1343,8 @@ static int fsmonitor_run_daemon(void)\n \t * directory.)\n \t */\n \tstrbuf_init(&state.path_ipc, 0);\n-\tstrbuf_addstr(&state.path_ipc, absolute_path(fsmonitor_ipc__get_path()));\n+\tstrbuf_addstr(&state.path_ipc,\n+\t\tabsolute_path(fsmonitor_ipc__get_path(the_repository)));\n \n \t/*\n \t * Confirm that we can create platform-specific resources for the\ndiff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-darwin.c\nnew file mode 100644\nindex 00000000000..ce843d63348\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-ipc-darwin.c\n@@ -0,0 +1,52 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"strbuf.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n+\n+static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n+\n+const char *fsmonitor_ipc__get_path(struct repository *r)\n+{\n+\tstatic const char *ipc_path = NULL;\n+\tSHA_CTX sha1ctx;\n+\tchar *sock_dir = NULL;\n+\tstruct strbuf ipc_file = STRBUF_INIT;\n+\tunsigned char hash[SHA_DIGEST_LENGTH];\n+\n+\tif (!r)\n+\t\tBUG(\"No repository passed into fsmonitor_ipc__get_path\");\n+\n+\tif (ipc_path)\n+\t\treturn ipc_path;\n+\n+\n+\t/* By default the socket file is created in the .git directory */\n+\tif (fsmonitor__is_fs_remote(r->gitdir) < 1) {\n+\t\tipc_path = fsmonitor_ipc__get_default_path();\n+\t\treturn ipc_path;\n+\t}\n+\n+\tSHA1_Init(&sha1ctx);\n+\tSHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n+\tSHA1_Final(hash, &sha1ctx);\n+\n+\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n+\n+\t/* Create the socket file in either socketDir or $HOME */\n+\tif (sock_dir && *sock_dir) {\n+\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n+\t\t\t\t\tsock_dir, hash_to_hex(hash));\n+\t} else {\n+\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n+\t}\n+\tfree(sock_dir);\n+\n+\tipc_path = interpolate_path(ipc_file.buf, 1);\n+\tif (!ipc_path)\n+\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n+\n+\tstrbuf_release(&ipc_file);\n+\treturn ipc_path;\n+}\ndiff --git a/compat/fsmonitor/fsm-ipc-win32.c b/compat/fsmonitor/fsm-ipc-win32.c\nnew file mode 100644\nindex 00000000000..e08c505c148\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-ipc-win32.c\n@@ -0,0 +1,9 @@\n+#include \"config.h\"\n+#include \"fsmonitor-ipc.h\"\n+\n+const char *fsmonitor_ipc__get_path(struct repository *r) {\n+\tstatic char *ret;\n+\tif (!ret)\n+\t\tret = git_pathdup(\"fsmonitor--daemon.ipc\");\n+\treturn ret;\n+}\ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c\nindex 699f0b272e6..7241c4c22c9 100644\n--- a/compat/fsmonitor/fsm-settings-darwin.c\n+++ b/compat/fsmonitor/fsm-settings-darwin.c\n@@ -26,7 +26,7 @@\n static enum fsmonitor_reason check_uds_volume(struct repository *r)\n {\n \tstruct fs_info fs;\n-\tconst char *ipc_path = fsmonitor_ipc__get_path();\n+\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n \tstruct strbuf path = STRBUF_INIT;\n \tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n \ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 5482a04b3ce..787738e6fa3 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -308,6 +308,7 @@ if(SUPPORTS_SIMPLE_IPC)\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-win32.c)\n \n \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n@@ -316,6 +317,7 @@ if(SUPPORTS_SIMPLE_IPC)\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-darwin.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c)\n \n \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\ndiff --git a/fsmonitor-ipc.c b/fsmonitor-ipc.c\nindex 789e7397baa..c0f42301c84 100644\n--- a/fsmonitor-ipc.c\n+++ b/fsmonitor-ipc.c\n@@ -18,7 +18,7 @@ int fsmonitor_ipc__is_supported(void)\n \treturn 0;\n }\n \n-const char *fsmonitor_ipc__get_path(void)\n+const char *fsmonitor_ipc__get_path(struct repository *r)\n {\n \treturn NULL;\n }\n@@ -47,11 +47,9 @@ int fsmonitor_ipc__is_supported(void)\n \treturn 1;\n }\n \n-GIT_PATH_FUNC(fsmonitor_ipc__get_path, \"fsmonitor--daemon.ipc\")\n-\n enum ipc_active_state fsmonitor_ipc__get_state(void)\n {\n-\treturn ipc_get_active_state(fsmonitor_ipc__get_path());\n+\treturn ipc_get_active_state(fsmonitor_ipc__get_path(the_repository));\n }\n \n static int spawn_daemon(void)\n@@ -81,8 +79,8 @@ int fsmonitor_ipc__send_query(const char *since_token,\n \ttrace2_data_string(\"fsm_client\", NULL, \"query/command\", tok);\n \n try_again:\n-\tstate = ipc_client_try_connect(fsmonitor_ipc__get_path(), &options,\n-\t\t\t\t       &connection);\n+\tstate = ipc_client_try_connect(fsmonitor_ipc__get_path(the_repository),\n+\t\t\t\t\t\t&options, &connection);\n \n \tswitch (state) {\n \tcase IPC_STATE__LISTENING:\n@@ -117,13 +115,13 @@ try_again:\n \n \tcase IPC_STATE__INVALID_PATH:\n \t\tret = error(_(\"fsmonitor_ipc__send_query: invalid path '%s'\"),\n-\t\t\t    fsmonitor_ipc__get_path());\n+\t\t\t    fsmonitor_ipc__get_path(the_repository));\n \t\tgoto done;\n \n \tcase IPC_STATE__OTHER_ERROR:\n \tdefault:\n \t\tret = error(_(\"fsmonitor_ipc__send_query: unspecified error on '%s'\"),\n-\t\t\t    fsmonitor_ipc__get_path());\n+\t\t\t    fsmonitor_ipc__get_path(the_repository));\n \t\tgoto done;\n \t}\n \n@@ -149,8 +147,8 @@ int fsmonitor_ipc__send_command(const char *command,\n \toptions.wait_if_busy = 1;\n \toptions.wait_if_not_found = 0;\n \n-\tstate = ipc_client_try_connect(fsmonitor_ipc__get_path(), &options,\n-\t\t\t\t       &connection);\n+\tstate = ipc_client_try_connect(fsmonitor_ipc__get_path(the_repository),\n+\t\t\t\t\t\t&options, &connection);\n \tif (state != IPC_STATE__LISTENING) {\n \t\tdie(_(\"fsmonitor--daemon is not running\"));\n \t\treturn -1;\ndiff --git a/fsmonitor-ipc.h b/fsmonitor-ipc.h\nindex b6a7067c3af..8b489da762b 100644\n--- a/fsmonitor-ipc.h\n+++ b/fsmonitor-ipc.h\n@@ -3,6 +3,8 @@\n \n #include \"simple-ipc.h\"\n \n+struct repository;\n+\n /*\n  * Returns true if built-in file system monitor daemon is defined\n  * for this platform.\n@@ -16,7 +18,7 @@ int fsmonitor_ipc__is_supported(void);\n  *\n  * Returns NULL if the daemon is not supported on this platform.\n  */\n-const char *fsmonitor_ipc__get_path(void);\n+const char *fsmonitor_ipc__get_path(struct repository *r);\n \n /*\n  * Try to determine whether there is a `git-fsmonitor--daemon` process\n-- \ngitgitgadget\n\n"},{"id":"464446","messageId":"863063aefeeecfd23bb50eb111fcfbf5879a8ee3.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 04/12] fsmonitor: deal with synthetic firmlinks on macOS","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:30Z","receivedAt":"2022-10-09T14:37:54Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nStarting with macOS 10.15 (Catalina), Apple introduced a new feature\ncalled 'firmlinks' in order to separate the boot volume into two\nvolumes, one read-only and one writable but still present them to the\nuser as a single volume. Along with this change, Apple removed the\nability to create symlinks in the root directory and replaced them with\n'synthetic firmlinks'. See 'man synthetic.conf'\n\nWhen FSEevents reports the path of changed files, if the path involves\na synthetic firmlink, the path is reported from the point of the\nsynthetic firmlink and not the real path. For example:\n\nReal path:\n/System/Volumes/Data/network/working/directory/foo.txt\n\nSynthetic firmlink:\n/network -> /System/Volumes/Data/network\n\nFSEvents path:\n/network/working/directory/foo.txt\n\nThis causes the FSEvents path to not match against the worktree\ndirectory.\n\nThere are several ways in which synthetic firmlinks can be created:\nthey can be defined in /etc/synthetic.conf, the automounter can create\nthem, and there may be other means. Simply reading /etc/synthetic.conf\nis insufficient. No matter what process creates synthetic firmlinks,\nthey all get created in the root directory.\n\nTherefore, in order to deal with synthetic firmlinks, the root directory\nis scanned and the first possible synthetic firmink that, when resolved,\nis a prefix of the worktree is used to map FSEvents paths to worktree\npaths.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n builtin/fsmonitor--daemon.c              |  8 +++\n compat/fsmonitor/fsm-listen-darwin.c     | 14 +++-\n compat/fsmonitor/fsm-path-utils-darwin.c | 92 ++++++++++++++++++++++++\n compat/fsmonitor/fsm-path-utils-win32.c  | 17 +++++\n fsmonitor--daemon.h                      |  3 +\n fsmonitor-path-utils.h                   | 36 +++++++++-\n 6 files changed, 167 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex 0123fc33ed2..7a4cb78c7dd 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -3,6 +3,7 @@\n #include \"parse-options.h\"\n #include \"fsmonitor.h\"\n #include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n #include \"compat/fsmonitor/fsm-health.h\"\n #include \"compat/fsmonitor/fsm-listen.h\"\n #include \"fsmonitor--daemon.h\"\n@@ -1282,6 +1283,11 @@ static int fsmonitor_run_daemon(void)\n \tstrbuf_addstr(&state.path_worktree_watch, absolute_path(get_git_work_tree()));\n \tstate.nr_paths_watching = 1;\n \n+\tstrbuf_init(&state.alias.alias, 0);\n+\tstrbuf_init(&state.alias.points_to, 0);\n+\tif ((err = fsmonitor__get_alias(state.path_worktree_watch.buf, &state.alias)))\n+\t\tgoto done;\n+\n \t/*\n \t * We create and delete cookie files somewhere inside the .git\n \t * directory to help us keep sync with the file system.  If\n@@ -1391,6 +1397,8 @@ done:\n \tstrbuf_release(&state.path_gitdir_watch);\n \tstrbuf_release(&state.path_cookie_prefix);\n \tstrbuf_release(&state.path_ipc);\n+\tstrbuf_release(&state.alias.alias);\n+\tstrbuf_release(&state.alias.points_to);\n \n \treturn err;\n }\ndiff --git a/compat/fsmonitor/fsm-listen-darwin.c b/compat/fsmonitor/fsm-listen-darwin.c\nindex 8e208e8289e..daeee4e465c 100644\n--- a/compat/fsmonitor/fsm-listen-darwin.c\n+++ b/compat/fsmonitor/fsm-listen-darwin.c\n@@ -26,6 +26,7 @@\n #include \"fsmonitor.h\"\n #include \"fsm-listen.h\"\n #include \"fsmonitor--daemon.h\"\n+#include \"fsmonitor-path-utils.h\"\n \n struct fsm_listen_data\n {\n@@ -198,8 +199,9 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,\n \tstruct string_list cookie_list = STRING_LIST_INIT_DUP;\n \tconst char *path_k;\n \tconst char *slash;\n-\tint k;\n+\tchar *resolved = NULL;\n \tstruct strbuf tmp = STRBUF_INIT;\n+\tint k;\n \n \t/*\n \t * Build a list of all filesystem changes into a private/local\n@@ -209,7 +211,12 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,\n \t\t/*\n \t\t * On Mac, we receive an array of absolute paths.\n \t\t */\n-\t\tpath_k = paths[k];\n+\t\tfree(resolved);\n+\t\tresolved = fsmonitor__resolve_alias(paths[k], &state->alias);\n+\t\tif (resolved)\n+\t\t\tpath_k = resolved;\n+\t\telse\n+\t\t\tpath_k = paths[k];\n \n \t\t/*\n \t\t * If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.\n@@ -238,6 +245,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,\n \t\t\tfsmonitor_force_resync(state);\n \t\t\tfsmonitor_batch__free_list(batch);\n \t\t\tstring_list_clear(&cookie_list, 0);\n+\t\t\tbatch = NULL;\n \n \t\t\t/*\n \t\t\t * We assume that any events that we received\n@@ -360,12 +368,14 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,\n \t\t}\n \t}\n \n+\tfree(resolved);\n \tfsmonitor_publish(state, batch, &cookie_list);\n \tstring_list_clear(&cookie_list, 0);\n \tstrbuf_release(&tmp);\n \treturn;\n \n force_shutdown:\n+\tfree(resolved);\n \tfsmonitor_batch__free_list(batch);\n \tstring_list_clear(&cookie_list, 0);\n \ndiff --git a/compat/fsmonitor/fsm-path-utils-darwin.c b/compat/fsmonitor/fsm-path-utils-darwin.c\nindex d46d7f13538..ce5a8febe09 100644\n--- a/compat/fsmonitor/fsm-path-utils-darwin.c\n+++ b/compat/fsmonitor/fsm-path-utils-darwin.c\n@@ -1,5 +1,8 @@\n #include \"fsmonitor.h\"\n #include \"fsmonitor-path-utils.h\"\n+#include <dirent.h>\n+#include <errno.h>\n+#include <fcntl.h>\n #include <sys/param.h>\n #include <sys/mount.h>\n \n@@ -41,3 +44,92 @@ int fsmonitor__is_fs_remote(const char *path)\n \n \treturn fs.is_remote;\n }\n+\n+/*\n+ * Scan the root directory for synthetic firmlinks that when resolved\n+ * are a prefix of the path, stopping at the first one found.\n+ *\n+ * Some information about firmlinks and synthetic firmlinks:\n+ * https://eclecticlight.co/2020/01/23/catalina-boot-volumes/\n+ *\n+ * macOS no longer allows symlinks in the root directory; any link found\n+ * there is therefore a synthetic firmlink.\n+ *\n+ * If this function gets called often, will want to cache all the firmlink\n+ * information, but for now there is only one caller of this function.\n+ *\n+ * If there is more than one alias for the path, that is another\n+ * matter altogether.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info)\n+{\n+\tDIR *dir;\n+\tint retval = -1;\n+\tconst char *const root = \"/\";\n+\tstruct stat st;\n+\tstruct dirent *de;\n+\tstruct strbuf alias;\n+\tstruct strbuf points_to = STRBUF_INIT;\n+\n+\tdir = opendir(root);\n+\tif (!dir)\n+\t\treturn error_errno(_(\"opendir('%s') failed\"), root);\n+\n+\tstrbuf_init(&alias, 256);\n+\n+\twhile ((de = readdir(dir)) != NULL) {\n+\t\tstrbuf_reset(&alias);\n+\t\tstrbuf_addf(&alias, \"%s%s\", root, de->d_name);\n+\n+\t\tif (lstat(alias.buf, &st) < 0) {\n+\t\t\terror_errno(_(\"lstat('%s') failed\"), alias.buf);\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tif (!S_ISLNK(st.st_mode))\n+\t\t\tcontinue;\n+\n+\t\tif (strbuf_readlink(&points_to, alias.buf, st.st_size) < 0) {\n+\t\t\terror_errno(_(\"strbuf_readlink('%s') failed\"), alias.buf);\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tif (!strncmp(points_to.buf, path, points_to.len) &&\n+\t\t\t(path[points_to.len] == '/')) {\n+\t\t\tstrbuf_addbuf(&info->alias, &alias);\n+\t\t\tstrbuf_addbuf(&info->points_to, &points_to);\n+\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"Found alias for '%s' : '%s' -> '%s'\",\n+\t\t\t\tpath, info->alias.buf, info->points_to.buf);\n+\t\t\tretval = 0;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\tretval = 0; /* no alias */\n+\n+done:\n+\tstrbuf_release(&alias);\n+\tstrbuf_release(&points_to);\n+\tif (closedir(dir) < 0)\n+\t\treturn error_errno(_(\"closedir('%s') failed\"), root);\n+\treturn retval;\n+}\n+\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info)\n+{\n+\tif (!info->alias.len)\n+\t\treturn NULL;\n+\n+\tif ((!strncmp(info->alias.buf, path, info->alias.len))\n+\t\t&& path[info->alias.len] == '/') {\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n+\t\tconst char *remainder = path + info->alias.len;\n+\n+\t\tstrbuf_addbuf(&tmp, &info->points_to);\n+\t\tstrbuf_add(&tmp, remainder, strlen(remainder));\n+\t\treturn strbuf_detach(&tmp, NULL);\n+\t}\n+\n+\treturn NULL;\n+}\ndiff --git a/compat/fsmonitor/fsm-path-utils-win32.c b/compat/fsmonitor/fsm-path-utils-win32.c\nindex a90b8f7925b..0d95bbb416f 100644\n--- a/compat/fsmonitor/fsm-path-utils-win32.c\n+++ b/compat/fsmonitor/fsm-path-utils-win32.c\n@@ -126,3 +126,20 @@ int fsmonitor__is_fs_remote(const char *path)\n \t\treturn -1;\n \treturn fs.is_remote;\n }\n+\n+/*\n+ * No-op for now.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info)\n+{\n+\treturn 0;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info)\n+{\n+\treturn NULL;\n+}\ndiff --git a/fsmonitor--daemon.h b/fsmonitor--daemon.h\nindex 2102a5c9ff5..e24838f9a86 100644\n--- a/fsmonitor--daemon.h\n+++ b/fsmonitor--daemon.h\n@@ -8,6 +8,7 @@\n #include \"run-command.h\"\n #include \"simple-ipc.h\"\n #include \"thread-utils.h\"\n+#include \"fsmonitor-path-utils.h\"\n \n struct fsmonitor_batch;\n struct fsmonitor_token_data;\n@@ -43,6 +44,7 @@ struct fsmonitor_daemon_state {\n \n \tstruct strbuf path_worktree_watch;\n \tstruct strbuf path_gitdir_watch;\n+\tstruct alias_info alias;\n \tint nr_paths_watching;\n \n \tstruct fsmonitor_token_data *current_token_data;\n@@ -59,6 +61,7 @@ struct fsmonitor_daemon_state {\n \n \tstruct ipc_server_data *ipc_server_data;\n \tstruct strbuf path_ipc;\n+\n };\n \n /*\ndiff --git a/fsmonitor-path-utils.h b/fsmonitor-path-utils.h\nindex 41edf5b934f..5bfdfb81c14 100644\n--- a/fsmonitor-path-utils.h\n+++ b/fsmonitor-path-utils.h\n@@ -1,13 +1,21 @@\n #ifndef FSM_PATH_UTILS_H\n #define FSM_PATH_UTILS_H\n \n+#include \"strbuf.h\"\n+\n+struct alias_info\n+{\n+\tstruct strbuf alias;\n+\tstruct strbuf points_to;\n+};\n+\n struct fs_info {\n \tint is_remote;\n \tchar *typename;\n };\n \n /*\n- * Get some basic filesystem informtion for the given path\n+ * Get some basic filesystem information for the given path\n  *\n  * The caller owns the storage that is occupied by fs_info and\n  * is responsible for releasing it.\n@@ -23,4 +31,30 @@ int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info);\n  */\n int fsmonitor__is_fs_remote(const char *path);\n \n+/*\n+ * Get the alias in given path, if any.\n+ *\n+ * Sets alias to the first alias that matches any part of the path.\n+ *\n+ * If an alias is found, info.alias and info.points_to are set to the\n+ * found mapping.\n+ *\n+ * Returns -1 on error, 0 otherwise.\n+ *\n+ * The caller owns the storage that is occupied by info.alias and\n+ * info.points_to and is responsible for releasing it.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info);\n+\n+/*\n+ * Resolve the path against the given alias.\n+ *\n+ * Returns the resolved path if there is one, NULL otherwise.\n+ *\n+ * The caller owns the storage that the returned string occupies and\n+ * is responsible for releasing it.\n+ */\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info);\n+\n #endif\n-- \ngitgitgadget\n\n"},{"id":"464447","messageId":"c5e8b6cfe5da269d27d3f171ccedd119a6db4840.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 03/12] fsmonitor: avoid socket location check if using hook","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:29Z","receivedAt":"2022-10-09T14:37:57Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nIf monitoring is done via fsmonitor hook rather than IPC there is no\nneed to check if the location of the Unix Domain socket (UDS) file is\non a remote filesystem.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-settings-darwin.c | 10 ++++++----\n compat/fsmonitor/fsm-settings-win32.c  |  2 +-\n fsmonitor-settings.c                   |  8 ++++----\n fsmonitor-settings.h                   |  2 +-\n 4 files changed, 12 insertions(+), 10 deletions(-)\n\ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c\nindex 7241c4c22c9..6abbc7af3ab 100644\n--- a/compat/fsmonitor/fsm-settings-darwin.c\n+++ b/compat/fsmonitor/fsm-settings-darwin.c\n@@ -48,13 +48,15 @@ static enum fsmonitor_reason check_uds_volume(struct repository *r)\n \treturn FSMONITOR_REASON_OK;\n }\n \n-enum fsmonitor_reason fsm_os__incompatible(struct repository *r)\n+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n {\n \tenum fsmonitor_reason reason;\n \n-\treason = check_uds_volume(r);\n-\tif (reason != FSMONITOR_REASON_OK)\n-\t\treturn reason;\n+\tif (ipc) {\n+\t\treason = check_uds_volume(r);\n+\t\tif (reason != FSMONITOR_REASON_OK)\n+\t\t\treturn reason;\n+\t}\n \n \treturn FSMONITOR_REASON_OK;\n }\ndiff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c\nindex d88b06ae610..a8af31b71de 100644\n--- a/compat/fsmonitor/fsm-settings-win32.c\n+++ b/compat/fsmonitor/fsm-settings-win32.c\n@@ -25,7 +25,7 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r)\n \treturn FSMONITOR_REASON_OK;\n }\n \n-enum fsmonitor_reason fsm_os__incompatible(struct repository *r)\n+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n {\n \tenum fsmonitor_reason reason;\n \ndiff --git a/fsmonitor-settings.c b/fsmonitor-settings.c\nindex d288cbad479..531a1b6f956 100644\n--- a/fsmonitor-settings.c\n+++ b/fsmonitor-settings.c\n@@ -60,7 +60,7 @@ static enum fsmonitor_reason check_remote(struct repository *r)\n }\n #endif\n \n-static enum fsmonitor_reason check_for_incompatible(struct repository *r)\n+static enum fsmonitor_reason check_for_incompatible(struct repository *r, int ipc)\n {\n \tif (!r->worktree) {\n \t\t/*\n@@ -77,7 +77,7 @@ static enum fsmonitor_reason check_for_incompatible(struct repository *r)\n \t\treason = check_remote(r);\n \t\tif (reason != FSMONITOR_REASON_OK)\n \t\t\treturn reason;\n-\t\treason = fsm_os__incompatible(r);\n+\t\treason = fsm_os__incompatible(r, ipc);\n \t\tif (reason != FSMONITOR_REASON_OK)\n \t\t\treturn reason;\n \t}\n@@ -162,7 +162,7 @@ const char *fsm_settings__get_hook_path(struct repository *r)\n \n void fsm_settings__set_ipc(struct repository *r)\n {\n-\tenum fsmonitor_reason reason = check_for_incompatible(r);\n+\tenum fsmonitor_reason reason = check_for_incompatible(r, 1);\n \n \tif (reason != FSMONITOR_REASON_OK) {\n \t\tfsm_settings__set_incompatible(r, reason);\n@@ -185,7 +185,7 @@ void fsm_settings__set_ipc(struct repository *r)\n \n void fsm_settings__set_hook(struct repository *r, const char *path)\n {\n-\tenum fsmonitor_reason reason = check_for_incompatible(r);\n+\tenum fsmonitor_reason reason = check_for_incompatible(r, 0);\n \n \tif (reason != FSMONITOR_REASON_OK) {\n \t\tfsm_settings__set_incompatible(r, reason);\ndiff --git a/fsmonitor-settings.h b/fsmonitor-settings.h\nindex d9c2605197f..0721617b95a 100644\n--- a/fsmonitor-settings.h\n+++ b/fsmonitor-settings.h\n@@ -48,7 +48,7 @@ struct fsmonitor_settings;\n  * fsm_os__* routines should considered private to fsm_settings__\n  * routines.\n  */\n-enum fsmonitor_reason fsm_os__incompatible(struct repository *r);\n+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc);\n #endif /* HAVE_FSMONITOR_OS_SETTINGS */\n \n #endif /* FSMONITOR_SETTINGS_H */\n-- \ngitgitgadget\n\n"},{"id":"464448","messageId":"fa974bfd5ef8f24d259ec31663ee18b65d96d83c.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 05/12] fsmonitor: check for compatability before communicating with fsmonitor","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:31Z","receivedAt":"2022-10-09T14:37:59Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nIf fsmonitor is not in a compatible state, warn with an appropriate message.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n fsmonitor-settings.c | 10 +++++++---\n fsmonitor-settings.h |  2 +-\n fsmonitor.c          |  7 +++++++\n 3 files changed, 15 insertions(+), 4 deletions(-)\n\ndiff --git a/fsmonitor-settings.c b/fsmonitor-settings.c\nindex 531a1b6f956..ee63a97dc51 100644\n--- a/fsmonitor-settings.c\n+++ b/fsmonitor-settings.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"config.h\"\n #include \"repository.h\"\n+#include \"fsmonitor-ipc.h\"\n #include \"fsmonitor-settings.h\"\n #include \"fsmonitor-path-utils.h\"\n \n@@ -242,10 +243,11 @@ enum fsmonitor_reason fsm_settings__get_reason(struct repository *r)\n \treturn r->settings.fsmonitor->reason;\n }\n \n-char *fsm_settings__get_incompatible_msg(const struct repository *r,\n+char *fsm_settings__get_incompatible_msg(struct repository *r,\n \t\t\t\t\t enum fsmonitor_reason reason)\n {\n \tstruct strbuf msg = STRBUF_INIT;\n+\tconst char *socket_dir;\n \n \tswitch (reason) {\n \tcase FSMONITOR_REASON_UNTESTED:\n@@ -281,9 +283,11 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,\n \t\tgoto done;\n \n \tcase FSMONITOR_REASON_NOSOCKETS:\n+\t\tsocket_dir = dirname((char *)fsmonitor_ipc__get_path(r));\n \t\tstrbuf_addf(&msg,\n-\t\t\t    _(\"repository '%s' is incompatible with fsmonitor due to lack of Unix sockets\"),\n-\t\t\t    r->worktree);\n+\t\t\t    _(\"socket directory '%s' is incompatible with fsmonitor due\"\n+\t\t\t      \" to lack of Unix sockets support\"),\n+\t\t\t    socket_dir);\n \t\tgoto done;\n \t}\n \ndiff --git a/fsmonitor-settings.h b/fsmonitor-settings.h\nindex 0721617b95a..ab02e3995ee 100644\n--- a/fsmonitor-settings.h\n+++ b/fsmonitor-settings.h\n@@ -33,7 +33,7 @@ enum fsmonitor_mode fsm_settings__get_mode(struct repository *r);\n const char *fsm_settings__get_hook_path(struct repository *r);\n \n enum fsmonitor_reason fsm_settings__get_reason(struct repository *r);\n-char *fsm_settings__get_incompatible_msg(const struct repository *r,\n+char *fsm_settings__get_incompatible_msg(struct repository *r,\n \t\t\t\t\t enum fsmonitor_reason reason);\n \n struct fsmonitor_settings;\ndiff --git a/fsmonitor.c b/fsmonitor.c\nindex 57d6a483bee..540736b39fd 100644\n--- a/fsmonitor.c\n+++ b/fsmonitor.c\n@@ -295,6 +295,7 @@ static int fsmonitor_force_update_threshold = 100;\n \n void refresh_fsmonitor(struct index_state *istate)\n {\n+\tstatic int warn_once = 0;\n \tstruct strbuf query_result = STRBUF_INIT;\n \tint query_success = 0, hook_version = -1;\n \tsize_t bol = 0; /* beginning of line */\n@@ -305,6 +306,12 @@ void refresh_fsmonitor(struct index_state *istate)\n \tint is_trivial = 0;\n \tstruct repository *r = istate->repo ? istate->repo : the_repository;\n \tenum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);\n+\tenum fsmonitor_reason reason = fsm_settings__get_reason(r);\n+\n+\tif (!warn_once && reason > FSMONITOR_REASON_OK) {\n+\t\twarn_once = 1;\n+\t\twarning(\"%s\", fsm_settings__get_incompatible_msg(r, reason));\n+\t}\n \n \tif (fsm_mode <= FSMONITOR_MODE_DISABLED ||\n \t    istate->fsmonitor_has_run_once)\n-- \ngitgitgadget\n\n"},{"id":"464449","messageId":"ec49a74086dee4545a3bb7b24c8da22e77aef579.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 01/12] fsmonitor: refactor filesystem checks to common interface","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:27Z","receivedAt":"2022-10-09T14:38:01Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nProvide a common interface for getting basic filesystem information\nincluding filesystem type and whether the filesystem is remote.\n\nRefactor existing code for getting basic filesystem info and detecting\nremote file systems to the new interface.\n\nRefactor filesystem checks to leverage new interface. For macOS,\nerror-out if the Unix Domain socket (UDS) file is on a remote\nfilesystem.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Makefile                                 |   1 +\n compat/fsmonitor/fsm-path-utils-darwin.c |  43 ++++++\n compat/fsmonitor/fsm-path-utils-win32.c  | 128 +++++++++++++++++\n compat/fsmonitor/fsm-settings-darwin.c   |  69 +++------\n compat/fsmonitor/fsm-settings-win32.c    | 172 +----------------------\n contrib/buildsystems/CMakeLists.txt      |   2 +\n fsmonitor-path-utils.h                   |  26 ++++\n fsmonitor-settings.c                     |  50 +++++++\n 8 files changed, 272 insertions(+), 219 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-path-utils-darwin.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-win32.c\n create mode 100644 fsmonitor-path-utils.h\n\ndiff --git a/Makefile b/Makefile\nindex cac3452edb9..ffab427ea5b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2044,6 +2044,7 @@ endif\n ifdef FSMONITOR_OS_SETTINGS\n \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o\n+\tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o\n endif\n \n ifeq ($(TCLTK_PATH),)\ndiff --git a/compat/fsmonitor/fsm-path-utils-darwin.c b/compat/fsmonitor/fsm-path-utils-darwin.c\nnew file mode 100644\nindex 00000000000..d46d7f13538\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-path-utils-darwin.c\n@@ -0,0 +1,43 @@\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-path-utils.h\"\n+#include <sys/param.h>\n+#include <sys/mount.h>\n+\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n+{\n+\tstruct statfs fs;\n+\tif (statfs(path, &fs) == -1) {\n+\t\tint saved_errno = errno;\n+\t\ttrace_printf_key(&trace_fsmonitor, \"statfs('%s') failed: %s\",\n+\t\t\t\t path, strerror(saved_errno));\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t \"statfs('%s') [type 0x%08x][flags 0x%08x] '%s'\",\n+\t\t\t path, fs.f_type, fs.f_flags, fs.f_fstypename);\n+\n+\tif (!(fs.f_flags & MNT_LOCAL))\n+\t\tfs_info->is_remote = 1;\n+\telse\n+\t\tfs_info->is_remote = 0;\n+\n+\tfs_info->typename = xstrdup(fs.f_fstypename);\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"'%s' is_remote: %d\",\n+\t\t\t\tpath, fs_info->is_remote);\n+\treturn 0;\n+}\n+\n+int fsmonitor__is_fs_remote(const char *path)\n+{\n+\tstruct fs_info fs;\n+\tif (fsmonitor__get_fs_info(path, &fs))\n+\t\treturn -1;\n+\n+\tfree(fs.typename);\n+\n+\treturn fs.is_remote;\n+}\ndiff --git a/compat/fsmonitor/fsm-path-utils-win32.c b/compat/fsmonitor/fsm-path-utils-win32.c\nnew file mode 100644\nindex 00000000000..a90b8f7925b\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-path-utils-win32.c\n@@ -0,0 +1,128 @@\n+#include \"cache.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-path-utils.h\"\n+\n+/*\n+ * Check remote working directory protocol.\n+ *\n+ * Return -1 if client machine cannot get remote protocol information.\n+ */\n+static int check_remote_protocol(wchar_t *wpath)\n+{\n+\tHANDLE h;\n+\tFILE_REMOTE_PROTOCOL_INFO proto_info;\n+\n+\th = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,\n+\t\t\tFILE_FLAG_BACKUP_SEMANTICS, NULL);\n+\n+\tif (h == INVALID_HANDLE_VALUE) {\n+\t\terror(_(\"[GLE %ld] unable to open for read '%ls'\"),\n+\t\t      GetLastError(), wpath);\n+\t\treturn -1;\n+\t}\n+\n+\tif (!GetFileInformationByHandleEx(h, FileRemoteProtocolInfo,\n+\t\t&proto_info, sizeof(proto_info))) {\n+\t\terror(_(\"[GLE %ld] unable to get protocol information for '%ls'\"),\n+\t\t      GetLastError(), wpath);\n+\t\tCloseHandle(h);\n+\t\treturn -1;\n+\t}\n+\n+\tCloseHandle(h);\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"check_remote_protocol('%ls') remote protocol %#8.8lx\",\n+\t\t\t\twpath, proto_info.Protocol);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Notes for testing:\n+ *\n+ * (a) Windows allows a network share to be mapped to a drive letter.\n+ *     (This is the normal method to access it.)\n+ *\n+ *     $ NET USE Z: \\\\server\\share\n+ *     $ git -C Z:/repo status\n+ *\n+ * (b) Windows allows a network share to be referenced WITHOUT mapping\n+ *     it to drive letter.\n+ *\n+ *     $ NET USE \\\\server\\share\\dir\n+ *     $ git -C //server/share/repo status\n+ *\n+ * (c) Windows allows \"SUBST\" to create a fake drive mapping to an\n+ *     arbitrary path (which may be remote)\n+ *\n+ *     $ SUBST Q: Z:\\repo\n+ *     $ git -C Q:/ status\n+ *\n+ * (d) Windows allows a directory symlink to be created on a local\n+ *     file system that points to a remote repo.\n+ *\n+ *     $ mklink /d ./link //server/share/repo\n+ *     $ git -C ./link status\n+ */\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n+{\n+\twchar_t wpath[MAX_PATH];\n+\twchar_t wfullpath[MAX_PATH];\n+\tsize_t wlen;\n+\tUINT driveType;\n+\n+\t/*\n+\t * Do everything in wide chars because the drive letter might be\n+\t * a multi-byte sequence.  See win32_has_dos_drive_prefix().\n+\t */\n+\tif (xutftowcs_path(wpath, path) < 0) {\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * GetDriveTypeW() requires a final slash.  We assume that the\n+\t * worktree pathname points to an actual directory.\n+\t */\n+\twlen = wcslen(wpath);\n+\tif (wpath[wlen - 1] != L'\\\\' && wpath[wlen - 1] != L'/') {\n+\t\twpath[wlen++] = L'\\\\';\n+\t\twpath[wlen] = 0;\n+\t}\n+\n+\t/*\n+\t * Normalize the path.  If nothing else, this converts forward\n+\t * slashes to backslashes.  This is essential to get GetDriveTypeW()\n+\t * correctly handle some UNC \"\\\\server\\share\\...\" paths.\n+\t */\n+\tif (!GetFullPathNameW(wpath, MAX_PATH, wfullpath, NULL)) {\n+\t\treturn -1;\n+\t}\n+\n+\tdriveType = GetDriveTypeW(wfullpath);\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t \"DriveType '%s' L'%ls' (%u)\",\n+\t\t\t path, wfullpath, driveType);\n+\n+\tif (driveType == DRIVE_REMOTE) {\n+\t\tfs_info->is_remote = 1;\n+\t\tif (check_remote_protocol(wfullpath) < 0)\n+\t\t\treturn -1;\n+\t} else {\n+\t\tfs_info->is_remote = 0;\n+\t}\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"'%s' is_remote: %d\",\n+\t\t\t\tpath, fs_info->is_remote);\n+\n+\treturn 0;\n+}\n+\n+int fsmonitor__is_fs_remote(const char *path)\n+{\n+\tstruct fs_info fs;\n+\tif (fsmonitor__get_fs_info(path, &fs))\n+\t\treturn -1;\n+\treturn fs.is_remote;\n+}\ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c\nindex efc732c0f31..699f0b272e6 100644\n--- a/compat/fsmonitor/fsm-settings-darwin.c\n+++ b/compat/fsmonitor/fsm-settings-darwin.c\n@@ -1,32 +1,10 @@\n-#include \"cache.h\"\n #include \"config.h\"\n-#include \"repository.h\"\n-#include \"fsmonitor-settings.h\"\n #include \"fsmonitor.h\"\n-#include <sys/param.h>\n-#include <sys/mount.h>\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-settings.h\"\n+#include \"fsmonitor-path-utils.h\"\n \n-/*\n- * [1] Remote working directories are problematic for FSMonitor.\n- *\n- * The underlying file system on the server machine and/or the remote\n- * mount type (NFS, SAMBA, etc.) dictates whether notification events\n- * are available at all to remote client machines.\n- *\n- * Kernel differences between the server and client machines also\n- * dictate the how (buffering, frequency, de-dup) the events are\n- * delivered to client machine processes.\n- *\n- * A client machine (such as a laptop) may choose to suspend/resume\n- * and it is unclear (without lots of testing) whether the watcher can\n- * resync after a resume.  We might be able to treat this as a normal\n- * \"events were dropped by the kernel\" event and do our normal \"flush\n- * and resync\" --or-- we might need to close the existing (zombie?)\n- * notification fd and create a new one.\n- *\n- * In theory, the above issues need to be addressed whether we are\n- * using the Hook or IPC API.\n- *\n+ /*\n  * For the builtin FSMonitor, we create the Unix domain socket for the\n  * IPC in the .git directory.  If the working directory is remote,\n  * then the socket will be created on the remote file system.  This\n@@ -38,42 +16,35 @@\n  * be taken to ensure that $HOME is actually local and not a managed\n  * file share.)\n  *\n- * So (for now at least), mark remote working directories as\n- * incompatible.\n- *\n- *\n- * [2] FAT32 and NTFS working directories are problematic too.\n+ * FAT32 and NTFS working directories are problematic too.\n  *\n  * The builtin FSMonitor uses a Unix domain socket in the .git\n  * directory for IPC.  These Windows drive formats do not support\n  * Unix domain sockets, so mark them as incompatible for the daemon.\n  *\n  */\n-static enum fsmonitor_reason check_volume(struct repository *r)\n+static enum fsmonitor_reason check_uds_volume(struct repository *r)\n {\n-\tstruct statfs fs;\n+\tstruct fs_info fs;\n+\tconst char *ipc_path = fsmonitor_ipc__get_path();\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n \n-\tif (statfs(r->worktree, &fs) == -1) {\n-\t\tint saved_errno = errno;\n-\t\ttrace_printf_key(&trace_fsmonitor, \"statfs('%s') failed: %s\",\n-\t\t\t\t r->worktree, strerror(saved_errno));\n-\t\terrno = saved_errno;\n+\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n+\t\tstrbuf_release(&path);\n \t\treturn FSMONITOR_REASON_ERROR;\n \t}\n \n-\ttrace_printf_key(&trace_fsmonitor,\n-\t\t\t \"statfs('%s') [type 0x%08x][flags 0x%08x] '%s'\",\n-\t\t\t r->worktree, fs.f_type, fs.f_flags, fs.f_fstypename);\n-\n-\tif (!(fs.f_flags & MNT_LOCAL))\n-\t\treturn FSMONITOR_REASON_REMOTE;\n+\tstrbuf_release(&path);\n \n-\tif (!strcmp(fs.f_fstypename, \"msdos\")) /* aka FAT32 */\n-\t\treturn FSMONITOR_REASON_NOSOCKETS;\n-\n-\tif (!strcmp(fs.f_fstypename, \"ntfs\"))\n+\tif (fs.is_remote ||\n+\t\t!strcmp(fs.typename, \"msdos\") ||\n+\t\t!strcmp(fs.typename, \"ntfs\")) {\n+\t\tfree(fs.typename);\n \t\treturn FSMONITOR_REASON_NOSOCKETS;\n+\t}\n \n+\tfree(fs.typename);\n \treturn FSMONITOR_REASON_OK;\n }\n \n@@ -81,7 +52,7 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r)\n {\n \tenum fsmonitor_reason reason;\n \n-\treason = check_volume(r);\n+\treason = check_uds_volume(r);\n \tif (reason != FSMONITOR_REASON_OK)\n \t\treturn reason;\n \ndiff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c\nindex e5ec5b0a9f7..d88b06ae610 100644\n--- a/compat/fsmonitor/fsm-settings-win32.c\n+++ b/compat/fsmonitor/fsm-settings-win32.c\n@@ -1,8 +1,9 @@\n #include \"cache.h\"\n #include \"config.h\"\n #include \"repository.h\"\n-#include \"fsmonitor-settings.h\"\n #include \"fsmonitor.h\"\n+#include \"fsmonitor-settings.h\"\n+#include \"fsmonitor-path-utils.h\"\n \n /*\n  * VFS for Git is incompatible with FSMonitor.\n@@ -24,171 +25,6 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r)\n \treturn FSMONITOR_REASON_OK;\n }\n \n-/*\n- * Check if monitoring remote working directories is allowed.\n- *\n- * By default, monitoring remote working directories is\n- * disabled.  Users may override this behavior in enviroments where\n- * they have proper support.\n- */\n-static int check_config_allowremote(struct repository *r)\n-{\n-\tint allow;\n-\n-\tif (!repo_config_get_bool(r, \"fsmonitor.allowremote\", &allow))\n-\t\treturn allow;\n-\n-\treturn -1; /* fsmonitor.allowremote not set */\n-}\n-\n-/*\n- * Check remote working directory protocol.\n- *\n- * Error if client machine cannot get remote protocol information.\n- */\n-static int check_remote_protocol(wchar_t *wpath)\n-{\n-\tHANDLE h;\n-\tFILE_REMOTE_PROTOCOL_INFO proto_info;\n-\n-\th = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,\n-\t\t\tFILE_FLAG_BACKUP_SEMANTICS, NULL);\n-\n-\tif (h == INVALID_HANDLE_VALUE) {\n-\t\terror(_(\"[GLE %ld] unable to open for read '%ls'\"),\n-\t\t      GetLastError(), wpath);\n-\t\treturn -1;\n-\t}\n-\n-\tif (!GetFileInformationByHandleEx(h, FileRemoteProtocolInfo,\n-\t\t&proto_info, sizeof(proto_info))) {\n-\t\terror(_(\"[GLE %ld] unable to get protocol information for '%ls'\"),\n-\t\t      GetLastError(), wpath);\n-\t\tCloseHandle(h);\n-\t\treturn -1;\n-\t}\n-\n-\tCloseHandle(h);\n-\n-\ttrace_printf_key(&trace_fsmonitor,\n-\t\t\t\t\"check_remote_protocol('%ls') remote protocol %#8.8lx\",\n-\t\t\t\twpath, proto_info.Protocol);\n-\n-\treturn 0;\n-}\n-\n-/*\n- * Remote working directories are problematic for FSMonitor.\n- *\n- * The underlying file system on the server machine and/or the remote\n- * mount type dictates whether notification events are available at\n- * all to remote client machines.\n- *\n- * Kernel differences between the server and client machines also\n- * dictate the how (buffering, frequency, de-dup) the events are\n- * delivered to client machine processes.\n- *\n- * A client machine (such as a laptop) may choose to suspend/resume\n- * and it is unclear (without lots of testing) whether the watcher can\n- * resync after a resume.  We might be able to treat this as a normal\n- * \"events were dropped by the kernel\" event and do our normal \"flush\n- * and resync\" --or-- we might need to close the existing (zombie?)\n- * notification fd and create a new one.\n- *\n- * In theory, the above issues need to be addressed whether we are\n- * using the Hook or IPC API.\n- *\n- * So (for now at least), mark remote working directories as\n- * incompatible.\n- *\n- * Notes for testing:\n- *\n- * (a) Windows allows a network share to be mapped to a drive letter.\n- *     (This is the normal method to access it.)\n- *\n- *     $ NET USE Z: \\\\server\\share\n- *     $ git -C Z:/repo status\n- *\n- * (b) Windows allows a network share to be referenced WITHOUT mapping\n- *     it to drive letter.\n- *\n- *     $ NET USE \\\\server\\share\\dir\n- *     $ git -C //server/share/repo status\n- *\n- * (c) Windows allows \"SUBST\" to create a fake drive mapping to an\n- *     arbitrary path (which may be remote)\n- *\n- *     $ SUBST Q: Z:\\repo\n- *     $ git -C Q:/ status\n- *\n- * (d) Windows allows a directory symlink to be created on a local\n- *     file system that points to a remote repo.\n- *\n- *     $ mklink /d ./link //server/share/repo\n- *     $ git -C ./link status\n- */\n-static enum fsmonitor_reason check_remote(struct repository *r)\n-{\n-\tint ret;\n-\twchar_t wpath[MAX_PATH];\n-\twchar_t wfullpath[MAX_PATH];\n-\tsize_t wlen;\n-\tUINT driveType;\n-\n-\t/*\n-\t * Do everything in wide chars because the drive letter might be\n-\t * a multi-byte sequence.  See win32_has_dos_drive_prefix().\n-\t */\n-\tif (xutftowcs_path(wpath, r->worktree) < 0)\n-\t\treturn FSMONITOR_REASON_ERROR;\n-\n-\t/*\n-\t * GetDriveTypeW() requires a final slash.  We assume that the\n-\t * worktree pathname points to an actual directory.\n-\t */\n-\twlen = wcslen(wpath);\n-\tif (wpath[wlen - 1] != L'\\\\' && wpath[wlen - 1] != L'/') {\n-\t\twpath[wlen++] = L'\\\\';\n-\t\twpath[wlen] = 0;\n-\t}\n-\n-\t/*\n-\t * Normalize the path.  If nothing else, this converts forward\n-\t * slashes to backslashes.  This is essential to get GetDriveTypeW()\n-\t * correctly handle some UNC \"\\\\server\\share\\...\" paths.\n-\t */\n-\tif (!GetFullPathNameW(wpath, MAX_PATH, wfullpath, NULL))\n-\t\treturn FSMONITOR_REASON_ERROR;\n-\n-\tdriveType = GetDriveTypeW(wfullpath);\n-\ttrace_printf_key(&trace_fsmonitor,\n-\t\t\t \"DriveType '%s' L'%ls' (%u)\",\n-\t\t\t r->worktree, wfullpath, driveType);\n-\n-\tif (driveType == DRIVE_REMOTE) {\n-\t\ttrace_printf_key(&trace_fsmonitor,\n-\t\t\t\t \"check_remote('%s') true\",\n-\t\t\t\t r->worktree);\n-\n-\t\tret = check_remote_protocol(wfullpath);\n-\t\tif (ret < 0)\n-\t\t\treturn FSMONITOR_REASON_ERROR;\n-\n-\t\tswitch (check_config_allowremote(r)) {\n-\t\tcase 0: /* config overrides and disables */\n-\t\t\treturn FSMONITOR_REASON_REMOTE;\n-\t\tcase 1: /* config overrides and enables */\n-\t\t\treturn FSMONITOR_REASON_OK;\n-\t\tdefault:\n-\t\t\tbreak; /* config has no opinion */\n-\t\t}\n-\n-\t\treturn FSMONITOR_REASON_REMOTE;\n-\t}\n-\n-\treturn FSMONITOR_REASON_OK;\n-}\n-\n enum fsmonitor_reason fsm_os__incompatible(struct repository *r)\n {\n \tenum fsmonitor_reason reason;\n@@ -197,9 +33,5 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r)\n \tif (reason != FSMONITOR_REASON_OK)\n \t\treturn reason;\n \n-\treason = check_remote(r);\n-\tif (reason != FSMONITOR_REASON_OK)\n-\t\treturn reason;\n-\n \treturn FSMONITOR_REASON_OK;\n }\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex ea2a531be87..5482a04b3ce 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -308,6 +308,7 @@ if(SUPPORTS_SIMPLE_IPC)\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-win32.c)\n \n \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c)\n@@ -315,6 +316,7 @@ if(SUPPORTS_SIMPLE_IPC)\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c)\n \n \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)\ndiff --git a/fsmonitor-path-utils.h b/fsmonitor-path-utils.h\nnew file mode 100644\nindex 00000000000..41edf5b934f\n--- /dev/null\n+++ b/fsmonitor-path-utils.h\n@@ -0,0 +1,26 @@\n+#ifndef FSM_PATH_UTILS_H\n+#define FSM_PATH_UTILS_H\n+\n+struct fs_info {\n+\tint is_remote;\n+\tchar *typename;\n+};\n+\n+/*\n+ * Get some basic filesystem informtion for the given path\n+ *\n+ * The caller owns the storage that is occupied by fs_info and\n+ * is responsible for releasing it.\n+ *\n+ * Returns -1 on error, zero otherwise.\n+ */\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info);\n+\n+/*\n+ * Determines if the filesystem that path resides on is remote.\n+ *\n+ * Returns -1 on error, 0 if not remote, 1 if remote.\n+ */\n+int fsmonitor__is_fs_remote(const char *path);\n+\n+#endif\ndiff --git a/fsmonitor-settings.c b/fsmonitor-settings.c\nindex 464424a1e92..d288cbad479 100644\n--- a/fsmonitor-settings.c\n+++ b/fsmonitor-settings.c\n@@ -2,6 +2,7 @@\n #include \"config.h\"\n #include \"repository.h\"\n #include \"fsmonitor-settings.h\"\n+#include \"fsmonitor-path-utils.h\"\n \n /*\n  * We keep this structure defintion private and have getters\n@@ -13,6 +14,52 @@ struct fsmonitor_settings {\n \tchar *hook_path;\n };\n \n+/*\n+ * Remote working directories are problematic for FSMonitor.\n+ *\n+ * The underlying file system on the server machine and/or the remote\n+ * mount type dictates whether notification events are available at\n+ * all to remote client machines.\n+ *\n+ * Kernel differences between the server and client machines also\n+ * dictate the how (buffering, frequency, de-dup) the events are\n+ * delivered to client machine processes.\n+ *\n+ * A client machine (such as a laptop) may choose to suspend/resume\n+ * and it is unclear (without lots of testing) whether the watcher can\n+ * resync after a resume.  We might be able to treat this as a normal\n+ * \"events were dropped by the kernel\" event and do our normal \"flush\n+ * and resync\" --or-- we might need to close the existing (zombie?)\n+ * notification fd and create a new one.\n+ *\n+ * In theory, the above issues need to be addressed whether we are\n+ * using the Hook or IPC API.\n+ *\n+ * So (for now at least), mark remote working directories as\n+ * incompatible unless 'fsmonitor.allowRemote' is true.\n+ *\n+ */\n+#ifdef HAVE_FSMONITOR_OS_SETTINGS\n+static enum fsmonitor_reason check_remote(struct repository *r)\n+{\n+\tint allow_remote = -1; /* -1 unset, 0 not allowed, 1 allowed */\n+\tint is_remote = fsmonitor__is_fs_remote(r->worktree);\n+\n+\tswitch (is_remote) {\n+\t\tcase 0:\n+\t\t\treturn FSMONITOR_REASON_OK;\n+\t\tcase 1:\n+\t\t\trepo_config_get_bool(r, \"fsmonitor.allowremote\", &allow_remote);\n+\t\t\tif (allow_remote < 1)\n+\t\t\t\treturn FSMONITOR_REASON_REMOTE;\n+\t\t\telse\n+\t\t\t\treturn FSMONITOR_REASON_OK;\n+\t\tdefault:\n+\t\t\treturn FSMONITOR_REASON_ERROR;\n+\t}\n+}\n+#endif\n+\n static enum fsmonitor_reason check_for_incompatible(struct repository *r)\n {\n \tif (!r->worktree) {\n@@ -27,6 +74,9 @@ static enum fsmonitor_reason check_for_incompatible(struct repository *r)\n \t{\n \t\tenum fsmonitor_reason reason;\n \n+\t\treason = check_remote(r);\n+\t\tif (reason != FSMONITOR_REASON_OK)\n+\t\t\treturn reason;\n \t\treason = fsm_os__incompatible(r);\n \t\tif (reason != FSMONITOR_REASON_OK)\n \t\t\treturn reason;\n-- \ngitgitgadget\n\n"},{"id":"464450","messageId":"af7309745f759532fdb79794289d9e02de0e035c.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 06/12] fsmonitor: add documentation for allowRemote and socketDir options","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:32Z","receivedAt":"2022-10-09T14:38:03Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nAdd documentation for 'fsmonitor.allowRemote' and 'fsmonitor.socketDir'.\nCall-out experimental nature of 'fsmonitor.allowRemote' and limited\nfilesystem support for 'fsmonitor.socketDir'.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Documentation/config.txt                   |  2 ++\n Documentation/config/fsmonitor--daemon.txt | 11 +++++++\n Documentation/git-fsmonitor--daemon.txt    | 37 ++++++++++++++++++++--\n 3 files changed, 47 insertions(+), 3 deletions(-)\n create mode 100644 Documentation/config/fsmonitor--daemon.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 5b5b9765699..1e205831656 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -423,6 +423,8 @@ include::config/filter.txt[]\n \n include::config/fsck.txt[]\n \n+include::config/fsmonitor--daemon.txt[]\n+\n include::config/gc.txt[]\n \n include::config/gitcvs.txt[]\ndiff --git a/Documentation/config/fsmonitor--daemon.txt b/Documentation/config/fsmonitor--daemon.txt\nnew file mode 100644\nindex 00000000000..c225c6c9e74\n--- /dev/null\n+++ b/Documentation/config/fsmonitor--daemon.txt\n@@ -0,0 +1,11 @@\n+fsmonitor.allowRemote::\n+    By default, the fsmonitor daemon refuses to work against network-mounted\n+    repositories. Setting `fsmonitor.allowRemote` to `true` overrides this\n+    behavior.  Only respected when `core.fsmonitor` is set to `true`.\n+\n+fsmonitor.socketDir::\n+    This Mac OS-specific option, if set, specifies the directory in\n+    which to create the Unix domain socket used for communication\n+    between the fsmonitor daemon and various Git commands. The directory must\n+    reside on a native Mac OS filesystem.  Only respected when `core.fsmonitor`\n+    is set to `true`.\ndiff --git a/Documentation/git-fsmonitor--daemon.txt b/Documentation/git-fsmonitor--daemon.txt\nindex cc142fb8612..8238eadb0e1 100644\n--- a/Documentation/git-fsmonitor--daemon.txt\n+++ b/Documentation/git-fsmonitor--daemon.txt\n@@ -3,7 +3,7 @@ git-fsmonitor{litdd}daemon(1)\n \n NAME\n ----\n-git-fsmonitor--daemon - A Built-in File System Monitor\n+git-fsmonitor--daemon - A Built-in Filesystem Monitor\n \n SYNOPSIS\n --------\n@@ -17,7 +17,7 @@ DESCRIPTION\n -----------\n \n A daemon to watch the working directory for file and directory\n-changes using platform-specific file system notification facilities.\n+changes using platform-specific filesystem notification facilities.\n \n This daemon communicates directly with commands like `git status`\n using the link:technical/api-simple-ipc.html[simple IPC] interface\n@@ -63,13 +63,44 @@ CAVEATS\n -------\n \n The fsmonitor daemon does not currently know about submodules and does\n-not know to filter out file system events that happen within a\n+not know to filter out filesystem events that happen within a\n submodule.  If fsmonitor daemon is watching a super repo and a file is\n modified within the working directory of a submodule, it will report\n the change (as happening against the super repo).  However, the client\n will properly ignore these extra events, so performance may be affected\n but it will not cause an incorrect result.\n \n+By default, the fsmonitor daemon refuses to work against network-mounted\n+repositories; this may be overridden by setting `fsmonitor.allowRemote` to\n+`true`. Note, however, that the fsmonitor daemon is not guaranteed to work\n+correctly with all network-mounted repositories and such use is considered\n+experimental.\n+\n+On Mac OS, the inter-process communication (IPC) between various Git\n+commands and the fsmonitor daemon is done via a Unix domain socket (UDS) -- a\n+special type of file -- which is supported by native Mac OS filesystems,\n+but not on network-mounted filesystems, NTFS, or FAT32.  Other filesystems\n+may or may not have the needed support; the fsmonitor daemon is not guaranteed\n+to work with these filesystems and such use is considered experimental.\n+\n+By default, the socket is created in the `.git` directory, however, if the\n+`.git` directory is on a network-mounted filesystem, it will be instead be\n+created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a\n+network-mounted filesystem in which case you must set the configuration\n+variable `fsmonitor.socketDir` to the path of a directory on a Mac OS native\n+filesystem in which to create the socket file.\n+\n+If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`)\n+is on a native Mac OS file filesystem the fsmonitor daemon will report an\n+error that will cause the daemon and the currently running command to exit.\n+\n+CONFIGURATION\n+-------------\n+\n+include::includes/cmd-config-section-all.txt[]\n+\n+include::config/fsmonitor--daemon.txt[]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n\n"},{"id":"464451","messageId":"c085fc15b314abcb5e5ca6b4ee5ac54a28327cab.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 07/12] fsmonitor: prepare to share code between Mac OS and Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:33Z","receivedAt":"2022-10-09T14:38:04Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nLinux and Mac OS can share some of the code originally developed for Mac OS.\nRename the shared code from compat/fsmonitor/fsm-*-dawrin.c to\ncompat/fsmonitor/fsm-*-unix.c\n\nUpdate the build to enable sharing of the fsm-*-unix.c files.\n\nMinor update to compat/fsmonitor/fsm-ipc-unix.c to make it cross-platform.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Makefile                                                  | 6 +++---\n .../fsmonitor/{fsm-health-darwin.c => fsm-health-unix.c}  | 0\n compat/fsmonitor/{fsm-ipc-darwin.c => fsm-ipc-unix.c}     | 8 ++++----\n .../{fsm-settings-darwin.c => fsm-settings-unix.c}        | 0\n config.mak.uname                                          | 4 ++++\n contrib/buildsystems/CMakeLists.txt                       | 6 +++---\n 6 files changed, 14 insertions(+), 10 deletions(-)\n rename compat/fsmonitor/{fsm-health-darwin.c => fsm-health-unix.c} (100%)\n rename compat/fsmonitor/{fsm-ipc-darwin.c => fsm-ipc-unix.c} (89%)\n rename compat/fsmonitor/{fsm-settings-darwin.c => fsm-settings-unix.c} (100%)\n\ndiff --git a/Makefile b/Makefile\nindex feb675a6959..31dd6ab2734 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2038,13 +2038,13 @@ endif\n ifdef FSMONITOR_DAEMON_BACKEND\n \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o\n-\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o\n-\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n+\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_COMMON).o\n+\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_COMMON).o\n endif\n \n ifdef FSMONITOR_OS_SETTINGS\n \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n-\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o\n+\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_COMMON).o\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o\n endif\n \ndiff --git a/compat/fsmonitor/fsm-health-darwin.c b/compat/fsmonitor/fsm-health-unix.c\nsimilarity index 100%\nrename from compat/fsmonitor/fsm-health-darwin.c\nrename to compat/fsmonitor/fsm-health-unix.c\ndiff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-unix.c\nsimilarity index 89%\nrename from compat/fsmonitor/fsm-ipc-darwin.c\nrename to compat/fsmonitor/fsm-ipc-unix.c\nindex ce843d63348..3ba3b9e17ed 100644\n--- a/compat/fsmonitor/fsm-ipc-darwin.c\n+++ b/compat/fsmonitor/fsm-ipc-unix.c\n@@ -10,7 +10,7 @@ static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n const char *fsmonitor_ipc__get_path(struct repository *r)\n {\n \tstatic const char *ipc_path = NULL;\n-\tSHA_CTX sha1ctx;\n+\tgit_SHA_CTX sha1ctx;\n \tchar *sock_dir = NULL;\n \tstruct strbuf ipc_file = STRBUF_INIT;\n \tunsigned char hash[SHA_DIGEST_LENGTH];\n@@ -28,9 +28,9 @@ const char *fsmonitor_ipc__get_path(struct repository *r)\n \t\treturn ipc_path;\n \t}\n \n-\tSHA1_Init(&sha1ctx);\n-\tSHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n-\tSHA1_Final(hash, &sha1ctx);\n+\tgit_SHA1_Init(&sha1ctx);\n+\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n+\tgit_SHA1_Final(hash, &sha1ctx);\n \n \trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n \ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-unix.c\nsimilarity index 100%\nrename from compat/fsmonitor/fsm-settings-darwin.c\nrename to compat/fsmonitor/fsm-settings-unix.c\ndiff --git a/config.mak.uname b/config.mak.uname\nindex d63629fe807..d454cec47c4 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -68,6 +68,7 @@ ifeq ($(uname_S),Linux)\n \tifneq ($(findstring .el7.,$(uname_R)),)\n \t\tBASIC_CFLAGS += -std=c99\n \tendif\n+\n endif\n ifeq ($(uname_S),GNU/kFreeBSD)\n \tHAVE_ALLOCA_H = YesPlease\n@@ -165,6 +166,7 @@ ifeq ($(uname_S),Darwin)\n \tifndef NO_UNIX_SOCKETS\n \tFSMONITOR_DAEMON_BACKEND = darwin\n \tFSMONITOR_OS_SETTINGS = darwin\n+\tFSMONITOR_DAEMON_COMMON = unix\n \tendif\n \tendif\n \n@@ -453,6 +455,7 @@ ifeq ($(uname_S),Windows)\n \t# support it.\n \tFSMONITOR_DAEMON_BACKEND = win32\n \tFSMONITOR_OS_SETTINGS = win32\n+\tFSMONITOR_DAEMON_COMMON = win32\n \n \tNO_SVN_TESTS = YesPlease\n \tRUNTIME_PREFIX = YesPlease\n@@ -645,6 +648,7 @@ ifeq ($(uname_S),MINGW)\n \t# support it.\n \tFSMONITOR_DAEMON_BACKEND = win32\n \tFSMONITOR_OS_SETTINGS = win32\n+\tFSMONITOR_DAEMON_COMMON = win32\n \n \tRUNTIME_PREFIX = YesPlease\n \tHAVE_WPGMPTR = YesWeDo\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 787738e6fa3..0b26a1a36e3 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -315,13 +315,13 @@ if(SUPPORTS_SIMPLE_IPC)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c)\n \telseif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-unix.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-unix.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n-\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)\n-\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-darwin.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c)\n \n \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n-\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-unix.c)\n \tendif()\n endif()\n \n-- \ngitgitgadget\n\n"},{"id":"464452","messageId":"5ecbb3082f16956d049cfa98662f8e3384a6aea2.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 08/12] fsmonitor: determine if filesystem is local or remote","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:34Z","receivedAt":"2022-10-09T14:38:08Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nCompare the given path to the mounted filesystems. Find the mount that is\nthe longest prefix of the path (if any) and determine if that mount is on a\nlocal or remote filesystem.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-path-utils-linux.c | 163 ++++++++++++++++++++++++\n 1 file changed, 163 insertions(+)\n create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n\ndiff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c\nnew file mode 100644\nindex 00000000000..369692a788f\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-path-utils-linux.c\n@@ -0,0 +1,163 @@\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-path-utils.h\"\n+#include <errno.h>\n+#include <mntent.h>\n+#include <sys/mount.h>\n+#include <sys/statvfs.h>\n+\n+/*\n+ * https://github.com/coreutils/gnulib/blob/master/lib/mountlist.c\n+ */\n+#ifndef ME_REMOTE\n+/* A file system is \"remote\" if its Fs_name contains a ':'\n+   or if (it is of type (smbfs or cifs) and its Fs_name starts with '//')\n+   or if it is of any other of the listed types\n+   or Fs_name is equal to \"-hosts\" (used by autofs to mount remote fs).\n+   \"VM\" file systems like prl_fs or vboxsf are not considered remote here. */\n+# define ME_REMOTE(Fs_name, Fs_type)            \\\n+\t(strchr (Fs_name, ':') != NULL              \\\n+\t || ((Fs_name)[0] == '/'                    \\\n+\t\t && (Fs_name)[1] == '/'                 \\\n+\t\t && (strcmp (Fs_type, \"smbfs\") == 0     \\\n+\t\t\t || strcmp (Fs_type, \"smb3\") == 0   \\\n+\t\t\t || strcmp (Fs_type, \"cifs\") == 0)) \\\n+\t || strcmp (Fs_type, \"acfs\") == 0           \\\n+\t || strcmp (Fs_type, \"afs\") == 0            \\\n+\t || strcmp (Fs_type, \"coda\") == 0           \\\n+\t || strcmp (Fs_type, \"auristorfs\") == 0     \\\n+\t || strcmp (Fs_type, \"fhgfs\") == 0          \\\n+\t || strcmp (Fs_type, \"gpfs\") == 0           \\\n+\t || strcmp (Fs_type, \"ibrix\") == 0          \\\n+\t || strcmp (Fs_type, \"ocfs2\") == 0          \\\n+\t || strcmp (Fs_type, \"vxfs\") == 0           \\\n+\t || strcmp (\"-hosts\", Fs_name) == 0)\n+#endif\n+\n+static struct mntent *find_mount(const char *path, const struct statvfs *fs)\n+{\n+\tconst char *const mounts = \"/proc/mounts\";\n+\tconst char *rp = real_pathdup(path, 1);\n+\tstruct mntent *ment = NULL;\n+\tstruct mntent *found = NULL;\n+\tstruct statvfs mntfs;\n+\tFILE *fp;\n+\tint dlen, plen, flen = 0;\n+\n+\tfp = setmntent(mounts, \"r\");\n+\tif (!fp) {\n+\t\terror_errno(_(\"setmntent('%s') failed\"), mounts);\n+\t\treturn NULL;\n+\t}\n+\n+\tplen = strlen(rp);\n+\n+\t/* read all the mount information and compare to path */\n+\twhile ((ment = getmntent(fp)) != NULL) {\n+\t\tif (statvfs(ment->mnt_dir, &mntfs)) {\n+\t\t\tswitch (errno) {\n+\t\t\tcase EPERM:\n+\t\t\tcase ESRCH:\n+\t\t\tcase EACCES:\n+\t\t\t\tcontinue;\n+\t\t\tdefault:\n+\t\t\t\terror_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n+\t\t\t\tendmntent(fp);\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* is mount on the same filesystem and is a prefix of the path */\n+\t\tif ((fs->f_fsid == mntfs.f_fsid) &&\n+\t\t\t!strncmp(ment->mnt_dir, rp, strlen(ment->mnt_dir))) {\n+\t\t\tdlen = strlen(ment->mnt_dir);\n+\t\t\tif (dlen > plen)\n+\t\t\t\tcontinue;\n+\t\t\t/*\n+\t\t\t * root is always a potential match; otherwise look for\n+\t\t\t * directory prefix\n+\t\t\t */\n+\t\t\tif ((dlen == 1 && ment->mnt_dir[0] == '/') ||\n+\t\t\t\t(dlen > flen && (!rp[dlen] || rp[dlen] == '/'))) {\n+\t\t\t\tflen = dlen;\n+\t\t\t\t/*\n+\t\t\t\t * https://man7.org/linux/man-pages/man3/getmntent.3.html\n+\t\t\t\t *\n+\t\t\t\t * The pointer points to a static area of memory which is\n+\t\t\t\t * overwritten by subsequent calls to getmntent().\n+\t\t\t\t */\n+\t\t\t\tif (!found)\n+\t\t\t\t\tCALLOC_ARRAY(found, 1);\n+\t\t\t\tfree(found->mnt_dir);\n+\t\t\t\tfree(found->mnt_type);\n+\t\t\t\tfree(found->mnt_fsname);\n+\t\t\t\tfound->mnt_dir = xmemdupz(ment->mnt_dir, strlen(ment->mnt_dir));\n+\t\t\t\tfound->mnt_type = xmemdupz(ment->mnt_type, strlen(ment->mnt_type));\n+\t\t\t\tfound->mnt_fsname = xmemdupz(ment->mnt_fsname, strlen(ment->mnt_fsname));\n+\t\t\t}\n+\t\t}\n+\t}\n+\tendmntent(fp);\n+\n+\treturn found;\n+}\n+\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n+{\n+\tstruct mntent *ment;\n+\tstruct statvfs fs;\n+\n+\tif (statvfs(path, &fs))\n+\t\treturn error_errno(_(\"statvfs('%s') failed\"), path);\n+\n+\tment = find_mount(path, &fs);\n+\tif (!ment)\n+\t\treturn -1;\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n+\t\t\t path, fs.f_flag, ment->mnt_type, ment->mnt_fsname);\n+\n+\tif (ME_REMOTE(ment->mnt_fsname, ment->mnt_type))\n+\t\tfs_info->is_remote = 1;\n+\telse\n+\t\tfs_info->is_remote = 0;\n+\n+\tfs_info->typename = ment->mnt_fsname;\n+\tfree(ment->mnt_dir);\n+\tfree(ment->mnt_type);\n+\tfree(ment);\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"'%s' is_remote: %d\",\n+\t\t\t\tpath, fs_info->is_remote);\n+\treturn 0;\n+}\n+\n+int fsmonitor__is_fs_remote(const char *path)\n+{\n+\tstruct fs_info fs;\n+\n+\tif (fsmonitor__get_fs_info(path, &fs))\n+\t\treturn -1;\n+\n+\tfree(fs.typename);\n+\n+\treturn fs.is_remote;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info)\n+{\n+\treturn 0;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info)\n+{\n+\treturn NULL;\n+}\n-- \ngitgitgadget\n\n"},{"id":"464453","messageId":"7465fe7a8b4c0e3c3ac7cf99581914f83b13cb19.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 09/12] fsmonitor: implement filesystem change listener for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:35Z","receivedAt":"2022-10-09T14:38:10Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nImplement a filesystem change listener for Linux based on the inotify API:\nhttps://man7.org/linux/man-pages/man7/inotify.7.html\n\ninotify requires registering a watch on every directory in the worktree and\nspecial handling of moves/renames.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-listen-linux.c | 664 ++++++++++++++++++++++++++++\n 1 file changed, 664 insertions(+)\n create mode 100644 compat/fsmonitor/fsm-listen-linux.c\n\ndiff --git a/compat/fsmonitor/fsm-listen-linux.c b/compat/fsmonitor/fsm-listen-linux.c\nnew file mode 100644\nindex 00000000000..244832fbec7\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-listen-linux.c\n@@ -0,0 +1,664 @@\n+#include \"cache.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsm-listen.h\"\n+#include \"fsmonitor--daemon.h\"\n+#include <dirent.h>\n+#include <fcntl.h>\n+#include <sys/inotify.h>\n+#include <sys/stat.h>\n+\n+/*\n+ * Safe value to bitwise OR with rest of mask for\n+ * kernels that do not support IN_MASK_CREATE\n+ */\n+#ifndef IN_MASK_CREATE\n+#define IN_MASK_CREATE 0x00000000\n+#endif\n+\n+enum shutdown_reason {\n+\tSHUTDOWN_CONTINUE = 0,\n+\tSHUTDOWN_STOP,\n+\tSHUTDOWN_ERROR,\n+\tSHUTDOWN_FORCE\n+};\n+\n+struct watch_entry {\n+\tstruct hashmap_entry ent;\n+\tint wd;\n+\tuint32_t cookie;\n+\tconst char *dir;\n+};\n+\n+struct rename_entry {\n+\tstruct hashmap_entry ent;\n+\ttime_t whence;\n+\tuint32_t cookie;\n+\tconst char *dir;\n+};\n+\n+struct fsm_listen_data {\n+\tint fd_inotify;\n+\tenum shutdown_reason shutdown;\n+\tstruct hashmap watches;\n+\tstruct hashmap renames;\n+\tstruct hashmap revwatches;\n+};\n+\n+static int watch_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct watch_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct watch_entry, ent);\n+\te2 = container_of(eptr, const struct watch_entry, ent);\n+\treturn e1->wd != e2->wd;\n+}\n+\n+static int revwatches_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct watch_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct watch_entry, ent);\n+\te2 = container_of(eptr, const struct watch_entry, ent);\n+\treturn strcmp(e1->dir, e2->dir);\n+}\n+\n+static int rename_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct rename_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct rename_entry, ent);\n+\te2 = container_of(eptr, const struct rename_entry, ent);\n+\treturn e1->cookie != e2->cookie;\n+}\n+\n+/*\n+ * Register an inotify watch, add watch descriptor to path mapping\n+ * and the reverse mapping.\n+ */\n+static int add_watch(const char *path, struct fsm_listen_data *data)\n+{\n+\tconst char *interned = strintern(path);\n+\tstruct watch_entry *w1, *w2;\n+\n+\t/* add the inotify watch, don't allow watches to be modified */\n+\tint wd = inotify_add_watch(data->fd_inotify, interned,\n+\t\t\t\t(IN_ALL_EVENTS | IN_ONLYDIR | IN_MASK_CREATE)\n+\t\t\t\t^ IN_ACCESS ^ IN_CLOSE ^ IN_OPEN);\n+\tif (wd < 0)\n+\t\treturn error_errno(\"inotify_add_watch('%s') failed\", interned);\n+\n+\t/* add watch descriptor -> directory mapping */\n+\tCALLOC_ARRAY(w1, 1);\n+\tw1->wd = wd;\n+\tw1->dir = interned;\n+\thashmap_entry_init(&w1->ent, memhash(&w1->wd, sizeof(int)));\n+\thashmap_add(&data->watches, &w1->ent);\n+\n+\t/* add directory -> watch descriptor mapping */\n+\tCALLOC_ARRAY(w2, 1);\n+\tw2->wd = wd;\n+\tw2->dir = interned;\n+\thashmap_entry_init(&w2->ent, memhash(w2->dir, strlen(w2->dir)));\n+\thashmap_add(&data->revwatches, &w2->ent);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove the inotify watch, the watch descriptor to path mapping\n+ * and the reverse mapping.\n+ */\n+static void remove_watch(struct watch_entry *w,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct watch_entry k1, k2, *w1, *w2;\n+\n+\t/* remove watch, ignore error if kernel already did it */\n+\tif (inotify_rm_watch(data->fd_inotify, w->wd) && errno != EINVAL)\n+\t\terror_errno(\"inotify_rm_watch() failed\");\n+\n+\thashmap_entry_init(&k1.ent, memhash(&w->wd, sizeof(int)));\n+\tw1 = hashmap_remove_entry(&data->watches, &k1, ent, NULL);\n+\tif (!w1)\n+\t\tBUG(\"Double remove of watch for '%s'\", w->dir);\n+\n+\tif (w1->cookie)\n+\t\tBUG(\"Removing watch for '%s' which has a pending rename\", w1->dir);\n+\n+\thashmap_entry_init(&k2.ent, memhash(w->dir, strlen(w->dir)));\n+\tw2 = hashmap_remove_entry(&data->revwatches, &k2, ent, NULL);\n+\tif (!w2)\n+\t\tBUG(\"Double remove of reverse watch for '%s'\", w->dir);\n+\n+\t/* w1->dir and w2->dir are interned strings, we don't own them */\n+\tfree(w1);\n+\tfree(w2);\n+}\n+\n+/*\n+ * Check for stale directory renames.\n+ *\n+ * https://man7.org/linux/man-pages/man7/inotify.7.html\n+ *\n+ * Allow for some small timeout to account for the fact that insertion of the\n+ * IN_MOVED_FROM+IN_MOVED_TO event pair is not atomic, and the possibility that\n+ * there may not be any IN_MOVED_TO event.\n+ *\n+ * If the IN_MOVED_TO event is not received within the timeout then events have\n+ * been missed and the monitor is in an inconsistent state with respect to the\n+ * filesystem.\n+ */\n+static int check_stale_dir_renames(struct hashmap *renames, time_t max_age)\n+{\n+\tstruct rename_entry *re;\n+\tstruct hashmap_iter iter;\n+\n+\thashmap_for_each_entry(renames, &iter, re, ent) {\n+\t\tif (re->whence <= max_age)\n+\t\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Track pending renames.\n+ *\n+ * Tracking is done via a event cookie to watch descriptor mapping.\n+ *\n+ * A rename is not complete until matching a IN_MOVED_TO event is received\n+ * for a corresponding IN_MOVED_FROM event.\n+ */\n+static void add_dir_rename(uint32_t cookie, const char *path,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct watch_entry k, *w;\n+\tstruct rename_entry *re;\n+\n+\t/* lookup the watch descriptor for the given path */\n+\thashmap_entry_init(&k.ent, memhash(path, strlen(path)));\n+\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n+\tif (!w) /* should never happen */\n+\t\tBUG(\"No watch for '%s'\", path);\n+\tw->cookie = cookie;\n+\n+\t/* add the pending rename to match against later */\n+\tCALLOC_ARRAY(re, 1);\n+\tre->dir = w->dir;\n+\tre->cookie = w->cookie;\n+\tre->whence = time(NULL);\n+\thashmap_entry_init(&re->ent, memhash(&re->cookie, sizeof(uint32_t)));\n+\thashmap_add(&data->renames, &re->ent);\n+}\n+\n+/*\n+ * Handle directory renames\n+ *\n+ * Once a IN_MOVED_TO event is received, lookup the rename tracking information\n+ * via the event cookie and use this information to update the watch.\n+ */\n+static void rename_dir(uint32_t cookie, const char *path,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct rename_entry rek, *re;\n+\tstruct watch_entry k, *w;\n+\n+\t/* lookup a pending rename to match */\n+\trek.cookie = cookie;\n+\thashmap_entry_init(&rek.ent, memhash(&rek.cookie, sizeof(uint32_t)));\n+\tre = hashmap_get_entry(&data->renames, &rek, ent, NULL);\n+\tif (re) {\n+\t\tk.dir = re->dir;\n+\t\thashmap_entry_init(&k.ent, memhash(k.dir, strlen(k.dir)));\n+\t\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n+\t\tif (w) {\n+\t\t\tw->cookie = 0; /* rename handled */\n+\t\t\tremove_watch(w, data);\n+\t\t\tadd_watch(path, data);\n+\t\t} else {\n+\t\t\tBUG(\"No matching watch\");\n+\t\t}\n+\t} else {\n+\t\tBUG(\"No matching cookie\");\n+\t}\n+}\n+\n+/*\n+ * Recursively add watches to every directory under path\n+ */\n+static int register_inotify(const char *path, struct fsm_listen_data *data)\n+{\n+\tDIR *dir;\n+\tstruct strbuf current = STRBUF_INIT;\n+\tstruct dirent *de;\n+\tstruct stat fs;\n+\tint ret = -1;\n+\n+\tdir = opendir(path);\n+\tif (!dir)\n+\t\treturn error_errno(\"opendir('%s') failed\", path);\n+\n+\twhile ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {\n+\t\tstrbuf_reset(&current);\n+\t\tstrbuf_addf(&current, \"%s/%s\", path, de->d_name);\n+\t\tif (lstat(current.buf, &fs)) {\n+\t\t\terror_errno(\"lstat('%s') failed\", current.buf);\n+\t\t\tgoto failed;\n+\t\t}\n+\n+\t\t/* recurse into directory */\n+\t\tif (S_ISDIR(fs.st_mode)) {\n+\t\t\tif (add_watch(current.buf, data))\n+\t\t\t\tgoto failed;\n+\t\t\tif (register_inotify(current.buf, data))\n+\t\t\t\tgoto failed;\n+\t\t}\n+\t}\n+\tret = 0;\n+\n+failed:\n+\tstrbuf_release(&current);\n+\tif (closedir(dir) < 0)\n+\t\treturn error_errno(\"closedir('%s') failed\", path);\n+\treturn ret;\n+}\n+\n+static int em_rename_dir_from(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVED_FROM));\n+}\n+\n+static int em_rename_dir_to(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVED_TO));\n+}\n+\n+static int em_remove_watch(u_int32_t mask)\n+{\n+\treturn (mask & IN_DELETE_SELF);\n+}\n+\n+static int em_dir_renamed(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVE));\n+}\n+\n+static int em_dir_created(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_CREATE));\n+}\n+\n+static int em_dir_deleted(uint32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_DELETE));\n+}\n+\n+static int em_force_shutdown(u_int32_t mask)\n+{\n+\treturn (mask & IN_UNMOUNT) || (mask & IN_Q_OVERFLOW);\n+}\n+\n+static int em_ignore(u_int32_t mask)\n+{\n+\treturn (mask & IN_IGNORED) || (mask & IN_MOVE_SELF);\n+}\n+\n+static void log_mask_set(const char *path, u_int32_t mask)\n+{\n+\tstruct strbuf msg = STRBUF_INIT;\n+\n+\tif (mask & IN_ACCESS)\n+\t\tstrbuf_addstr(&msg, \"IN_ACCESS|\");\n+\tif (mask & IN_MODIFY)\n+\t\tstrbuf_addstr(&msg, \"IN_MODIFY|\");\n+\tif (mask & IN_ATTRIB)\n+\t\tstrbuf_addstr(&msg, \"IN_ATTRIB|\");\n+\tif (mask & IN_CLOSE_WRITE)\n+\t\tstrbuf_addstr(&msg, \"IN_CLOSE_WRITE|\");\n+\tif (mask & IN_CLOSE_NOWRITE)\n+\t\tstrbuf_addstr(&msg, \"IN_CLOSE_NOWRITE|\");\n+\tif (mask & IN_OPEN)\n+\t\tstrbuf_addstr(&msg, \"IN_OPEN|\");\n+\tif (mask & IN_MOVED_FROM)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVED_FROM|\");\n+\tif (mask & IN_MOVED_TO)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVED_TO|\");\n+\tif (mask & IN_CREATE)\n+\t\tstrbuf_addstr(&msg, \"IN_CREATE|\");\n+\tif (mask & IN_DELETE)\n+\t\tstrbuf_addstr(&msg, \"IN_DELETE|\");\n+\tif (mask & IN_DELETE_SELF)\n+\t\tstrbuf_addstr(&msg, \"IN_DELETE_SELF|\");\n+\tif (mask & IN_MOVE_SELF)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVE_SELF|\");\n+\tif (mask & IN_UNMOUNT)\n+\t\tstrbuf_addstr(&msg, \"IN_UNMOUNT|\");\n+\tif (mask & IN_Q_OVERFLOW)\n+\t\tstrbuf_addstr(&msg, \"IN_Q_OVERFLOW|\");\n+\tif (mask & IN_IGNORED)\n+\t\tstrbuf_addstr(&msg, \"IN_IGNORED|\");\n+\tif (mask & IN_ISDIR)\n+\t\tstrbuf_addstr(&msg, \"IN_ISDIR|\");\n+\n+\ttrace_printf_key(&trace_fsmonitor, \"inotify_event: '%s', mask=%#8.8x %s\",\n+\t\t\t\tpath, mask, msg.buf);\n+\n+\tstrbuf_release(&msg);\n+}\n+\n+int fsm_listen__ctor(struct fsmonitor_daemon_state *state)\n+{\n+\tint fd;\n+\tint ret = 0;\n+\tstruct fsm_listen_data *data;\n+\n+\tCALLOC_ARRAY(data, 1);\n+\tstate->listen_data = data;\n+\tstate->listen_error_code = -1;\n+\tdata->shutdown = SHUTDOWN_ERROR;\n+\n+\tfd = inotify_init1(O_NONBLOCK);\n+\tif (fd < 0)\n+\t\treturn error_errno(\"inotify_init1() failed\");\n+\n+\tdata->fd_inotify = fd;\n+\n+\thashmap_init(&data->watches, watch_entry_cmp, NULL, 0);\n+\thashmap_init(&data->renames, rename_entry_cmp, NULL, 0);\n+\thashmap_init(&data->revwatches, revwatches_entry_cmp, NULL, 0);\n+\n+\tif (add_watch(state->path_worktree_watch.buf, data))\n+\t\tret = -1;\n+\telse if (register_inotify(state->path_worktree_watch.buf, data))\n+\t\tret = -1;\n+\telse if (state->nr_paths_watching > 1) {\n+\t\tif (add_watch(state->path_gitdir_watch.buf, data))\n+\t\t\tret = -1;\n+\t\telse if (register_inotify(state->path_gitdir_watch.buf, data))\n+\t\t\tret = -1;\n+\t}\n+\n+\tif (!ret) {\n+\t\tstate->listen_error_code = 0;\n+\t\tdata->shutdown = SHUTDOWN_CONTINUE;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+void fsm_listen__dtor(struct fsmonitor_daemon_state *state)\n+{\n+\tstruct fsm_listen_data *data;\n+\tstruct hashmap_iter iter;\n+\tstruct watch_entry *w;\n+\tint fd;\n+\n+\tif (!state || !state->listen_data)\n+\t\treturn;\n+\n+\tdata = state->listen_data;\n+\tfd = data->fd_inotify;\n+\n+\thashmap_for_each_entry(&data->watches, &iter, w, ent) {\n+\t\tw->cookie = 0; /* ignore any pending renames */\n+\t\tremove_watch(w, data);\n+\t}\n+\thashmap_clear(&data->watches);\n+\n+\thashmap_clear(&data->revwatches); /* remove_watch freed the entries */\n+\n+\thashmap_clear_and_free(&data->renames, struct rename_entry, ent);\n+\n+\tFREE_AND_NULL(state->listen_data);\n+\n+\tif (fd && (close(fd) < 0))\n+\t\terror_errno(_(\"closing inotify file descriptor failed\"));\n+}\n+\n+void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)\n+{\n+\tif (!state->listen_data->shutdown)\n+\t\tstate->listen_data->shutdown = SHUTDOWN_STOP;\n+}\n+\n+/*\n+ * Process a single inotify event and queue for publication.\n+ */\n+static int process_event(const char *path,\n+\tconst struct inotify_event *event,\n+\tstruct fsmonitor_batch *batch,\n+\tstruct string_list *cookie_list,\n+\tstruct fsmonitor_daemon_state *state)\n+{\n+\tconst char *rel;\n+\tconst char *last_sep;\n+\n+\tswitch (fsmonitor_classify_path_absolute(state, path)) {\n+\t\tcase IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:\n+\t\tcase IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:\n+\t\t\t/* Use just the filename of the cookie file. */\n+\t\t\tlast_sep = find_last_dir_sep(path);\n+\t\t\tstring_list_append(cookie_list,\n+\t\t\t\t\tlast_sep ? last_sep + 1 : path);\n+\t\t\tbreak;\n+\t\tcase IS_INSIDE_DOT_GIT:\n+\t\tcase IS_INSIDE_GITDIR:\n+\t\t\tbreak;\n+\t\tcase IS_DOT_GIT:\n+\t\tcase IS_GITDIR:\n+\t\t\t/*\n+\t\t\t* If .git directory is deleted or renamed away,\n+\t\t\t* we have to quit.\n+\t\t\t*/\n+\t\t\tif (em_dir_deleted(event->mask)) {\n+\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\"event: gitdir removed\");\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\tif (em_dir_renamed(event->mask)) {\n+\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\"event: gitdir renamed\");\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase IS_WORKDIR_PATH:\n+\t\t\t/* normal events in the working directory */\n+\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n+\t\t\t\tlog_mask_set(path, event->mask);\n+\n+\t\t\trel = path + state->path_worktree_watch.len + 1;\n+\t\t\tfsmonitor_batch__add_path(batch, rel);\n+\n+\t\t\tif (em_dir_deleted(event->mask))\n+\t\t\t\tbreak;\n+\n+\t\t\t/* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */\n+\t\t\tif (em_rename_dir_from(event->mask))\n+\t\t\t\tadd_dir_rename(event->cookie, path, state->listen_data);\n+\n+\t\t\t/* received IN_MOVE_TO, update watch to reflect new path */\n+\t\t\tif (em_rename_dir_to(event->mask))\n+\t\t\t\trename_dir(event->cookie, path, state->listen_data);\n+\n+\t\t\tif (em_dir_created(event->mask)) {\n+\t\t\t\tif (add_watch(path, state->listen_data)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t\tif (register_inotify(path, state->listen_data)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase IS_OUTSIDE_CONE:\n+\t\tdefault:\n+\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\"ignoring '%s'\", path);\n+\t\t\tbreak;\n+\t}\n+\treturn 0;\n+done:\n+\treturn -1;\n+}\n+\n+/*\n+ * Read the inotify event stream and pre-process events before further\n+ * processing and eventual publishing.\n+ */\n+static void handle_events(struct fsmonitor_daemon_state *state)\n+{\n+\t /* See https://man7.org/linux/man-pages/man7/inotify.7.html */\n+\tchar buf[4096]\n+\t\t__attribute__ ((aligned(__alignof__(struct inotify_event))));\n+\n+\tstruct hashmap watches = state->listen_data->watches;\n+\tstruct fsmonitor_batch *batch = NULL;\n+\tstruct string_list cookie_list = STRING_LIST_INIT_DUP;\n+\tstruct watch_entry k, *w;\n+\tstruct strbuf path;\n+\tconst struct inotify_event *event;\n+\tint fd = state->listen_data->fd_inotify;\n+\tssize_t len;\n+\tchar *ptr, *p;\n+\n+\tstrbuf_init(&path, PATH_MAX);\n+\n+\tfor(;;) {\n+\t\tlen = read(fd, buf, sizeof(buf));\n+\t\tif (len == -1 && errno != EAGAIN) {\n+\t\t\terror_errno(_(\"reading inotify message stream failed\"));\n+\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\t/* nothing to read */\n+\t\tif (len <= 0)\n+\t\t\tgoto done;\n+\n+\t\t/* Loop over all events in the buffer. */\n+\t\tfor (ptr = buf; ptr < buf + len;\n+\t\t\t ptr += sizeof(struct inotify_event) + event->len) {\n+\n+\t\t\tevent = (const struct inotify_event *) ptr;\n+\n+\t\t\tif (em_ignore(event->mask))\n+\t\t\t\tcontinue;\n+\n+\t\t\t/* File system was unmounted or event queue overflowed */\n+\t\t\tif (em_force_shutdown(event->mask)) {\n+\t\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n+\t\t\t\t\tlog_mask_set(\"Forcing shutdown\", event->mask);\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\thashmap_entry_init(&k.ent, memhash(&event->wd, sizeof(int)));\n+\t\t\tk.wd = event->wd;\n+\n+\t\t\tw = hashmap_get_entry(&watches, &k, ent, NULL);\n+\t\t\tif (!w) /* should never happen */\n+\t\t\t\tBUG(\"No watch for '%s'\", event->name);\n+\n+\t\t\t/* directory watch was removed */\n+\t\t\tif (em_remove_watch(event->mask)) {\n+\t\t\t\tremove_watch(w, state->listen_data);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tstrbuf_reset(&path);\n+\t\t\tstrbuf_add(&path, w->dir, strlen(w->dir));\n+\t\t\tstrbuf_addch(&path, '/');\n+\t\t\tstrbuf_add(&path, event->name,  strlen(event->name));\n+\n+\t\t\tp = fsmonitor__resolve_alias(path.buf, &state->alias);\n+\t\t\tif (!p)\n+\t\t\t\tp = strbuf_detach(&path, NULL);\n+\n+\t\t\tif (!batch)\n+\t\t\t\tbatch = fsmonitor_batch__new();\n+\n+\t\t\tif (process_event(p, event, batch, &cookie_list, state)) {\n+\t\t\t\tfree(p);\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tfree(p);\n+\t\t}\n+\t\tstrbuf_reset(&path);\n+\t\tfsmonitor_publish(state, batch, &cookie_list);\n+\t\tstring_list_clear(&cookie_list, 0);\n+\t\tbatch = NULL;\n+\t}\n+done:\n+\tstrbuf_release(&path);\n+\tfsmonitor_batch__free_list(batch);\n+\tstring_list_clear(&cookie_list, 0);\n+}\n+\n+/*\n+ * Non-blocking read of the inotify events stream. The inotify fd is polled\n+ * every few millisec to help minimize the number of queue overflows.\n+ */\n+void fsm_listen__loop(struct fsmonitor_daemon_state *state)\n+{\n+\tint poll_num;\n+\tconst int interval = 1000;\n+\ttime_t checked = time(NULL);\n+\tstruct pollfd fds[1];\n+\tfds[0].fd = state->listen_data->fd_inotify;\n+\tfds[0].events = POLLIN;\n+\n+\tfor(;;) {\n+\t\tswitch (state->listen_data->shutdown) {\n+\t\t\tcase SHUTDOWN_CONTINUE:\n+\t\t\t\tpoll_num = poll(fds, 1, 10);\n+\t\t\t\tif (poll_num == -1) {\n+\t\t\t\t\tif (errno == EINTR)\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\terror_errno(_(\"polling inotify message stream failed\"));\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif ((time(NULL) - checked) >= interval) {\n+\t\t\t\t\tchecked = time(NULL);\n+\t\t\t\t\tif (check_stale_dir_renames(&state->listen_data->renames,\n+\t\t\t\t\t\tchecked - interval)) {\n+\t\t\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\t\"Missed IN_MOVED_TO events, forcing shutdown\");\n+\t\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\n+\t\t\t\tif (poll_num > 0 && (fds[0].revents & POLLIN))\n+\t\t\t\t\thandle_events(state);\n+\n+\t\t\t\tcontinue;\n+\t\t\tcase SHUTDOWN_ERROR:\n+\t\t\t\tstate->listen_error_code = -1;\n+\t\t\t\tipc_server_stop_async(state->ipc_server_data);\n+\t\t\t\tbreak;\n+\t\t\tcase SHUTDOWN_FORCE:\n+\t\t\t\tstate->listen_error_code = 0;\n+\t\t\t\tipc_server_stop_async(state->ipc_server_data);\n+\t\t\t\tbreak;\n+\t\t\tcase SHUTDOWN_STOP:\n+\t\t\tdefault:\n+\t\t\t\tstate->listen_error_code = 0;\n+\t\t\t\tbreak;\n+\t\t}\n+\t\treturn;\n+\t}\n+}\n-- \ngitgitgadget\n\n"},{"id":"464454","messageId":"3a2db9aa07697fd4b66b533d1afee4f49cef0248.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 10/12] fsmonitor: enable fsmonitor for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:36Z","receivedAt":"2022-10-09T14:38:11Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nUodate build to enable fsmonitor for Linux.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n config.mak.uname                    |  9 +++++++++\n contrib/buildsystems/CMakeLists.txt | 11 ++++++++++-\n 2 files changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/config.mak.uname b/config.mak.uname\nindex d454cec47c4..04988266835 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -69,6 +69,15 @@ ifeq ($(uname_S),Linux)\n \t\tBASIC_CFLAGS += -std=c99\n \tendif\n \n+\t# The builtin FSMonitor on Linux builds upon Simple-IPC.  Both require\n+\t# Unix domain sockets and PThreads.\n+\tifndef NO_PTHREADS\n+\tifndef NO_UNIX_SOCKETS\n+\tFSMONITOR_DAEMON_BACKEND = linux\n+\tFSMONITOR_OS_SETTINGS = linux\n+\tFSMONITOR_DAEMON_COMMON = unix\n+\tendif\n+\tendif\n endif\n ifeq ($(uname_S),GNU/kFreeBSD)\n \tHAVE_ALLOCA_H = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 0b26a1a36e3..afabfcdefdd 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -304,7 +304,16 @@ else()\n endif()\n \n if(SUPPORTS_SIMPLE_IPC)\n-\tif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n+\t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-unix.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-unix.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-linux.c)\n+\n+\t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-unix.c)\n+\telseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n-- \ngitgitgadget\n\n"},{"id":"464455","messageId":"743bdacded5183b5b11c9f19c24b07fa26eded24.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 11/12] fsmonitor: test updates","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:37Z","receivedAt":"2022-10-09T14:38:39Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nt7527-builtin-fsmonitor was leaking fsmonitor--daemon processes in some\ncases.\n\nAccomodate slight difference in the number of events generated on Linux.\n\nOn lower-powered systems, spin a little to give the daemon time\nto respond to and log filesystem events.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n t/t7527-builtin-fsmonitor.sh | 58 +++++++++++++++++++++++++++---------\n 1 file changed, 44 insertions(+), 14 deletions(-)\n\ndiff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh\nindex 56c0dfffea6..19a243588e2 100755\n--- a/t/t7527-builtin-fsmonitor.sh\n+++ b/t/t7527-builtin-fsmonitor.sh\n@@ -13,7 +13,7 @@ fi\n stop_daemon_delete_repo () {\n \tr=$1 &&\n \ttest_might_fail git -C $r fsmonitor--daemon stop &&\n-\trm -rf $1\n+\trm -rf $r\n }\n \n start_daemon () {\n@@ -72,6 +72,32 @@ start_daemon () {\n \t)\n }\n \n+IMPLICIT_TIMEOUT=5\n+\n+wait_for_update () {\n+\tfunc=$1 &&\n+\tfile=$2 &&\n+\tsz=$(wc -c < \"$file\") &&\n+\tlast=0 &&\n+\t$func &&\n+\tk=0 &&\n+\twhile test \"$k\" -lt $IMPLICIT_TIMEOUT\n+\tdo\n+\t\tnsz=$(wc -c < \"$file\")\n+\t\tif test \"$nsz\" -gt \"$sz\"\n+\t\tthen\n+\t\t\tif test \"$last\" -eq \"$nsz\"\n+\t\t\tthen\n+\t\t\t\treturn 0\n+\t\t\tfi\n+\t\t\tlast=$nsz\n+\t\tfi\n+\t\tsleep 1\n+\t\tk=$(( $k + 1 ))\n+\tdone &&\n+\treturn 0\n+}\n+\n # Is a Trace2 data event present with the given catetory and key?\n # We do not care what the value is.\n #\n@@ -137,7 +163,6 @@ test_expect_success 'implicit daemon start' '\n # machines (where it might take a moment to wake and reschedule the\n # daemon process) to avoid false alarms during test runs.)\n #\n-IMPLICIT_TIMEOUT=5\n \n verify_implicit_shutdown () {\n \tr=$1 &&\n@@ -373,6 +398,10 @@ create_files () {\n \techo 3 >dir2/new\n }\n \n+rename_directory () {\n+\tmv dirtorename dirrenamed\n+}\n+\n rename_files () {\n \tmv rename renamed &&\n \tmv dir1/rename dir1/renamed &&\n@@ -427,7 +456,7 @@ test_expect_success 'edit some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tedit_files &&\n+\twait_for_update edit_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n@@ -442,7 +471,7 @@ test_expect_success 'create some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tcreate_files &&\n+\twait_for_update create_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n@@ -456,7 +485,7 @@ test_expect_success 'delete some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tdelete_files &&\n+\twait_for_update delete_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n@@ -470,7 +499,7 @@ test_expect_success 'rename some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\trename_files &&\n+\twait_for_update rename_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n@@ -487,7 +516,7 @@ test_expect_success 'rename directory' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tmv dirtorename dirrenamed &&\n+\twait_for_update rename_directory \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n@@ -500,7 +529,7 @@ test_expect_success 'file changes to directory' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tfile_to_directory &&\n+\twait_for_update file_to_directory \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n@@ -513,7 +542,7 @@ test_expect_success 'directory changes to a file' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tdirectory_to_file &&\n+\twait_for_update directory_to_file \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n@@ -561,7 +590,7 @@ test_expect_success 'flush cached data' '\n \ttest-tool -C test_flush fsmonitor-client query --token \"builtin:test_00000002:0\" >actual_2 &&\n \tnul_to_q <actual_2 >actual_q2 &&\n \n-\tgrep \"^builtin:test_00000002:0Q$\" actual_q2 &&\n+\tgrep \"^builtin:test_00000002:[0-1]Q$\" actual_q2 &&\n \n \t>test_flush/file_3 &&\n \n@@ -732,7 +761,8 @@ u_values=\"$u1 $u2\"\n for u in $u_values\n do\n \ttest_expect_success \"unicode in repo root path: $u\" '\n-\t\ttest_when_finished \"stop_daemon_delete_repo $u\" &&\n+\t\ttest_when_finished \\\n+\t\t\"stop_daemon_delete_repo `echo \"$u\" | sed 's:x:\\\\\\\\\\\\\\\\\\\\\\\\\\\\x:g'`\" &&\n \n \t\tgit init \"$u\" &&\n \t\techo 1 >\"$u\"/file1 &&\n@@ -814,8 +844,7 @@ my_match_and_clean () {\n }\n \n test_expect_success 'submodule always visited' '\n-\ttest_when_finished \"git -C super fsmonitor--daemon stop; \\\n-\t\t\t    rm -rf super; \\\n+\ttest_when_finished \"rm -rf super; \\\n \t\t\t    rm -rf sub\" &&\n \n \tcreate_super super &&\n@@ -883,7 +912,8 @@ have_t2_error_event () {\n }\n \n test_expect_success \"stray submodule super-prefix warning\" '\n-\ttest_when_finished \"rm -rf super; \\\n+\ttest_when_finished \"git -C super/dir_1/dir_2/sub fsmonitor--daemon stop; \\\n+\t\t\t    rm -rf super; \\\n \t\t\t    rm -rf sub;   \\\n \t\t\t    rm super-sub.trace\" &&\n \n-- \ngitgitgadget\n\n"},{"id":"464456","messageId":"77ed35b3b80de9ecfea179357115428ab89d9864.1665326258.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH 12/12] fsmonitor: update doc for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-09T14:37:38Z","receivedAt":"2022-10-09T14:38:41Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nUpdate the documentation for Linux.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Documentation/config/fsmonitor--daemon.txt |  4 ++--\n Documentation/git-fsmonitor--daemon.txt    | 24 ++++++++++++++--------\n 2 files changed, 18 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config/fsmonitor--daemon.txt b/Documentation/config/fsmonitor--daemon.txt\nindex c225c6c9e74..2cafb040d96 100644\n--- a/Documentation/config/fsmonitor--daemon.txt\n+++ b/Documentation/config/fsmonitor--daemon.txt\n@@ -4,8 +4,8 @@ fsmonitor.allowRemote::\n     behavior.  Only respected when `core.fsmonitor` is set to `true`.\n \n fsmonitor.socketDir::\n-    This Mac OS-specific option, if set, specifies the directory in\n+    Mac OS and Linux-specific option. If set, specifies the directory in\n     which to create the Unix domain socket used for communication\n     between the fsmonitor daemon and various Git commands. The directory must\n-    reside on a native Mac OS filesystem.  Only respected when `core.fsmonitor`\n+    reside on a native filesystem.  Only respected when `core.fsmonitor`\n     is set to `true`.\ndiff --git a/Documentation/git-fsmonitor--daemon.txt b/Documentation/git-fsmonitor--daemon.txt\nindex 8238eadb0e1..c2b08229c74 100644\n--- a/Documentation/git-fsmonitor--daemon.txt\n+++ b/Documentation/git-fsmonitor--daemon.txt\n@@ -76,23 +76,31 @@ repositories; this may be overridden by setting `fsmonitor.allowRemote` to\n correctly with all network-mounted repositories and such use is considered\n experimental.\n \n-On Mac OS, the inter-process communication (IPC) between various Git\n+On Linux and Mac OS, the inter-process communication (IPC) between various Git\n commands and the fsmonitor daemon is done via a Unix domain socket (UDS) -- a\n-special type of file -- which is supported by native Mac OS filesystems,\n-but not on network-mounted filesystems, NTFS, or FAT32.  Other filesystems\n-may or may not have the needed support; the fsmonitor daemon is not guaranteed\n-to work with these filesystems and such use is considered experimental.\n+special type of file -- which is supported by many native Linux and Mac OS\n+filesystems, but not on network-mounted filesystems, NTFS, or FAT32.  Other\n+filesystems may or may not have the needed support; the fsmonitor daemon is not\n+guaranteed to work with these filesystems and such use is considered\n+experimental.\n \n By default, the socket is created in the `.git` directory, however, if the\n `.git` directory is on a network-mounted filesystem, it will be instead be\n created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a\n network-mounted filesystem in which case you must set the configuration\n-variable `fsmonitor.socketDir` to the path of a directory on a Mac OS native\n+variable `fsmonitor.socketDir` to the path of a directory on a native\n filesystem in which to create the socket file.\n \n If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`)\n-is on a native Mac OS file filesystem the fsmonitor daemon will report an\n-error that will cause the daemon and the currently running command to exit.\n+is on a native Linux or Mac OS filesystem the fsmonitor daemon will report\n+an error that will cause the daemon to exit and the currently running command\n+to issue a warning.\n+\n+On Linux, the fsmonitor daemon registers a watch for each directory in the\n+repository.  The default per-user limit for the number of watches on most Linux\n+systems is 8192.  This may not be sufficient for large repositories or if\n+multiple instances of the fsmonitor daemon are running.\n+See https://watchexec.github.io/docs/inotify-limits.html[Linux inotify limits] for more information.\n \n CONFIGURATION\n -------------\n-- \ngitgitgadget\n"},{"id":"464463","messageId":"xmqqh70c62w0.fsf@gitster.g","threadId":"58588","inReplyTo":"c085fc15b314abcb5e5ca6b4ee5ac54a28327cab.1665326258.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 07/12] fsmonitor: prepare to share code between Mac OS and Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-09T22:36:15Z","receivedAt":"2022-10-09T23:40:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/Makefile b/Makefile\n> index feb675a6959..31dd6ab2734 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -2038,13 +2038,13 @@ endif\n>  ifdef FSMONITOR_DAEMON_BACKEND\n>  \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n>  \tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o\n> -\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o\n> -\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n> +\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_COMMON).o\n> +\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_COMMON).o\n>  endif\n>  \n>  ifdef FSMONITOR_OS_SETTINGS\n>  \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n> -\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o\n> +\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_COMMON).o\n>  \tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o\n>  endif\n\nThese two look sloppier than the existing \"if we have DAEMON_BACKEND\ndefined, then add $(DAEMON_BACKEND).o to the set of objects used\".\n$(DAEMON_COMMON).o should be used after the same kind of check, i.e.\n\n+ifdef FSMONITOR_DAEMON_COMMON\n+\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_COMMON).o\n+\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_COMMON).o\n+\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_COMMON).o\n+endif\n\nas a separate if/endif block, without touching the above two, perhaps?\n\n> --- a/compat/fsmonitor/fsm-ipc-darwin.c\n> +++ b/compat/fsmonitor/fsm-ipc-unix.c\n> @@ -10,7 +10,7 @@ static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n>  const char *fsmonitor_ipc__get_path(struct repository *r)\n>  {\n>  \tstatic const char *ipc_path = NULL;\n> -\tSHA_CTX sha1ctx;\n> +\tgit_SHA_CTX sha1ctx;\n>  \tchar *sock_dir = NULL;\n>  \tstruct strbuf ipc_file = STRBUF_INIT;\n>  \tunsigned char hash[SHA_DIGEST_LENGTH];\n> @@ -28,9 +28,9 @@ const char *fsmonitor_ipc__get_path(struct repository *r)\n>  \t\treturn ipc_path;\n>  \t}\n>  \n> -\tSHA1_Init(&sha1ctx);\n> -\tSHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n> -\tSHA1_Final(hash, &sha1ctx);\n> +\tgit_SHA1_Init(&sha1ctx);\n> +\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n> +\tgit_SHA1_Final(hash, &sha1ctx);\n>  \n>  \trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n\nInteresting.  The result of course is good, but I wonder how we have\nbeen happy to queue the original code that lack git_ prefix in the\nfirst place X-<.\n\n\n> diff --git a/config.mak.uname b/config.mak.uname\n> index d63629fe807..d454cec47c4 100644\n> --- a/config.mak.uname\n> +++ b/config.mak.uname\n> @@ -68,6 +68,7 @@ ifeq ($(uname_S),Linux)\n>  \tifneq ($(findstring .el7.,$(uname_R)),)\n>  \t\tBASIC_CFLAGS += -std=c99\n>  \tendif\n> +\n>  endif\n>  ifeq ($(uname_S),GNU/kFreeBSD)\n>  \tHAVE_ALLOCA_H = YesPlease\n\nThat's a new blank line in an unexpected place.  Did you mean to add\nit after the outer endif?\n"},{"id":"464465","messageId":"xmqqsfjw63f6.fsf@gitster.g","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-09T22:24:45Z","receivedAt":"2022-10-09T23:46:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\n> Windows and Mac OS.\n>\n> This patch set builds upon previous work for done for Windows and Mac OS\n> (first 6 patches) ...\n\nFor those who are watching from sidelines...\n\nThe Windows part is already in Git 2.38; the changes needed for\nmacOS are already in 'next' and the first 6 patches in this 12-patch\nseries are identical to them.  The patches 7-12 are new.\n\n> to implement a fsmonitor back-end for Linux based on the\n> Linux inotify API. inotify differs significantly from the equivalent Windows\n> and Mac OS APIs in that a watch must be registered for every directory of\n> interest (rather than a singular watch at the root of the directory tree)\n> and special care must be taken to handle directory renames correctly.\n\n"},{"id":"464466","messageId":"CAPig+cRqKvu-hLRpd3KZf-UmV92mpgfAuieBBzUPR2JQ0s9AdA@mail.gmail.com","threadId":"58588","inReplyTo":"xmqqsfjw63f6.fsf@gitster.g","subject":"Re: [PATCH 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2022-10-10T00:19:21Z","receivedAt":"2022-10-10T00:42:58Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, Oct 9, 2022 at 7:55 PM Junio C Hamano <gitster@pobox.com> wrote:\n> \"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> > Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\n> > Windows and Mac OS.\n> >\n> > This patch set builds upon previous work for done for Windows and Mac OS\n> > (first 6 patches) ...\n>\n> For those who are watching from sidelines...\n>\n> The Windows part is already in Git 2.38; the changes needed for\n> macOS are already in 'next' and the first 6 patches in this 12-patch\n> series are identical to them.  The patches 7-12 are new.\n\nThanks for clarifying. I found it confusing that there were a number\nof patches in this series which I had already seen despite the cover\nletter's claim that this series builds upon \"previous work\". Thus, it\nwasn't clear whether this series was a reroll (refining some\nalready-seen patches) with additional patches for Linux, or if it was\npurely new work with the original patches included by accident[1].\n\n[1]: In the few times I've used GitGitGadget, I've had to jump through\nhoops to make it send just the \"new\" patches when I've built a series\natop some other series only in 'next' or 'seen', so I can understand\nthe inclusion of the first six patches being accidental. (Regarding\nthe hoop-jumping, it may be that I just don't understand how to \"work\"\nGitGitGadget or GitHub pull-requests.)\n"},{"id":"464476","messageId":"221010.86sfjwouqx.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"5ecbb3082f16956d049cfa98662f8e3384a6aea2.1665326258.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 08/12] fsmonitor: determine if filesystem is local or remote","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-10-10T10:04:01Z","receivedAt":"2022-10-10T10:09:34Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Oct 09 2022, Eric DeCosta via GitGitGadget wrote:\n\n> From: Eric DeCosta <edecosta@mathworks.com>\n>\n> Compare the given path to the mounted filesystems. Find the mount that is\n> the longest prefix of the path (if any) and determine if that mount is on a\n> local or remote filesystem.\n>\n> Signed-off-by: Eric DeCosta <edecosta@mathworks.com>\n> ---\n>  compat/fsmonitor/fsm-path-utils-linux.c | 163 ++++++++++++++++++++++++\n>  1 file changed, 163 insertions(+)\n>  create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n>\n> diff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c\n> new file mode 100644\n> index 00000000000..369692a788f\n> --- /dev/null\n> +++ b/compat/fsmonitor/fsm-path-utils-linux.c\n> @@ -0,0 +1,163 @@\n> +#include \"fsmonitor.h\"\n> +#include \"fsmonitor-path-utils.h\"\n> +#include <errno.h>\n> +#include <mntent.h>\n> +#include <sys/mount.h>\n> +#include <sys/statvfs.h>\n> +\n> +/*\n> + * https://github.com/coreutils/gnulib/blob/master/lib/mountlist.c\n> + */\n> +#ifndef ME_REMOTE\n> +/* A file system is \"remote\" if its Fs_name contains a ':'\n> +   or if (it is of type (smbfs or cifs) and its Fs_name starts with '//')\n> +   or if it is of any other of the listed types\n> +   or Fs_name is equal to \"-hosts\" (used by autofs to mount remote fs).\n> +   \"VM\" file systems like prl_fs or vboxsf are not considered remote here. */\n> +# define ME_REMOTE(Fs_name, Fs_type)            \\\n> +\t(strchr (Fs_name, ':') != NULL              \\\n> +\t || ((Fs_name)[0] == '/'                    \\\n> +\t\t && (Fs_name)[1] == '/'                 \\\n> +\t\t && (strcmp (Fs_type, \"smbfs\") == 0     \\\n> +\t\t\t || strcmp (Fs_type, \"smb3\") == 0   \\\n> +\t\t\t || strcmp (Fs_type, \"cifs\") == 0)) \\\n> +\t || strcmp (Fs_type, \"acfs\") == 0           \\\n> +\t || strcmp (Fs_type, \"afs\") == 0            \\\n> +\t || strcmp (Fs_type, \"coda\") == 0           \\\n> +\t || strcmp (Fs_type, \"auristorfs\") == 0     \\\n> +\t || strcmp (Fs_type, \"fhgfs\") == 0          \\\n> +\t || strcmp (Fs_type, \"gpfs\") == 0           \\\n> +\t || strcmp (Fs_type, \"ibrix\") == 0          \\\n> +\t || strcmp (Fs_type, \"ocfs2\") == 0          \\\n> +\t || strcmp (Fs_type, \"vxfs\") == 0           \\\n> +\t || strcmp (\"-hosts\", Fs_name) == 0)\n> +#endif\n\nSo, this is just copy/pasted GPLv3 code into our GPLv2-only codebase?:\nhttps://github.com/coreutils/gnulib/blob/cd1fdabe8b66c102124b6a5b0c97dded20246b46/lib/mountlist.c#L230-L247\n\n> +static struct mntent *find_mount(const char *path, const struct statvfs *fs)\n> +{\n> +\tconst char *const mounts = \"/proc/mounts\";\n> +\tconst char *rp = real_pathdup(path, 1);\n> +\tstruct mntent *ment = NULL;\n> +\tstruct mntent *found = NULL;\n> +\tstruct statvfs mntfs;\n> +\tFILE *fp;\n> +\tint dlen, plen, flen = 0;\n> +\n> +\tfp = setmntent(mounts, \"r\");\n> +\tif (!fp) {\n> +\t\terror_errno(_(\"setmntent('%s') failed\"), mounts);\n> +\t\treturn NULL;\n\nThis code would probably be nicer if you returned int, and passed a\npointer to a struct to populate as a paremeter. Then you could just\n\"return error...\" for this and the below cases.\n\n> +\t}\n> +\n> +\tplen = strlen(rp);\n> +\n> +\t/* read all the mount information and compare to path */\n> +\twhile ((ment = getmntent(fp)) != NULL) {\n> +\t\tif (statvfs(ment->mnt_dir, &mntfs)) {\n> +\t\t\tswitch (errno) {\n> +\t\t\tcase EPERM:\n> +\t\t\tcase ESRCH:\n> +\t\t\tcase EACCES:\n> +\t\t\t\tcontinue;\n> +\t\t\tdefault:\n> +\t\t\t\terror_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n> +\t\t\t\tendmntent(fp);is \n> +\t\t\t\treturn NULL;\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\t/* is mount on the same filesystem and is a prefix of the path */\n> +\t\tif ((fs->f_fsid == mntfs.f_fsid) &&\n> +\t\t\t!strncmp(ment->mnt_dir, rp, strlen(ment->mnt_dir))) {\n> +\t\t\tdlen = strlen(ment->mnt_dir);\n> +\t\t\tif (dlen > plen)\n> +\t\t\t\tcontinue;\n> +\t\t\t/*\n> +\t\t\t * root is always a potential match; otherwise look for\n> +\t\t\t * directory prefix\n> +\t\t\t */\n> +\t\t\tif ((dlen == 1 && ment->mnt_dir[0] == '/') ||\n> +\t\t\t\t(dlen > flen && (!rp[dlen] || rp[dlen] == '/'))) {\n> +\t\t\t\tflen = dlen;\n> +\t\t\t\t/*\n> +\t\t\t\t * https://man7.org/linux/man-pages/man3/getmntent.3.html\n> +\t\t\t\t *\n> +\t\t\t\t * The pointer points to a static area of memory which is\n> +\t\t\t\t * overwritten by subsequent calls to getmntent().\n> +\t\t\t\t */\n> +\t\t\t\tif (!found)\n> +\t\t\t\t\tCALLOC_ARRAY(found, 1);\n\nIt seems we never populate >1 of these, so don't you just want\nxmalloc(). Or actually...\n\n> +\t\t\t\tfree(found->mnt_dir);\n> +\t\t\t\tfree(found->mnt_type);\n> +\t\t\t\tfree(found->mnt_fsname);\n> +\t\t\t\tfound->mnt_dir = xmemdupz(ment->mnt_dir, strlen(ment->mnt_dir));\n> +\t\t\t\tfound->mnt_type = xmemdupz(ment->mnt_type, strlen(ment->mnt_type));\n> +\t\t\t\tfound->mnt_fsname = xmemdupz(ment->mnt_fsname, strlen(ment->mnt_fsname));\n\nDon't mix mem*() and str*(). In this case we need the string to be '\\0'\ndelimited, so use xstrndup().\n\nAnd once we do that, we might wonder why we're explicitly finding the\n'\\0', just to pass it to the xstrn*() function, when we can just do:\n\n\tfound->mnt_dir = xstrdup(ment->mnt_dir);\n\t...\n\nWhich would AFAICT be equivalent to what you're doing here.\n\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +\tendmntent(fp);\n> +\n> +\treturn found;\n> +}\n> +\n> +int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n> +{\n> +\tstruct mntent *ment;\n\n...make this (or above) a \"struct mntent ment\", then pass &ment down, so\nwe can fill that struct? Dunno...\n\n> +\tstruct statvfs fs;\n> +\n> +\tif (statvfs(path, &fs))\n> +\t\treturn error_errno(_(\"statvfs('%s') failed\"), path);\n> +\n> +\tment = find_mount(path, &fs);\n> +\tif (!ment)\n> +\t\treturn -1;\n> +\n> +\ttrace_printf_key(&trace_fsmonitor,\n> +\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n> +\t\t\t path, fs.f_flag, ment->mnt_type, ment->mnt_fsname);\n> +\n> +\tif (ME_REMOTE(ment->mnt_fsname, ment->mnt_type))\n> +\t\tfs_info->is_remote = 1;\n> +\telse\n> +\t\tfs_info->is_remote = 0;\n\nShorter:\n\n\tfs_info->is_remote = ME_REMOTE(ment->mnt_fsname, ment->mnt_type);\n"},{"id":"464496","messageId":"75f92394-b800-3404-3f3f-20fc4725fb13@jeffhostetler.com","threadId":"58588","inReplyTo":"c085fc15b314abcb5e5ca6b4ee5ac54a28327cab.1665326258.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 07/12] fsmonitor: prepare to share code between Mac OS and Linux","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2022-10-10T16:23:22Z","receivedAt":"2022-10-10T16:23:39Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 10/9/22 10:37 AM, Eric DeCosta via GitGitGadget wrote:\n> From: Eric DeCosta <edecosta@mathworks.com>\n> \n> Linux and Mac OS can share some of the code originally developed for Mac OS.\n> Rename the shared code from compat/fsmonitor/fsm-*-dawrin.c to\n> compat/fsmonitor/fsm-*-unix.c\n> \n> Update the build to enable sharing of the fsm-*-unix.c files.\n> \n> Minor update to compat/fsmonitor/fsm-ipc-unix.c to make it cross-platform.\n\nI fear that it may be too early to claim that the platform-specific\ncode can truly be shared (at least without some amount of future\nifdef'ing).  Yes, there are some commonalities, such as our use of\nUnix domain sockets on Mac and *nix, but there may be other things\nthat are not.\n\nSo maybe do the rename for the IPC code, but not for the health, for\nexample.  Or, keep the platform-named files and add new -unix-common.c\nfiles that can be called from the other (or vice versa).\n\n\n> \n> Signed-off-by: Eric DeCosta <edecosta@mathworks.com>\n> ---\n>   Makefile                                                  | 6 +++---\n>   .../fsmonitor/{fsm-health-darwin.c => fsm-health-unix.c}  | 0\n>   compat/fsmonitor/{fsm-ipc-darwin.c => fsm-ipc-unix.c}     | 8 ++++----\n>   .../{fsm-settings-darwin.c => fsm-settings-unix.c}        | 0\n>   config.mak.uname                                          | 4 ++++\n>   contrib/buildsystems/CMakeLists.txt                       | 6 +++---\n>   6 files changed, 14 insertions(+), 10 deletions(-)\n>   rename compat/fsmonitor/{fsm-health-darwin.c => fsm-health-unix.c} (100%)\n>   rename compat/fsmonitor/{fsm-ipc-darwin.c => fsm-ipc-unix.c} (89%)\n>   rename compat/fsmonitor/{fsm-settings-darwin.c => fsm-settings-unix.c} (100%)\n> \n> diff --git a/Makefile b/Makefile\n> index feb675a6959..31dd6ab2734 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -2038,13 +2038,13 @@ endif\n>   ifdef FSMONITOR_DAEMON_BACKEND\n>   \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n>   \tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o\n> -\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o\n> -\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n> +\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_COMMON).o\n> +\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_COMMON).o\n>   endif\n>   \n>   ifdef FSMONITOR_OS_SETTINGS\n>   \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n> -\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o\n> +\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_COMMON).o\n\nThis is wrong.  you're mixing the usage of the FSMONITOR_OS_SETTINGS\ndefinition and the usage of your new _COMMON definition.\n\nAnd I agree with Junio's comment about grouping the _COMMON ones in\ntheir own block and leaving the original COMPAT_OBJS lines as they were.\n\n\nBut really, I think the use of that \"common\" definition is going to\ncause us problems later on.  I mean, you're using it to smash together\nthe ipc, health, and settings code -- because they cluster around the\nunix sockets and how on unix to tell if something is remote.  That\ngrouping may break if a platform needs something else, such as how to\nbehave when running under inetd vs launchctl vs <whatever>, for example.\n\n\n>   \tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o\n>   endif\n>   \n> diff --git a/compat/fsmonitor/fsm-health-darwin.c b/compat/fsmonitor/fsm-health-unix.c\n> similarity index 100%\n> rename from compat/fsmonitor/fsm-health-darwin.c\n> rename to compat/fsmonitor/fsm-health-unix.c\n> diff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-unix.c\n> similarity index 89%\n> rename from compat/fsmonitor/fsm-ipc-darwin.c\n> rename to compat/fsmonitor/fsm-ipc-unix.c\n> index ce843d63348..3ba3b9e17ed 100644\n> --- a/compat/fsmonitor/fsm-ipc-darwin.c\n> +++ b/compat/fsmonitor/fsm-ipc-unix.c\n> @@ -10,7 +10,7 @@ static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n>   const char *fsmonitor_ipc__get_path(struct repository *r)\n>   {\n>   \tstatic const char *ipc_path = NULL;\n> -\tSHA_CTX sha1ctx;\n> +\tgit_SHA_CTX sha1ctx;\n\nCould we put these in a separate commit.  They're not related\nto the file renaming focus of this commit.\n\n>   \tchar *sock_dir = NULL;\n>   \tstruct strbuf ipc_file = STRBUF_INIT;\n>   \tunsigned char hash[SHA_DIGEST_LENGTH];\n> @@ -28,9 +28,9 @@ const char *fsmonitor_ipc__get_path(struct repository *r)\n>   \t\treturn ipc_path;\n>   \t}\n>   \n> -\tSHA1_Init(&sha1ctx);\n> -\tSHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n> -\tSHA1_Final(hash, &sha1ctx);\n> +\tgit_SHA1_Init(&sha1ctx);\n> +\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n> +\tgit_SHA1_Final(hash, &sha1ctx);\n>   \n>   \trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n>   \n> diff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-unix.c\n> similarity index 100%\n> rename from compat/fsmonitor/fsm-settings-darwin.c\n> rename to compat/fsmonitor/fsm-settings-unix.c\n> diff --git a/config.mak.uname b/config.mak.uname\n> index d63629fe807..d454cec47c4 100644\n> --- a/config.mak.uname\n> +++ b/config.mak.uname\n> @@ -68,6 +68,7 @@ ifeq ($(uname_S),Linux)\n>   \tifneq ($(findstring .el7.,$(uname_R)),)\n>   \t\tBASIC_CFLAGS += -std=c99\n>   \tendif\n> +\n>   endif\n>   ifeq ($(uname_S),GNU/kFreeBSD)\n>   \tHAVE_ALLOCA_H = YesPlease\n> @@ -165,6 +166,7 @@ ifeq ($(uname_S),Darwin)\n>   \tifndef NO_UNIX_SOCKETS\n>   \tFSMONITOR_DAEMON_BACKEND = darwin\n>   \tFSMONITOR_OS_SETTINGS = darwin\n> +\tFSMONITOR_DAEMON_COMMON = unix\n>   \tendif\n>   \tendif\n>   \n> @@ -453,6 +455,7 @@ ifeq ($(uname_S),Windows)\n>   \t# support it.\n>   \tFSMONITOR_DAEMON_BACKEND = win32\n>   \tFSMONITOR_OS_SETTINGS = win32\n> +\tFSMONITOR_DAEMON_COMMON = win32\n>   \n>   \tNO_SVN_TESTS = YesPlease\n>   \tRUNTIME_PREFIX = YesPlease\n> @@ -645,6 +648,7 @@ ifeq ($(uname_S),MINGW)\n>   \t# support it.\n>   \tFSMONITOR_DAEMON_BACKEND = win32\n>   \tFSMONITOR_OS_SETTINGS = win32\n> +\tFSMONITOR_DAEMON_COMMON = win32\n>   \n>   \tRUNTIME_PREFIX = YesPlease\n>   \tHAVE_WPGMPTR = YesWeDo\n> diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\n> index 787738e6fa3..0b26a1a36e3 100644\n> --- a/contrib/buildsystems/CMakeLists.txt\n> +++ b/contrib/buildsystems/CMakeLists.txt\n> @@ -315,13 +315,13 @@ if(SUPPORTS_SIMPLE_IPC)\n>   \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c)\n>   \telseif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n>   \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n> +\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-unix.c)\n> +\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-unix.c)\n>   \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n> -\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)\n> -\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-darwin.c)\n>   \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c)\n>   \n>   \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n> -\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)\n> +\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-unix.c)\n>   \tendif()\n>   endif()\n>   \n> \n"},{"id":"464538","messageId":"xmqqilkr1j60.fsf@gitster.g","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-10T21:08:07Z","receivedAt":"2022-10-10T21:08:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\n> Windows and Mac OS.\n>\n> This patch set builds upon previous work for done for Windows and Mac OS\n> (first 6 patches) to implement a fsmonitor back-end for Linux based on the\n> Linux inotify API. inotify differs significantly from the equivalent Windows\n> and Mac OS APIs in that a watch must be registered for every directory of\n> interest (rather than a singular watch at the root of the directory tree)\n> and special care must be taken to handle directory renames correctly.\n\nI am seeing occasional breakages of t7527.16 that does not seem to\nreproduce reliably.\n"},{"id":"464979","messageId":"BL0PR05MB557182AE46A91AD0CEF15CBDD9249@BL0PR05MB5571.namprd05.prod.outlook.com","threadId":"58588","inReplyTo":"221010.86sfjwouqx.gmgdl@evledraar.gmail.com","subject":"RE: [PATCH 08/12] fsmonitor: determine if filesystem is local or remote","fromName":"Eric DeCosta","fromEmail":"edecosta@mathworks.com","sentAt":"2022-10-14T19:38:47Z","receivedAt":"2022-10-14T19:39:01Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"\n\n> -----Original Message-----\n> From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Sent: Monday, October 10, 2022 6:04 AM\n> To: Eric DeCosta via GitGitGadget <gitgitgadget@gmail.com>\n> Cc: git@vger.kernel.org; Eric DeCosta <edecosta@mathworks.com>\n> Subject: Re: [PATCH 08/12] fsmonitor: determine if filesystem is local or\n> remote\n> \n> \n> On Sun, Oct 09 2022, Eric DeCosta via GitGitGadget wrote:\n> \n> > From: Eric DeCosta <edecosta@mathworks.com>\n> >\n> > Compare the given path to the mounted filesystems. Find the mount that\n> > is the longest prefix of the path (if any) and determine if that mount\n> > is on a local or remote filesystem.\n> >\n> > Signed-off-by: Eric DeCosta <edecosta@mathworks.com>\n> > ---\n> > compat/fsmonitor/fsm-path-utils-linux.c | 163\n> ++++++++++++++++++++++++\n> > 1 file changed, 163 insertions(+)\n> > create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n> >\n> > diff --git a/compat/fsmonitor/fsm-path-utils-linux.c\n> > b/compat/fsmonitor/fsm-path-utils-linux.c\n> > new file mode 100644\n> > index 00000000000..369692a788f\n> > --- /dev/null\n> > +++ b/compat/fsmonitor/fsm-path-utils-linux.c\n> > @@ -0,0 +1,163 @@\n> > +#include \"fsmonitor.h\"\n> > +#include \"fsmonitor-path-utils.h\"\n> > +#include <errno.h>\n> > +#include <mntent.h>\n> > +#include <sys/mount.h>\n> > +#include <sys/statvfs.h>\n> > +\n> > +/*\n> > + * https://github.com/coreutils/gnulib/blob/master/lib/mountlist.c\n> > +<https://protect-\n> us.mimecast.com/s/wKJsC9rj7lc3WKmYUOq9E9?domain=gith\n> > +ub.com>\n> > + */\n> > +#ifndef ME_REMOTE\n> > +/* A file system is \"remote\" if its Fs_name contains a ':'\n> > + or if (it is of type (smbfs or cifs) and its Fs_name starts with\n> > +'//')  or if it is of any other of the listed types  or Fs_name is\n> > +equal to \"-hosts\" (used by autofs to mount remote fs).\n> > + \"VM\" file systems like prl_fs or vboxsf are not considered remote\n> > +here. */ # define ME_REMOTE(Fs_name, Fs_type) \\  (strchr (Fs_name,\n> > +':') != NULL \\\n> > + || ((Fs_name)[0] == '/' \\\n> > + && (Fs_name)[1] == '/' \\\n> > + && (strcmp (Fs_type, \"smbfs\") == 0 \\\n> > + || strcmp (Fs_type, \"smb3\") == 0 \\\n> > + || strcmp (Fs_type, \"cifs\") == 0)) \\ strcmp (Fs_type, \"acfs\") == 0 \\\n> > + || strcmp (Fs_type, \"afs\") == 0 \\ strcmp (Fs_type, \"coda\") == 0 \\\n> > + || strcmp (Fs_type, \"auristorfs\") == 0 \\ strcmp (Fs_type, \"fhgfs\")\n> > + || == 0 \\ strcmp (Fs_type, \"gpfs\") == 0 \\ strcmp (Fs_type, \"ibrix\")\n> > + || == 0 \\ strcmp (Fs_type, \"ocfs2\") == 0 \\ strcmp (Fs_type, \"vxfs\")\n> > + || == 0 \\ strcmp (\"-hosts\", Fs_name) == 0)\n> > +#endif\n> \n> So, this is just copy/pasted GPLv3 code into our GPLv2-only codebase?:\n> https://github.com/coreutils/gnulib/blob/cd1fdabe8b66c102124b6a5b0c97d\n> ded20246b46/lib/mountlist.c#L230-L247 <https://protect-\n> us.mimecast.com/s/zxPbC0RMy1hoXW2rCOSXJD?domain=github.com>\n> \nYes. I was hoping for some guidance as to what to do with ME_REMOTE.\n\nI also found it, verbatim,  here in midnight commander:\nhttps://github.com/MidnightCommander/mc/blob/e48cd98ac13a9b4366bd88287f632413766b967f/src/filemanager/mountlist.c#L258-L281\n\nBut that's just another GPLv3 code base.\n\n> > +static struct mntent *find_mount(const char *path, const struct\n> > +statvfs *fs) {  const char *const mounts = \"/proc/mounts\";  const\n> > +char *rp = real_pathdup(path, 1);  struct mntent *ment = NULL;\n> > +struct mntent *found = NULL;  struct statvfs mntfs;  FILE *fp;  int\n> > +dlen, plen, flen = 0;\n> > +\n> > + fp = setmntent(mounts, \"r\");\n> > + if (!fp) {\n> > + error_errno(_(\"setmntent('%s') failed\"), mounts); return NULL;\n> \n> This code would probably be nicer if you returned int, and passed a pointer\n> to a struct to populate as a paremeter. Then you could just \"return error...\"\n> for this and the below cases.\n> \n> > + }\n> > +\n> > + plen = strlen(rp);\n> > +\n> > + /* read all the mount information and compare to path */ while\n> > + ((ment = getmntent(fp)) != NULL) { if (statvfs(ment->mnt_dir,\n> > + &mntfs)) { switch (errno) { case EPERM:\n> > + case ESRCH:\n> > + case EACCES:\n> > + continue;\n> > + default:\n> > + error_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n> > + endmntent(fp);is return NULL; } }\n> > +\n> > + /* is mount on the same filesystem and is a prefix of the path */ if\n> > + ((fs->f_fsid == mntfs.f_fsid) && !strncmp(ment->mnt_dir, rp,\n> > + strlen(ment->mnt_dir))) { dlen = strlen(ment->mnt_dir); if (dlen >\n> > + plen) continue;\n> > + /*\n> > + * root is always a potential match; otherwise look for\n> > + * directory prefix\n> > + */\n> > + if ((dlen == 1 && ment->mnt_dir[0] == '/') || (dlen > flen &&\n> > + (!rp[dlen] || rp[dlen] == '/'))) { flen = dlen;\n> > + /*\n> > + * https://man7.org/linux/man-pages/man3/getmntent.3.html\n> > + <https://protect-\n> us.mimecast.com/s/aOmSCgJyVrT01WlYc76tSR?domain=man\n> > + 7.org>\n> > + *\n> > + * The pointer points to a static area of memory which is\n> > + * overwritten by subsequent calls to getmntent().\n> > + */\n> > + if (!found)\n> > + CALLOC_ARRAY(found, 1);\n> \n> It seems we never populate >1 of these, so don't you just want xmalloc(). Or\n> actually...\n> \n> > + free(found->mnt_dir);\n> > + free(found->mnt_type);\n> > + free(found->mnt_fsname);\n> > + found->mnt_dir = xmemdupz(ment->mnt_dir, strlen(ment->mnt_dir));\n> > + found->mnt_type = xmemdupz(ment->mnt_type, strlen(ment-\n> >mnt_type));\n> > + found->mnt_fsname = xmemdupz(ment->mnt_fsname,\n> > + found->strlen(ment->mnt_fsname));\n> \n> Don't mix mem*() and str*(). In this case we need the string to be '\\0'\n> delimited, so use xstrndup().\n> \n> And once we do that, we might wonder why we're explicitly finding the '\\0',\n> just to pass it to the xstrn*() function, when we can just do:\n> \n> found->mnt_dir = xstrdup(ment->mnt_dir);\n> ...\n> \n> Which would AFAICT be equivalent to what you're doing here.\n> \n> > + }\n> > + }\n> > + }\n> > + endmntent(fp);\n> > +\n> > + return found;\n> > +}\n> > +\n> > +int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n> > +{  struct mntent *ment;\n> \n> ...make this (or above) a \"struct mntent ment\", then pass &ment down, so\n> we can fill that struct? Dunno...\n> \n> > + struct statvfs fs;\n> > +\n> > + if (statvfs(path, &fs))\n> > + return error_errno(_(\"statvfs('%s') failed\"), path);\n> > +\n> > + ment = find_mount(path, &fs);\n> > + if (!ment)\n> > + return -1;\n> > +\n> > + trace_printf_key(&trace_fsmonitor,\n> > + \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\", path, fs.f_flag,\n> > + ment->mnt_type, ment->mnt_fsname);\n> > +\n> > + if (ME_REMOTE(ment->mnt_fsname, ment->mnt_type)) fs_info-\n> >is_remote\n> > + = 1; else fs_info->is_remote = 0;\n> \n> Shorter:\n> \n> fs_info->is_remote = ME_REMOTE(ment->mnt_fsname, ment->mnt_type);\n\n"},{"id":"464993","messageId":"cd46bed37c36eab75cabecd277cd2d09eec629b4.1665783944.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 01/12] fsmonitor: refactor filesystem checks to common interface","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:33Z","receivedAt":"2022-10-14T21:45:57Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nProvide a common interface for getting basic filesystem information\nincluding filesystem type and whether the filesystem is remote.\n\nRefactor existing code for getting basic filesystem info and detecting\nremote file systems to the new interface.\n\nRefactor filesystem checks to leverage new interface. For macOS,\nerror-out if the Unix Domain socket (UDS) file is on a remote\nfilesystem.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Makefile                                 |   1 +\n compat/fsmonitor/fsm-path-utils-darwin.c |  43 ++++++\n compat/fsmonitor/fsm-path-utils-win32.c  | 128 +++++++++++++++++\n compat/fsmonitor/fsm-settings-darwin.c   |  69 +++------\n compat/fsmonitor/fsm-settings-win32.c    | 172 +----------------------\n contrib/buildsystems/CMakeLists.txt      |   2 +\n fsmonitor-path-utils.h                   |  26 ++++\n fsmonitor-settings.c                     |  50 +++++++\n 8 files changed, 272 insertions(+), 219 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-path-utils-darwin.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-win32.c\n create mode 100644 fsmonitor-path-utils.h\n\ndiff --git a/Makefile b/Makefile\nindex 6bfb62cbe94..99a3d9e6322 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2043,6 +2043,7 @@ endif\n ifdef FSMONITOR_OS_SETTINGS\n \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o\n+\tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o\n endif\n \n ifeq ($(TCLTK_PATH),)\ndiff --git a/compat/fsmonitor/fsm-path-utils-darwin.c b/compat/fsmonitor/fsm-path-utils-darwin.c\nnew file mode 100644\nindex 00000000000..d46d7f13538\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-path-utils-darwin.c\n@@ -0,0 +1,43 @@\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-path-utils.h\"\n+#include <sys/param.h>\n+#include <sys/mount.h>\n+\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n+{\n+\tstruct statfs fs;\n+\tif (statfs(path, &fs) == -1) {\n+\t\tint saved_errno = errno;\n+\t\ttrace_printf_key(&trace_fsmonitor, \"statfs('%s') failed: %s\",\n+\t\t\t\t path, strerror(saved_errno));\n+\t\terrno = saved_errno;\n+\t\treturn -1;\n+\t}\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t \"statfs('%s') [type 0x%08x][flags 0x%08x] '%s'\",\n+\t\t\t path, fs.f_type, fs.f_flags, fs.f_fstypename);\n+\n+\tif (!(fs.f_flags & MNT_LOCAL))\n+\t\tfs_info->is_remote = 1;\n+\telse\n+\t\tfs_info->is_remote = 0;\n+\n+\tfs_info->typename = xstrdup(fs.f_fstypename);\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"'%s' is_remote: %d\",\n+\t\t\t\tpath, fs_info->is_remote);\n+\treturn 0;\n+}\n+\n+int fsmonitor__is_fs_remote(const char *path)\n+{\n+\tstruct fs_info fs;\n+\tif (fsmonitor__get_fs_info(path, &fs))\n+\t\treturn -1;\n+\n+\tfree(fs.typename);\n+\n+\treturn fs.is_remote;\n+}\ndiff --git a/compat/fsmonitor/fsm-path-utils-win32.c b/compat/fsmonitor/fsm-path-utils-win32.c\nnew file mode 100644\nindex 00000000000..a90b8f7925b\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-path-utils-win32.c\n@@ -0,0 +1,128 @@\n+#include \"cache.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-path-utils.h\"\n+\n+/*\n+ * Check remote working directory protocol.\n+ *\n+ * Return -1 if client machine cannot get remote protocol information.\n+ */\n+static int check_remote_protocol(wchar_t *wpath)\n+{\n+\tHANDLE h;\n+\tFILE_REMOTE_PROTOCOL_INFO proto_info;\n+\n+\th = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,\n+\t\t\tFILE_FLAG_BACKUP_SEMANTICS, NULL);\n+\n+\tif (h == INVALID_HANDLE_VALUE) {\n+\t\terror(_(\"[GLE %ld] unable to open for read '%ls'\"),\n+\t\t      GetLastError(), wpath);\n+\t\treturn -1;\n+\t}\n+\n+\tif (!GetFileInformationByHandleEx(h, FileRemoteProtocolInfo,\n+\t\t&proto_info, sizeof(proto_info))) {\n+\t\terror(_(\"[GLE %ld] unable to get protocol information for '%ls'\"),\n+\t\t      GetLastError(), wpath);\n+\t\tCloseHandle(h);\n+\t\treturn -1;\n+\t}\n+\n+\tCloseHandle(h);\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"check_remote_protocol('%ls') remote protocol %#8.8lx\",\n+\t\t\t\twpath, proto_info.Protocol);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Notes for testing:\n+ *\n+ * (a) Windows allows a network share to be mapped to a drive letter.\n+ *     (This is the normal method to access it.)\n+ *\n+ *     $ NET USE Z: \\\\server\\share\n+ *     $ git -C Z:/repo status\n+ *\n+ * (b) Windows allows a network share to be referenced WITHOUT mapping\n+ *     it to drive letter.\n+ *\n+ *     $ NET USE \\\\server\\share\\dir\n+ *     $ git -C //server/share/repo status\n+ *\n+ * (c) Windows allows \"SUBST\" to create a fake drive mapping to an\n+ *     arbitrary path (which may be remote)\n+ *\n+ *     $ SUBST Q: Z:\\repo\n+ *     $ git -C Q:/ status\n+ *\n+ * (d) Windows allows a directory symlink to be created on a local\n+ *     file system that points to a remote repo.\n+ *\n+ *     $ mklink /d ./link //server/share/repo\n+ *     $ git -C ./link status\n+ */\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n+{\n+\twchar_t wpath[MAX_PATH];\n+\twchar_t wfullpath[MAX_PATH];\n+\tsize_t wlen;\n+\tUINT driveType;\n+\n+\t/*\n+\t * Do everything in wide chars because the drive letter might be\n+\t * a multi-byte sequence.  See win32_has_dos_drive_prefix().\n+\t */\n+\tif (xutftowcs_path(wpath, path) < 0) {\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * GetDriveTypeW() requires a final slash.  We assume that the\n+\t * worktree pathname points to an actual directory.\n+\t */\n+\twlen = wcslen(wpath);\n+\tif (wpath[wlen - 1] != L'\\\\' && wpath[wlen - 1] != L'/') {\n+\t\twpath[wlen++] = L'\\\\';\n+\t\twpath[wlen] = 0;\n+\t}\n+\n+\t/*\n+\t * Normalize the path.  If nothing else, this converts forward\n+\t * slashes to backslashes.  This is essential to get GetDriveTypeW()\n+\t * correctly handle some UNC \"\\\\server\\share\\...\" paths.\n+\t */\n+\tif (!GetFullPathNameW(wpath, MAX_PATH, wfullpath, NULL)) {\n+\t\treturn -1;\n+\t}\n+\n+\tdriveType = GetDriveTypeW(wfullpath);\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t \"DriveType '%s' L'%ls' (%u)\",\n+\t\t\t path, wfullpath, driveType);\n+\n+\tif (driveType == DRIVE_REMOTE) {\n+\t\tfs_info->is_remote = 1;\n+\t\tif (check_remote_protocol(wfullpath) < 0)\n+\t\t\treturn -1;\n+\t} else {\n+\t\tfs_info->is_remote = 0;\n+\t}\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"'%s' is_remote: %d\",\n+\t\t\t\tpath, fs_info->is_remote);\n+\n+\treturn 0;\n+}\n+\n+int fsmonitor__is_fs_remote(const char *path)\n+{\n+\tstruct fs_info fs;\n+\tif (fsmonitor__get_fs_info(path, &fs))\n+\t\treturn -1;\n+\treturn fs.is_remote;\n+}\ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c\nindex efc732c0f31..699f0b272e6 100644\n--- a/compat/fsmonitor/fsm-settings-darwin.c\n+++ b/compat/fsmonitor/fsm-settings-darwin.c\n@@ -1,32 +1,10 @@\n-#include \"cache.h\"\n #include \"config.h\"\n-#include \"repository.h\"\n-#include \"fsmonitor-settings.h\"\n #include \"fsmonitor.h\"\n-#include <sys/param.h>\n-#include <sys/mount.h>\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-settings.h\"\n+#include \"fsmonitor-path-utils.h\"\n \n-/*\n- * [1] Remote working directories are problematic for FSMonitor.\n- *\n- * The underlying file system on the server machine and/or the remote\n- * mount type (NFS, SAMBA, etc.) dictates whether notification events\n- * are available at all to remote client machines.\n- *\n- * Kernel differences between the server and client machines also\n- * dictate the how (buffering, frequency, de-dup) the events are\n- * delivered to client machine processes.\n- *\n- * A client machine (such as a laptop) may choose to suspend/resume\n- * and it is unclear (without lots of testing) whether the watcher can\n- * resync after a resume.  We might be able to treat this as a normal\n- * \"events were dropped by the kernel\" event and do our normal \"flush\n- * and resync\" --or-- we might need to close the existing (zombie?)\n- * notification fd and create a new one.\n- *\n- * In theory, the above issues need to be addressed whether we are\n- * using the Hook or IPC API.\n- *\n+ /*\n  * For the builtin FSMonitor, we create the Unix domain socket for the\n  * IPC in the .git directory.  If the working directory is remote,\n  * then the socket will be created on the remote file system.  This\n@@ -38,42 +16,35 @@\n  * be taken to ensure that $HOME is actually local and not a managed\n  * file share.)\n  *\n- * So (for now at least), mark remote working directories as\n- * incompatible.\n- *\n- *\n- * [2] FAT32 and NTFS working directories are problematic too.\n+ * FAT32 and NTFS working directories are problematic too.\n  *\n  * The builtin FSMonitor uses a Unix domain socket in the .git\n  * directory for IPC.  These Windows drive formats do not support\n  * Unix domain sockets, so mark them as incompatible for the daemon.\n  *\n  */\n-static enum fsmonitor_reason check_volume(struct repository *r)\n+static enum fsmonitor_reason check_uds_volume(struct repository *r)\n {\n-\tstruct statfs fs;\n+\tstruct fs_info fs;\n+\tconst char *ipc_path = fsmonitor_ipc__get_path();\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n \n-\tif (statfs(r->worktree, &fs) == -1) {\n-\t\tint saved_errno = errno;\n-\t\ttrace_printf_key(&trace_fsmonitor, \"statfs('%s') failed: %s\",\n-\t\t\t\t r->worktree, strerror(saved_errno));\n-\t\terrno = saved_errno;\n+\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n+\t\tstrbuf_release(&path);\n \t\treturn FSMONITOR_REASON_ERROR;\n \t}\n \n-\ttrace_printf_key(&trace_fsmonitor,\n-\t\t\t \"statfs('%s') [type 0x%08x][flags 0x%08x] '%s'\",\n-\t\t\t r->worktree, fs.f_type, fs.f_flags, fs.f_fstypename);\n-\n-\tif (!(fs.f_flags & MNT_LOCAL))\n-\t\treturn FSMONITOR_REASON_REMOTE;\n+\tstrbuf_release(&path);\n \n-\tif (!strcmp(fs.f_fstypename, \"msdos\")) /* aka FAT32 */\n-\t\treturn FSMONITOR_REASON_NOSOCKETS;\n-\n-\tif (!strcmp(fs.f_fstypename, \"ntfs\"))\n+\tif (fs.is_remote ||\n+\t\t!strcmp(fs.typename, \"msdos\") ||\n+\t\t!strcmp(fs.typename, \"ntfs\")) {\n+\t\tfree(fs.typename);\n \t\treturn FSMONITOR_REASON_NOSOCKETS;\n+\t}\n \n+\tfree(fs.typename);\n \treturn FSMONITOR_REASON_OK;\n }\n \n@@ -81,7 +52,7 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r)\n {\n \tenum fsmonitor_reason reason;\n \n-\treason = check_volume(r);\n+\treason = check_uds_volume(r);\n \tif (reason != FSMONITOR_REASON_OK)\n \t\treturn reason;\n \ndiff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c\nindex e5ec5b0a9f7..d88b06ae610 100644\n--- a/compat/fsmonitor/fsm-settings-win32.c\n+++ b/compat/fsmonitor/fsm-settings-win32.c\n@@ -1,8 +1,9 @@\n #include \"cache.h\"\n #include \"config.h\"\n #include \"repository.h\"\n-#include \"fsmonitor-settings.h\"\n #include \"fsmonitor.h\"\n+#include \"fsmonitor-settings.h\"\n+#include \"fsmonitor-path-utils.h\"\n \n /*\n  * VFS for Git is incompatible with FSMonitor.\n@@ -24,171 +25,6 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r)\n \treturn FSMONITOR_REASON_OK;\n }\n \n-/*\n- * Check if monitoring remote working directories is allowed.\n- *\n- * By default, monitoring remote working directories is\n- * disabled.  Users may override this behavior in enviroments where\n- * they have proper support.\n- */\n-static int check_config_allowremote(struct repository *r)\n-{\n-\tint allow;\n-\n-\tif (!repo_config_get_bool(r, \"fsmonitor.allowremote\", &allow))\n-\t\treturn allow;\n-\n-\treturn -1; /* fsmonitor.allowremote not set */\n-}\n-\n-/*\n- * Check remote working directory protocol.\n- *\n- * Error if client machine cannot get remote protocol information.\n- */\n-static int check_remote_protocol(wchar_t *wpath)\n-{\n-\tHANDLE h;\n-\tFILE_REMOTE_PROTOCOL_INFO proto_info;\n-\n-\th = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,\n-\t\t\tFILE_FLAG_BACKUP_SEMANTICS, NULL);\n-\n-\tif (h == INVALID_HANDLE_VALUE) {\n-\t\terror(_(\"[GLE %ld] unable to open for read '%ls'\"),\n-\t\t      GetLastError(), wpath);\n-\t\treturn -1;\n-\t}\n-\n-\tif (!GetFileInformationByHandleEx(h, FileRemoteProtocolInfo,\n-\t\t&proto_info, sizeof(proto_info))) {\n-\t\terror(_(\"[GLE %ld] unable to get protocol information for '%ls'\"),\n-\t\t      GetLastError(), wpath);\n-\t\tCloseHandle(h);\n-\t\treturn -1;\n-\t}\n-\n-\tCloseHandle(h);\n-\n-\ttrace_printf_key(&trace_fsmonitor,\n-\t\t\t\t\"check_remote_protocol('%ls') remote protocol %#8.8lx\",\n-\t\t\t\twpath, proto_info.Protocol);\n-\n-\treturn 0;\n-}\n-\n-/*\n- * Remote working directories are problematic for FSMonitor.\n- *\n- * The underlying file system on the server machine and/or the remote\n- * mount type dictates whether notification events are available at\n- * all to remote client machines.\n- *\n- * Kernel differences between the server and client machines also\n- * dictate the how (buffering, frequency, de-dup) the events are\n- * delivered to client machine processes.\n- *\n- * A client machine (such as a laptop) may choose to suspend/resume\n- * and it is unclear (without lots of testing) whether the watcher can\n- * resync after a resume.  We might be able to treat this as a normal\n- * \"events were dropped by the kernel\" event and do our normal \"flush\n- * and resync\" --or-- we might need to close the existing (zombie?)\n- * notification fd and create a new one.\n- *\n- * In theory, the above issues need to be addressed whether we are\n- * using the Hook or IPC API.\n- *\n- * So (for now at least), mark remote working directories as\n- * incompatible.\n- *\n- * Notes for testing:\n- *\n- * (a) Windows allows a network share to be mapped to a drive letter.\n- *     (This is the normal method to access it.)\n- *\n- *     $ NET USE Z: \\\\server\\share\n- *     $ git -C Z:/repo status\n- *\n- * (b) Windows allows a network share to be referenced WITHOUT mapping\n- *     it to drive letter.\n- *\n- *     $ NET USE \\\\server\\share\\dir\n- *     $ git -C //server/share/repo status\n- *\n- * (c) Windows allows \"SUBST\" to create a fake drive mapping to an\n- *     arbitrary path (which may be remote)\n- *\n- *     $ SUBST Q: Z:\\repo\n- *     $ git -C Q:/ status\n- *\n- * (d) Windows allows a directory symlink to be created on a local\n- *     file system that points to a remote repo.\n- *\n- *     $ mklink /d ./link //server/share/repo\n- *     $ git -C ./link status\n- */\n-static enum fsmonitor_reason check_remote(struct repository *r)\n-{\n-\tint ret;\n-\twchar_t wpath[MAX_PATH];\n-\twchar_t wfullpath[MAX_PATH];\n-\tsize_t wlen;\n-\tUINT driveType;\n-\n-\t/*\n-\t * Do everything in wide chars because the drive letter might be\n-\t * a multi-byte sequence.  See win32_has_dos_drive_prefix().\n-\t */\n-\tif (xutftowcs_path(wpath, r->worktree) < 0)\n-\t\treturn FSMONITOR_REASON_ERROR;\n-\n-\t/*\n-\t * GetDriveTypeW() requires a final slash.  We assume that the\n-\t * worktree pathname points to an actual directory.\n-\t */\n-\twlen = wcslen(wpath);\n-\tif (wpath[wlen - 1] != L'\\\\' && wpath[wlen - 1] != L'/') {\n-\t\twpath[wlen++] = L'\\\\';\n-\t\twpath[wlen] = 0;\n-\t}\n-\n-\t/*\n-\t * Normalize the path.  If nothing else, this converts forward\n-\t * slashes to backslashes.  This is essential to get GetDriveTypeW()\n-\t * correctly handle some UNC \"\\\\server\\share\\...\" paths.\n-\t */\n-\tif (!GetFullPathNameW(wpath, MAX_PATH, wfullpath, NULL))\n-\t\treturn FSMONITOR_REASON_ERROR;\n-\n-\tdriveType = GetDriveTypeW(wfullpath);\n-\ttrace_printf_key(&trace_fsmonitor,\n-\t\t\t \"DriveType '%s' L'%ls' (%u)\",\n-\t\t\t r->worktree, wfullpath, driveType);\n-\n-\tif (driveType == DRIVE_REMOTE) {\n-\t\ttrace_printf_key(&trace_fsmonitor,\n-\t\t\t\t \"check_remote('%s') true\",\n-\t\t\t\t r->worktree);\n-\n-\t\tret = check_remote_protocol(wfullpath);\n-\t\tif (ret < 0)\n-\t\t\treturn FSMONITOR_REASON_ERROR;\n-\n-\t\tswitch (check_config_allowremote(r)) {\n-\t\tcase 0: /* config overrides and disables */\n-\t\t\treturn FSMONITOR_REASON_REMOTE;\n-\t\tcase 1: /* config overrides and enables */\n-\t\t\treturn FSMONITOR_REASON_OK;\n-\t\tdefault:\n-\t\t\tbreak; /* config has no opinion */\n-\t\t}\n-\n-\t\treturn FSMONITOR_REASON_REMOTE;\n-\t}\n-\n-\treturn FSMONITOR_REASON_OK;\n-}\n-\n enum fsmonitor_reason fsm_os__incompatible(struct repository *r)\n {\n \tenum fsmonitor_reason reason;\n@@ -197,9 +33,5 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r)\n \tif (reason != FSMONITOR_REASON_OK)\n \t\treturn reason;\n \n-\treason = check_remote(r);\n-\tif (reason != FSMONITOR_REASON_OK)\n-\t\treturn reason;\n-\n \treturn FSMONITOR_REASON_OK;\n }\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex ea2a531be87..5482a04b3ce 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -308,6 +308,7 @@ if(SUPPORTS_SIMPLE_IPC)\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-win32.c)\n \n \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c)\n@@ -315,6 +316,7 @@ if(SUPPORTS_SIMPLE_IPC)\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c)\n \n \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)\ndiff --git a/fsmonitor-path-utils.h b/fsmonitor-path-utils.h\nnew file mode 100644\nindex 00000000000..41edf5b934f\n--- /dev/null\n+++ b/fsmonitor-path-utils.h\n@@ -0,0 +1,26 @@\n+#ifndef FSM_PATH_UTILS_H\n+#define FSM_PATH_UTILS_H\n+\n+struct fs_info {\n+\tint is_remote;\n+\tchar *typename;\n+};\n+\n+/*\n+ * Get some basic filesystem informtion for the given path\n+ *\n+ * The caller owns the storage that is occupied by fs_info and\n+ * is responsible for releasing it.\n+ *\n+ * Returns -1 on error, zero otherwise.\n+ */\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info);\n+\n+/*\n+ * Determines if the filesystem that path resides on is remote.\n+ *\n+ * Returns -1 on error, 0 if not remote, 1 if remote.\n+ */\n+int fsmonitor__is_fs_remote(const char *path);\n+\n+#endif\ndiff --git a/fsmonitor-settings.c b/fsmonitor-settings.c\nindex 464424a1e92..d288cbad479 100644\n--- a/fsmonitor-settings.c\n+++ b/fsmonitor-settings.c\n@@ -2,6 +2,7 @@\n #include \"config.h\"\n #include \"repository.h\"\n #include \"fsmonitor-settings.h\"\n+#include \"fsmonitor-path-utils.h\"\n \n /*\n  * We keep this structure defintion private and have getters\n@@ -13,6 +14,52 @@ struct fsmonitor_settings {\n \tchar *hook_path;\n };\n \n+/*\n+ * Remote working directories are problematic for FSMonitor.\n+ *\n+ * The underlying file system on the server machine and/or the remote\n+ * mount type dictates whether notification events are available at\n+ * all to remote client machines.\n+ *\n+ * Kernel differences between the server and client machines also\n+ * dictate the how (buffering, frequency, de-dup) the events are\n+ * delivered to client machine processes.\n+ *\n+ * A client machine (such as a laptop) may choose to suspend/resume\n+ * and it is unclear (without lots of testing) whether the watcher can\n+ * resync after a resume.  We might be able to treat this as a normal\n+ * \"events were dropped by the kernel\" event and do our normal \"flush\n+ * and resync\" --or-- we might need to close the existing (zombie?)\n+ * notification fd and create a new one.\n+ *\n+ * In theory, the above issues need to be addressed whether we are\n+ * using the Hook or IPC API.\n+ *\n+ * So (for now at least), mark remote working directories as\n+ * incompatible unless 'fsmonitor.allowRemote' is true.\n+ *\n+ */\n+#ifdef HAVE_FSMONITOR_OS_SETTINGS\n+static enum fsmonitor_reason check_remote(struct repository *r)\n+{\n+\tint allow_remote = -1; /* -1 unset, 0 not allowed, 1 allowed */\n+\tint is_remote = fsmonitor__is_fs_remote(r->worktree);\n+\n+\tswitch (is_remote) {\n+\t\tcase 0:\n+\t\t\treturn FSMONITOR_REASON_OK;\n+\t\tcase 1:\n+\t\t\trepo_config_get_bool(r, \"fsmonitor.allowremote\", &allow_remote);\n+\t\t\tif (allow_remote < 1)\n+\t\t\t\treturn FSMONITOR_REASON_REMOTE;\n+\t\t\telse\n+\t\t\t\treturn FSMONITOR_REASON_OK;\n+\t\tdefault:\n+\t\t\treturn FSMONITOR_REASON_ERROR;\n+\t}\n+}\n+#endif\n+\n static enum fsmonitor_reason check_for_incompatible(struct repository *r)\n {\n \tif (!r->worktree) {\n@@ -27,6 +74,9 @@ static enum fsmonitor_reason check_for_incompatible(struct repository *r)\n \t{\n \t\tenum fsmonitor_reason reason;\n \n+\t\treason = check_remote(r);\n+\t\tif (reason != FSMONITOR_REASON_OK)\n+\t\t\treturn reason;\n \t\treason = fsm_os__incompatible(r);\n \t\tif (reason != FSMONITOR_REASON_OK)\n \t\t\treturn reason;\n-- \ngitgitgadget\n\n"},{"id":"464994","messageId":"21d114bda4b515170ae43f01f9bb14befec611f2.1665783944.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 02/12] fsmonitor: relocate socket file if .git directory is remote","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:34Z","receivedAt":"2022-10-14T21:45:59Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nIf the .git directory is on a remote filesystem, create the socket\nfile in 'fsmonitor.socketDir' if it is defined, else create it in $HOME.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Makefile                               |  1 +\n builtin/fsmonitor--daemon.c            |  3 +-\n compat/fsmonitor/fsm-ipc-darwin.c      | 52 ++++++++++++++++++++++++++\n compat/fsmonitor/fsm-ipc-win32.c       |  9 +++++\n compat/fsmonitor/fsm-settings-darwin.c |  2 +-\n contrib/buildsystems/CMakeLists.txt    |  2 +\n fsmonitor-ipc.c                        | 18 ++++-----\n fsmonitor-ipc.h                        |  4 +-\n 8 files changed, 78 insertions(+), 13 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-ipc-darwin.c\n create mode 100644 compat/fsmonitor/fsm-ipc-win32.c\n\ndiff --git a/Makefile b/Makefile\nindex 99a3d9e6322..9553d590824 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2038,6 +2038,7 @@ ifdef FSMONITOR_DAEMON_BACKEND\n \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o\n+\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n endif\n \n ifdef FSMONITOR_OS_SETTINGS\ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex c69da93eceb..6e417846d8b 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -1343,7 +1343,8 @@ static int fsmonitor_run_daemon(void)\n \t * directory.)\n \t */\n \tstrbuf_init(&state.path_ipc, 0);\n-\tstrbuf_addstr(&state.path_ipc, absolute_path(fsmonitor_ipc__get_path()));\n+\tstrbuf_addstr(&state.path_ipc,\n+\t\tabsolute_path(fsmonitor_ipc__get_path(the_repository)));\n \n \t/*\n \t * Confirm that we can create platform-specific resources for the\ndiff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-darwin.c\nnew file mode 100644\nindex 00000000000..ce843d63348\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-ipc-darwin.c\n@@ -0,0 +1,52 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"strbuf.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n+\n+static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n+\n+const char *fsmonitor_ipc__get_path(struct repository *r)\n+{\n+\tstatic const char *ipc_path = NULL;\n+\tSHA_CTX sha1ctx;\n+\tchar *sock_dir = NULL;\n+\tstruct strbuf ipc_file = STRBUF_INIT;\n+\tunsigned char hash[SHA_DIGEST_LENGTH];\n+\n+\tif (!r)\n+\t\tBUG(\"No repository passed into fsmonitor_ipc__get_path\");\n+\n+\tif (ipc_path)\n+\t\treturn ipc_path;\n+\n+\n+\t/* By default the socket file is created in the .git directory */\n+\tif (fsmonitor__is_fs_remote(r->gitdir) < 1) {\n+\t\tipc_path = fsmonitor_ipc__get_default_path();\n+\t\treturn ipc_path;\n+\t}\n+\n+\tSHA1_Init(&sha1ctx);\n+\tSHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n+\tSHA1_Final(hash, &sha1ctx);\n+\n+\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n+\n+\t/* Create the socket file in either socketDir or $HOME */\n+\tif (sock_dir && *sock_dir) {\n+\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n+\t\t\t\t\tsock_dir, hash_to_hex(hash));\n+\t} else {\n+\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n+\t}\n+\tfree(sock_dir);\n+\n+\tipc_path = interpolate_path(ipc_file.buf, 1);\n+\tif (!ipc_path)\n+\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n+\n+\tstrbuf_release(&ipc_file);\n+\treturn ipc_path;\n+}\ndiff --git a/compat/fsmonitor/fsm-ipc-win32.c b/compat/fsmonitor/fsm-ipc-win32.c\nnew file mode 100644\nindex 00000000000..e08c505c148\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-ipc-win32.c\n@@ -0,0 +1,9 @@\n+#include \"config.h\"\n+#include \"fsmonitor-ipc.h\"\n+\n+const char *fsmonitor_ipc__get_path(struct repository *r) {\n+\tstatic char *ret;\n+\tif (!ret)\n+\t\tret = git_pathdup(\"fsmonitor--daemon.ipc\");\n+\treturn ret;\n+}\ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c\nindex 699f0b272e6..7241c4c22c9 100644\n--- a/compat/fsmonitor/fsm-settings-darwin.c\n+++ b/compat/fsmonitor/fsm-settings-darwin.c\n@@ -26,7 +26,7 @@\n static enum fsmonitor_reason check_uds_volume(struct repository *r)\n {\n \tstruct fs_info fs;\n-\tconst char *ipc_path = fsmonitor_ipc__get_path();\n+\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n \tstruct strbuf path = STRBUF_INIT;\n \tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n \ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 5482a04b3ce..787738e6fa3 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -308,6 +308,7 @@ if(SUPPORTS_SIMPLE_IPC)\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-win32.c)\n \n \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n@@ -316,6 +317,7 @@ if(SUPPORTS_SIMPLE_IPC)\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-darwin.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c)\n \n \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\ndiff --git a/fsmonitor-ipc.c b/fsmonitor-ipc.c\nindex 789e7397baa..c0f42301c84 100644\n--- a/fsmonitor-ipc.c\n+++ b/fsmonitor-ipc.c\n@@ -18,7 +18,7 @@ int fsmonitor_ipc__is_supported(void)\n \treturn 0;\n }\n \n-const char *fsmonitor_ipc__get_path(void)\n+const char *fsmonitor_ipc__get_path(struct repository *r)\n {\n \treturn NULL;\n }\n@@ -47,11 +47,9 @@ int fsmonitor_ipc__is_supported(void)\n \treturn 1;\n }\n \n-GIT_PATH_FUNC(fsmonitor_ipc__get_path, \"fsmonitor--daemon.ipc\")\n-\n enum ipc_active_state fsmonitor_ipc__get_state(void)\n {\n-\treturn ipc_get_active_state(fsmonitor_ipc__get_path());\n+\treturn ipc_get_active_state(fsmonitor_ipc__get_path(the_repository));\n }\n \n static int spawn_daemon(void)\n@@ -81,8 +79,8 @@ int fsmonitor_ipc__send_query(const char *since_token,\n \ttrace2_data_string(\"fsm_client\", NULL, \"query/command\", tok);\n \n try_again:\n-\tstate = ipc_client_try_connect(fsmonitor_ipc__get_path(), &options,\n-\t\t\t\t       &connection);\n+\tstate = ipc_client_try_connect(fsmonitor_ipc__get_path(the_repository),\n+\t\t\t\t\t\t&options, &connection);\n \n \tswitch (state) {\n \tcase IPC_STATE__LISTENING:\n@@ -117,13 +115,13 @@ try_again:\n \n \tcase IPC_STATE__INVALID_PATH:\n \t\tret = error(_(\"fsmonitor_ipc__send_query: invalid path '%s'\"),\n-\t\t\t    fsmonitor_ipc__get_path());\n+\t\t\t    fsmonitor_ipc__get_path(the_repository));\n \t\tgoto done;\n \n \tcase IPC_STATE__OTHER_ERROR:\n \tdefault:\n \t\tret = error(_(\"fsmonitor_ipc__send_query: unspecified error on '%s'\"),\n-\t\t\t    fsmonitor_ipc__get_path());\n+\t\t\t    fsmonitor_ipc__get_path(the_repository));\n \t\tgoto done;\n \t}\n \n@@ -149,8 +147,8 @@ int fsmonitor_ipc__send_command(const char *command,\n \toptions.wait_if_busy = 1;\n \toptions.wait_if_not_found = 0;\n \n-\tstate = ipc_client_try_connect(fsmonitor_ipc__get_path(), &options,\n-\t\t\t\t       &connection);\n+\tstate = ipc_client_try_connect(fsmonitor_ipc__get_path(the_repository),\n+\t\t\t\t\t\t&options, &connection);\n \tif (state != IPC_STATE__LISTENING) {\n \t\tdie(_(\"fsmonitor--daemon is not running\"));\n \t\treturn -1;\ndiff --git a/fsmonitor-ipc.h b/fsmonitor-ipc.h\nindex b6a7067c3af..8b489da762b 100644\n--- a/fsmonitor-ipc.h\n+++ b/fsmonitor-ipc.h\n@@ -3,6 +3,8 @@\n \n #include \"simple-ipc.h\"\n \n+struct repository;\n+\n /*\n  * Returns true if built-in file system monitor daemon is defined\n  * for this platform.\n@@ -16,7 +18,7 @@ int fsmonitor_ipc__is_supported(void);\n  *\n  * Returns NULL if the daemon is not supported on this platform.\n  */\n-const char *fsmonitor_ipc__get_path(void);\n+const char *fsmonitor_ipc__get_path(struct repository *r);\n \n /*\n  * Try to determine whether there is a `git-fsmonitor--daemon` process\n-- \ngitgitgadget\n\n"},{"id":"464995","messageId":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"[PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:32Z","receivedAt":"2022-10-14T21:46:07Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\nWindows and Mac OS.\n\nThis patch set builds upon previous work for done for Windows and Mac OS\n(first 6 patches) to implement a fsmonitor back-end for Linux based on the\nLinux inotify API. inotify differs significantly from the equivalent Windows\nand Mac OS APIs in that a watch must be registered for every directory of\ninterest (rather than a singular watch at the root of the directory tree)\nand special care must be taken to handle directory renames correctly.\n\nMore information about inotify:\nhttps://man7.org/linux/man-pages/man7/inotify.7.html\n\nv1 differs from v0:\n\n * Code review feedback\n * Update how and which code can be shared between Mac OS and Linux\n * Increase polling frequency to every 1ms (matches Mac OS)\n * Updates to t7527 to improve test stability\n\nEric DeCosta (12):\n  fsmonitor: refactor filesystem checks to common interface\n  fsmonitor: relocate socket file if .git directory is remote\n  fsmonitor: avoid socket location check if using hook\n  fsmonitor: deal with synthetic firmlinks on macOS\n  fsmonitor: check for compatability before communicating with fsmonitor\n  fsmonitor: add documentation for allowRemote and socketDir options\n  fsmonitor: prepare to share code between Mac OS and Linux\n  fsmonitor: determine if filesystem is local or remote\n  fsmonitor: implement filesystem change listener for Linux\n  fsmonitor: enable fsmonitor for Linux\n  fsmonitor: test updates\n  fsmonitor: update doc for Linux\n\n Documentation/config.txt                   |   2 +\n Documentation/config/fsmonitor--daemon.txt |  11 +\n Documentation/git-fsmonitor--daemon.txt    |  45 +-\n Makefile                                   |   9 +\n builtin/fsmonitor--daemon.c                |  11 +-\n compat/fsmonitor/fsm-health-linux.c        |  24 +\n compat/fsmonitor/fsm-ipc-unix.c            |  52 ++\n compat/fsmonitor/fsm-ipc-win32.c           |   9 +\n compat/fsmonitor/fsm-listen-darwin.c       |  14 +-\n compat/fsmonitor/fsm-listen-linux.c        | 664 +++++++++++++++++++++\n compat/fsmonitor/fsm-path-utils-darwin.c   | 135 +++++\n compat/fsmonitor/fsm-path-utils-linux.c    | 169 ++++++\n compat/fsmonitor/fsm-path-utils-win32.c    | 145 +++++\n compat/fsmonitor/fsm-settings-darwin.c     |  90 +--\n compat/fsmonitor/fsm-settings-linux.c      |  11 +\n compat/fsmonitor/fsm-settings-unix.c       |  62 ++\n compat/fsmonitor/fsm-settings-unix.h       |  11 +\n compat/fsmonitor/fsm-settings-win32.c      | 174 +-----\n config.mak.uname                           |  10 +\n contrib/buildsystems/CMakeLists.txt        |  19 +-\n fsmonitor--daemon.h                        |   3 +\n fsmonitor-ipc.c                            |  18 +-\n fsmonitor-ipc.h                            |   4 +-\n fsmonitor-path-utils.h                     |  60 ++\n fsmonitor-settings.c                       |  68 ++-\n fsmonitor-settings.h                       |   4 +-\n fsmonitor.c                                |   7 +\n t/t7527-builtin-fsmonitor.sh               |  72 ++-\n 28 files changed, 1606 insertions(+), 297 deletions(-)\n create mode 100644 Documentation/config/fsmonitor--daemon.txt\n create mode 100644 compat/fsmonitor/fsm-health-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-unix.c\n create mode 100644 compat/fsmonitor/fsm-ipc-win32.c\n create mode 100644 compat/fsmonitor/fsm-listen-linux.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-darwin.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-win32.c\n create mode 100644 compat/fsmonitor/fsm-settings-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.h\n create mode 100644 fsmonitor-path-utils.h\n\n\nbase-commit: d420dda0576340909c3faff364cfbd1485f70376\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1352%2Fedecosta-mw%2Ffsmonitor_linux-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1352/edecosta-mw/fsmonitor_linux-v2\nPull-Request: https://github.com/git/git/pull/1352\n\nRange-diff vs v1:\n\n  1:  ec49a74086d =  1:  cd46bed37c3 fsmonitor: refactor filesystem checks to common interface\n  2:  7bf1cdfe3b2 =  2:  21d114bda4b fsmonitor: relocate socket file if .git directory is remote\n  3:  c5e8b6cfe5d =  3:  664259ed57a fsmonitor: avoid socket location check if using hook\n  4:  863063aefee =  4:  d8f20032d6b fsmonitor: deal with synthetic firmlinks on macOS\n  5:  fa974bfd5ef =  5:  ab1e0ab967c fsmonitor: check for compatability before communicating with fsmonitor\n  6:  af7309745f7 =  6:  9c552239b57 fsmonitor: add documentation for allowRemote and socketDir options\n  7:  c085fc15b31 !  7:  295beb89ab1 fsmonitor: prepare to share code between Mac OS and Linux\n     @@ Commit message\n          fsmonitor: prepare to share code between Mac OS and Linux\n      \n          Linux and Mac OS can share some of the code originally developed for Mac OS.\n     -    Rename the shared code from compat/fsmonitor/fsm-*-dawrin.c to\n     -    compat/fsmonitor/fsm-*-unix.c\n     -\n     -    Update the build to enable sharing of the fsm-*-unix.c files.\n      \n          Minor update to compat/fsmonitor/fsm-ipc-unix.c to make it cross-platform.\n     +    Mac OS and Linux can share fsm-ipc-unix.c\n     +\n     +    Both platforms can also share compat/fsmonitor/fsm-settings-unix.c but we\n     +    will leave room for future, platform-specific checks by having the platform-\n     +    specific implementations call into fsm-settings-unix.\n      \n          Signed-off-by: Eric DeCosta <edecosta@mathworks.com>\n      \n       ## Makefile ##\n      @@ Makefile: endif\n     + \n       ifdef FSMONITOR_DAEMON_BACKEND\n       \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n     ++\tifdef FSMONITOR_DAEMON_COMMON\n     ++\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_COMMON).o\n     ++\telse\n     ++\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n     ++\tendif\n       \tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o\n     --\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o\n     + \tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o\n      -\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n     -+\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_COMMON).o\n     -+\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_COMMON).o\n       endif\n       \n       ifdef FSMONITOR_OS_SETTINGS\n       \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n     --\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o\n     -+\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_COMMON).o\n     ++\tifdef FSMONITOR_DAEMON_COMMON\n     ++\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_COMMON).o\n     ++\tendif\n     + \tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o\n       \tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o\n       endif\n     - \n      \n     - ## compat/fsmonitor/fsm-health-darwin.c => compat/fsmonitor/fsm-health-unix.c ##\n     + ## compat/fsmonitor/fsm-health-linux.c (new) ##\n     +@@\n     ++#include \"cache.h\"\n     ++#include \"config.h\"\n     ++#include \"fsmonitor.h\"\n     ++#include \"fsm-health.h\"\n     ++#include \"fsmonitor--daemon.h\"\n     ++\n     ++int fsm_health__ctor(struct fsmonitor_daemon_state *state)\n     ++{\n     ++\treturn 0;\n     ++}\n     ++\n     ++void fsm_health__dtor(struct fsmonitor_daemon_state *state)\n     ++{\n     ++\treturn;\n     ++}\n     ++\n     ++void fsm_health__loop(struct fsmonitor_daemon_state *state)\n     ++{\n     ++\treturn;\n     ++}\n     ++\n     ++void fsm_health__stop_async(struct fsmonitor_daemon_state *state)\n     ++{\n     ++}\n      \n       ## compat/fsmonitor/fsm-ipc-darwin.c => compat/fsmonitor/fsm-ipc-unix.c ##\n      @@ compat/fsmonitor/fsm-ipc-unix.c: static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n     @@ compat/fsmonitor/fsm-ipc-unix.c: const char *fsmonitor_ipc__get_path(struct repo\n       \trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n       \n      \n     - ## compat/fsmonitor/fsm-settings-darwin.c => compat/fsmonitor/fsm-settings-unix.c ##\n     + ## compat/fsmonitor/fsm-settings-darwin.c ##\n     +@@\n     + #include \"config.h\"\n     + #include \"fsmonitor.h\"\n     + #include \"fsmonitor-ipc.h\"\n     +-#include \"fsmonitor-settings.h\"\n     + #include \"fsmonitor-path-utils.h\"\n     +-\n     +- /*\n     +- * For the builtin FSMonitor, we create the Unix domain socket for the\n     +- * IPC in the .git directory.  If the working directory is remote,\n     +- * then the socket will be created on the remote file system.  This\n     +- * can fail if the remote file system does not support UDS file types\n     +- * (e.g. smbfs to a Windows server) or if the remote kernel does not\n     +- * allow a non-local process to bind() the socket.  (These problems\n     +- * could be fixed by moving the UDS out of the .git directory and to a\n     +- * well-known local directory on the client machine, but care should\n     +- * be taken to ensure that $HOME is actually local and not a managed\n     +- * file share.)\n     +- *\n     +- * FAT32 and NTFS working directories are problematic too.\n     +- *\n     +- * The builtin FSMonitor uses a Unix domain socket in the .git\n     +- * directory for IPC.  These Windows drive formats do not support\n     +- * Unix domain sockets, so mark them as incompatible for the daemon.\n     +- *\n     +- */\n     +-static enum fsmonitor_reason check_uds_volume(struct repository *r)\n     +-{\n     +-\tstruct fs_info fs;\n     +-\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n     +-\tstruct strbuf path = STRBUF_INIT;\n     +-\tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n     +-\n     +-\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n     +-\t\tstrbuf_release(&path);\n     +-\t\treturn FSMONITOR_REASON_ERROR;\n     +-\t}\n     +-\n     +-\tstrbuf_release(&path);\n     +-\n     +-\tif (fs.is_remote ||\n     +-\t\t!strcmp(fs.typename, \"msdos\") ||\n     +-\t\t!strcmp(fs.typename, \"ntfs\")) {\n     +-\t\tfree(fs.typename);\n     +-\t\treturn FSMONITOR_REASON_NOSOCKETS;\n     +-\t}\n     +-\n     +-\tfree(fs.typename);\n     +-\treturn FSMONITOR_REASON_OK;\n     +-}\n     ++#include \"fsmonitor-settings.h\"\n     ++#include \"fsm-settings-unix.h\"\n     + \n     + enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n     + {\n     +-\tenum fsmonitor_reason reason;\n     +-\n     +-\tif (ipc) {\n     +-\t\treason = check_uds_volume(r);\n     +-\t\tif (reason != FSMONITOR_REASON_OK)\n     +-\t\t\treturn reason;\n     +-\t}\n     +-\n     +-\treturn FSMONITOR_REASON_OK;\n     ++    return fsm_os__incompatible_unix(r, ipc);\n     + }\n     +\n     + ## compat/fsmonitor/fsm-settings-linux.c (new) ##\n     +@@\n     ++#include \"config.h\"\n     ++#include \"fsmonitor.h\"\n     ++#include \"fsmonitor-ipc.h\"\n     ++#include \"fsmonitor-path-utils.h\"\n     ++#include \"fsmonitor-settings.h\"\n     ++#include \"fsm-settings-unix.h\"\n     ++\n     ++enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n     ++{\n     ++    return fsm_os__incompatible_unix(r, ipc);\n     ++}\n      \n     - ## config.mak.uname ##\n     -@@ config.mak.uname: ifeq ($(uname_S),Linux)\n     - \tifneq ($(findstring .el7.,$(uname_R)),)\n     - \t\tBASIC_CFLAGS += -std=c99\n     - \tendif\n     + ## compat/fsmonitor/fsm-settings-unix.c (new) ##\n     +@@\n     ++#include \"config.h\"\n     ++#include \"fsmonitor.h\"\n     ++#include \"fsmonitor-ipc.h\"\n     ++#include \"fsmonitor-path-utils.h\"\n     ++#include \"fsm-settings-unix.h\"\n      +\n     - endif\n     - ifeq ($(uname_S),GNU/kFreeBSD)\n     - \tHAVE_ALLOCA_H = YesPlease\n     ++ /*\n     ++ * For the builtin FSMonitor, we create the Unix domain socket for the\n     ++ * IPC in the .git directory.  If the working directory is remote,\n     ++ * then the socket will be created on the remote file system.  This\n     ++ * can fail if the remote file system does not support UDS file types\n     ++ * (e.g. smbfs to a Windows server) or if the remote kernel does not\n     ++ * allow a non-local process to bind() the socket.  (These problems\n     ++ * could be fixed by moving the UDS out of the .git directory and to a\n     ++ * well-known local directory on the client machine, but care should\n     ++ * be taken to ensure that $HOME is actually local and not a managed\n     ++ * file share.)\n     ++ *\n     ++ * FAT32 and NTFS working directories are problematic too.\n     ++ *\n     ++ * The builtin FSMonitor uses a Unix domain socket in the .git\n     ++ * directory for IPC.  These Windows drive formats do not support\n     ++ * Unix domain sockets, so mark them as incompatible for the daemon.\n     ++ *\n     ++ */\n     ++static enum fsmonitor_reason check_uds_volume(struct repository *r)\n     ++{\n     ++\tstruct fs_info fs;\n     ++\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n     ++\tstruct strbuf path = STRBUF_INIT;\n     ++\tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n     ++\n     ++\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n     ++\t\tstrbuf_release(&path);\n     ++\t\treturn FSMONITOR_REASON_ERROR;\n     ++\t}\n     ++\n     ++\tstrbuf_release(&path);\n     ++\n     ++\tif (fs.is_remote ||\n     ++\t\t!strcmp(fs.typename, \"msdos\") ||\n     ++\t\t!strcmp(fs.typename, \"ntfs\")) {\n     ++\t\tfree(fs.typename);\n     ++\t\treturn FSMONITOR_REASON_NOSOCKETS;\n     ++\t}\n     ++\n     ++\tfree(fs.typename);\n     ++\treturn FSMONITOR_REASON_OK;\n     ++}\n     ++\n     ++enum fsmonitor_reason fsm_os__incompatible_unix(struct repository *r, int ipc)\n     ++{\n     ++\tenum fsmonitor_reason reason;\n     ++\n     ++\tif (ipc) {\n     ++\t\treason = check_uds_volume(r);\n     ++\t\tif (reason != FSMONITOR_REASON_OK)\n     ++\t\t\treturn reason;\n     ++\t}\n     ++\n     ++\treturn FSMONITOR_REASON_OK;\n     ++}\n     +\n     + ## compat/fsmonitor/fsm-settings-unix.h (new) ##\n     +@@\n     ++#ifndef FSM_SETTINGS_UNIX_H\n     ++#define FSM_SETTINGS_UNIX_H\n     ++\n     ++#ifdef HAVE_FSMONITOR_OS_SETTINGS\n     ++/*\n     ++ * Check for compatibility on unix-like systems (e.g. Darwin and Linux)\n     ++ */\n     ++enum fsmonitor_reason fsm_os__incompatible_unix(struct repository *r, int ipc);\n     ++#endif /* HAVE_FSMONITOR_OS_SETTINGS */\n     ++\n     ++#endif /* FSM_SETTINGS_UNIX_H */\n     +\n     + ## config.mak.uname ##\n      @@ config.mak.uname: ifeq ($(uname_S),Darwin)\n       \tifndef NO_UNIX_SOCKETS\n       \tFSMONITOR_DAEMON_BACKEND = darwin\n     @@ config.mak.uname: ifeq ($(uname_S),Darwin)\n       \tendif\n       \tendif\n       \n     -@@ config.mak.uname: ifeq ($(uname_S),Windows)\n     - \t# support it.\n     - \tFSMONITOR_DAEMON_BACKEND = win32\n     - \tFSMONITOR_OS_SETTINGS = win32\n     -+\tFSMONITOR_DAEMON_COMMON = win32\n     - \n     - \tNO_SVN_TESTS = YesPlease\n     - \tRUNTIME_PREFIX = YesPlease\n     -@@ config.mak.uname: ifeq ($(uname_S),MINGW)\n     - \t# support it.\n     - \tFSMONITOR_DAEMON_BACKEND = win32\n     - \tFSMONITOR_OS_SETTINGS = win32\n     -+\tFSMONITOR_DAEMON_COMMON = win32\n     - \n     - \tRUNTIME_PREFIX = YesPlease\n     - \tHAVE_WPGMPTR = YesWeDo\n      \n       ## contrib/buildsystems/CMakeLists.txt ##\n     +@@ contrib/buildsystems/CMakeLists.txt: else()\n     + endif()\n     + \n     + if(SUPPORTS_SIMPLE_IPC)\n     +-\tif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n     ++\tif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n     ++\t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-unix.c)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-linux.c)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-linux.c)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-linux.c)\n     ++\n     ++\t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-unix.c)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-linux.c)\n     ++\telseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n     + \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n     + \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n     + \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n      @@ contrib/buildsystems/CMakeLists.txt: if(SUPPORTS_SIMPLE_IPC)\n       \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c)\n       \telseif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n       \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n     +-\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n      +\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-unix.c)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-unix.c)\n     - \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n     --\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)\n     + \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)\n      -\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-darwin.c)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n       \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c)\n       \n       \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n     --\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)\n      +\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-unix.c)\n     + \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)\n       \tendif()\n       endif()\n     - \n  8:  5ecbb3082f1 !  8:  7d7ef78728f fsmonitor: determine if filesystem is local or remote\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +\t || strcmp (\"-hosts\", Fs_name) == 0)\n      +#endif\n      +\n     -+static struct mntent *find_mount(const char *path, const struct statvfs *fs)\n     ++static int find_mount(const char *path, const struct statvfs *fs,\n     ++\tstruct mntent *ent)\n      +{\n      +\tconst char *const mounts = \"/proc/mounts\";\n      +\tconst char *rp = real_pathdup(path, 1);\n      +\tstruct mntent *ment = NULL;\n     -+\tstruct mntent *found = NULL;\n      +\tstruct statvfs mntfs;\n      +\tFILE *fp;\n     ++\tint found = 0;\n      +\tint dlen, plen, flen = 0;\n      +\n     ++\tent->mnt_fsname = NULL;\n     ++\tent->mnt_dir = NULL;\n     ++\tent->mnt_type = NULL;\n     ++\n      +\tfp = setmntent(mounts, \"r\");\n      +\tif (!fp) {\n      +\t\terror_errno(_(\"setmntent('%s') failed\"), mounts);\n     -+\t\treturn NULL;\n     ++\t\treturn -1;\n      +\t}\n      +\n      +\tplen = strlen(rp);\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +\t\t\tdefault:\n      +\t\t\t\terror_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n      +\t\t\t\tendmntent(fp);\n     -+\t\t\t\treturn NULL;\n     ++\t\t\t\treturn -1;\n      +\t\t\t}\n      +\t\t}\n      +\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +\t\t\t\t * The pointer points to a static area of memory which is\n      +\t\t\t\t * overwritten by subsequent calls to getmntent().\n      +\t\t\t\t */\n     -+\t\t\t\tif (!found)\n     -+\t\t\t\t\tCALLOC_ARRAY(found, 1);\n     -+\t\t\t\tfree(found->mnt_dir);\n     -+\t\t\t\tfree(found->mnt_type);\n     -+\t\t\t\tfree(found->mnt_fsname);\n     -+\t\t\t\tfound->mnt_dir = xmemdupz(ment->mnt_dir, strlen(ment->mnt_dir));\n     -+\t\t\t\tfound->mnt_type = xmemdupz(ment->mnt_type, strlen(ment->mnt_type));\n     -+\t\t\t\tfound->mnt_fsname = xmemdupz(ment->mnt_fsname, strlen(ment->mnt_fsname));\n     ++\t\t\t\tfound = 1;\n     ++\t\t\t\tfree(ent->mnt_fsname);\n     ++\t\t\t\tfree(ent->mnt_dir);\n     ++\t\t\t\tfree(ent->mnt_type);\n     ++\t\t\t\tent->mnt_fsname = xstrdup(ment->mnt_fsname);\n     ++\t\t\t\tent->mnt_dir = xstrdup(ment->mnt_dir);\n     ++\t\t\t\tent->mnt_type = xstrdup(ment->mnt_type);\n      +\t\t\t}\n      +\t\t}\n      +\t}\n      +\tendmntent(fp);\n      +\n     -+\treturn found;\n     ++\tif (!found)\n     ++\t\treturn -1;\n     ++\n     ++\treturn 0;\n      +}\n      +\n      +int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n      +{\n     -+\tstruct mntent *ment;\n     ++\tstruct mntent ment;\n      +\tstruct statvfs fs;\n      +\n      +\tif (statvfs(path, &fs))\n      +\t\treturn error_errno(_(\"statvfs('%s') failed\"), path);\n      +\n     -+\tment = find_mount(path, &fs);\n     -+\tif (!ment)\n     ++\n     ++\tif (find_mount(path, &fs, &ment) < 0) {\n     ++\t\tfree(ment.mnt_fsname);\n     ++\t\tfree(ment.mnt_dir);\n     ++\t\tfree(ment.mnt_type);\n      +\t\treturn -1;\n     ++\t}\n      +\n      +\ttrace_printf_key(&trace_fsmonitor,\n      +\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n     -+\t\t\t path, fs.f_flag, ment->mnt_type, ment->mnt_fsname);\n     -+\n     -+\tif (ME_REMOTE(ment->mnt_fsname, ment->mnt_type))\n     -+\t\tfs_info->is_remote = 1;\n     -+\telse\n     -+\t\tfs_info->is_remote = 0;\n     ++\t\t\t path, fs.f_flag, ment.mnt_type, ment.mnt_fsname);\n      +\n     -+\tfs_info->typename = ment->mnt_fsname;\n     -+\tfree(ment->mnt_dir);\n     -+\tfree(ment->mnt_type);\n     -+\tfree(ment);\n     ++\tfs_info->is_remote = ME_REMOTE(ment.mnt_fsname, ment.mnt_type);\n     ++\tfs_info->typename = ment.mnt_fsname;\n     ++\tfree(ment.mnt_dir);\n     ++\tfree(ment.mnt_type);\n      +\n      +\ttrace_printf_key(&trace_fsmonitor,\n      +\t\t\t\t\"'%s' is_remote: %d\",\n  9:  7465fe7a8b4 !  9:  4f9c5358475 fsmonitor: implement filesystem change listener for Linux\n     @@ compat/fsmonitor/fsm-listen-linux.c (new)\n      +\n      +/*\n      + * Non-blocking read of the inotify events stream. The inotify fd is polled\n     -+ * every few millisec to help minimize the number of queue overflows.\n     ++ * frequently to help minimize the number of queue overflows.\n      + */\n      +void fsm_listen__loop(struct fsmonitor_daemon_state *state)\n      +{\n     @@ compat/fsmonitor/fsm-listen-linux.c (new)\n      +\tfor(;;) {\n      +\t\tswitch (state->listen_data->shutdown) {\n      +\t\t\tcase SHUTDOWN_CONTINUE:\n     -+\t\t\t\tpoll_num = poll(fds, 1, 10);\n     ++\t\t\t\tpoll_num = poll(fds, 1, 1);\n      +\t\t\t\tif (poll_num == -1) {\n      +\t\t\t\t\tif (errno == EINTR)\n      +\t\t\t\t\t\tcontinue;\n 10:  3a2db9aa076 ! 10:  07650ecd27b fsmonitor: enable fsmonitor for Linux\n     @@ Metadata\n       ## Commit message ##\n          fsmonitor: enable fsmonitor for Linux\n      \n     -    Uodate build to enable fsmonitor for Linux.\n     +    Update build to enable fsmonitor for Linux.\n      \n          Signed-off-by: Eric DeCosta <edecosta@mathworks.com>\n      \n       ## config.mak.uname ##\n      @@ config.mak.uname: ifeq ($(uname_S),Linux)\n     + \tifneq ($(findstring .el7.,$(uname_R)),)\n       \t\tBASIC_CFLAGS += -std=c99\n       \tendif\n     - \n      +\t# The builtin FSMonitor on Linux builds upon Simple-IPC.  Both require\n      +\t# Unix domain sockets and PThreads.\n      +\tifndef NO_PTHREADS\n     @@ config.mak.uname: ifeq ($(uname_S),Linux)\n       endif\n       ifeq ($(uname_S),GNU/kFreeBSD)\n       \tHAVE_ALLOCA_H = YesPlease\n     -\n     - ## contrib/buildsystems/CMakeLists.txt ##\n     -@@ contrib/buildsystems/CMakeLists.txt: else()\n     - endif()\n     - \n     - if(SUPPORTS_SIMPLE_IPC)\n     --\tif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n     -+\tif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n     -+\t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-unix.c)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-unix.c)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-linux.c)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-linux.c)\n     -+\n     -+\t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-unix.c)\n     -+\telseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n     - \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n     - \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n     - \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n 11:  743bdacded5 ! 11:  6682938fff8 fsmonitor: test updates\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'edit some files' '\n       \n       \ttest-tool fsmonitor-client query --token 0 &&\n       \n     ++\ttest_might_fail git fsmonitor--daemon stop &&\n     ++\n     + \tgrep \"^event: dir1/modified$\"  .git/trace &&\n     + \tgrep \"^event: dir2/modified$\"  .git/trace &&\n     + \tgrep \"^event: modified$\"       .git/trace &&\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'create some files' '\n       \n       \tstart_daemon --tf \"$PWD/.git/trace\" &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'create some files' '\n       \n       \ttest-tool fsmonitor-client query --token 0 &&\n       \n     ++\ttest_might_fail git fsmonitor--daemon stop &&\n     ++\n     + \tgrep \"^event: dir1/new$\" .git/trace &&\n     + \tgrep \"^event: dir2/new$\" .git/trace &&\n     + \tgrep \"^event: new$\"      .git/trace\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'delete some files' '\n       \n       \tstart_daemon --tf \"$PWD/.git/trace\" &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'delete some files' '\n       \n       \ttest-tool fsmonitor-client query --token 0 &&\n       \n     ++\ttest_might_fail git fsmonitor--daemon stop &&\n     ++\n     + \tgrep \"^event: dir1/delete$\" .git/trace &&\n     + \tgrep \"^event: dir2/delete$\" .git/trace &&\n     + \tgrep \"^event: delete$\"      .git/trace\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'rename some files' '\n       \n       \tstart_daemon --tf \"$PWD/.git/trace\" &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'rename some files' '\n       \n       \ttest-tool fsmonitor-client query --token 0 &&\n       \n     ++\ttest_might_fail git fsmonitor--daemon stop &&\n     ++\n     + \tgrep \"^event: dir1/rename$\"  .git/trace &&\n     + \tgrep \"^event: dir2/rename$\"  .git/trace &&\n     + \tgrep \"^event: rename$\"       .git/trace &&\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'rename directory' '\n       \n       \tstart_daemon --tf \"$PWD/.git/trace\" &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'rename directory' '\n       \n       \ttest-tool fsmonitor-client query --token 0 &&\n       \n     ++\ttest_might_fail git fsmonitor--daemon stop &&\n     ++\n     + \tgrep \"^event: dirtorename/*$\" .git/trace &&\n     + \tgrep \"^event: dirrenamed/*$\"  .git/trace\n     + '\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'file changes to directory' '\n       \n       \tstart_daemon --tf \"$PWD/.git/trace\" &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'file changes to directory' '\n       \n       \ttest-tool fsmonitor-client query --token 0 &&\n       \n     ++\ttest_might_fail git fsmonitor--daemon stop &&\n     ++\n     + \tgrep \"^event: delete$\"     .git/trace &&\n     + \tgrep \"^event: delete/new$\" .git/trace\n     + '\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'directory changes to a file' '\n       \n       \tstart_daemon --tf \"$PWD/.git/trace\" &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'directory changes to a file'\n       \n       \ttest-tool fsmonitor-client query --token 0 &&\n       \n     ++\ttest_might_fail git fsmonitor--daemon stop &&\n     ++\n     + \tgrep \"^event: dir1$\" .git/trace\n     + '\n     + \n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'flush cached data' '\n       \ttest-tool -C test_flush fsmonitor-client query --token \"builtin:test_00000002:0\" >actual_2 &&\n       \tnul_to_q <actual_2 >actual_q2 &&\n 12:  77ed35b3b80 = 12:  dd73e126810 fsmonitor: update doc for Linux\n\n-- \ngitgitgadget\n"},{"id":"464996","messageId":"ab1e0ab967cee0e903e1ca2ee9e349a5ad400603.1665783944.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 05/12] fsmonitor: check for compatability before communicating with fsmonitor","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:37Z","receivedAt":"2022-10-14T21:46:09Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nIf fsmonitor is not in a compatible state, warn with an appropriate message.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n fsmonitor-settings.c | 10 +++++++---\n fsmonitor-settings.h |  2 +-\n fsmonitor.c          |  7 +++++++\n 3 files changed, 15 insertions(+), 4 deletions(-)\n\ndiff --git a/fsmonitor-settings.c b/fsmonitor-settings.c\nindex 531a1b6f956..ee63a97dc51 100644\n--- a/fsmonitor-settings.c\n+++ b/fsmonitor-settings.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"config.h\"\n #include \"repository.h\"\n+#include \"fsmonitor-ipc.h\"\n #include \"fsmonitor-settings.h\"\n #include \"fsmonitor-path-utils.h\"\n \n@@ -242,10 +243,11 @@ enum fsmonitor_reason fsm_settings__get_reason(struct repository *r)\n \treturn r->settings.fsmonitor->reason;\n }\n \n-char *fsm_settings__get_incompatible_msg(const struct repository *r,\n+char *fsm_settings__get_incompatible_msg(struct repository *r,\n \t\t\t\t\t enum fsmonitor_reason reason)\n {\n \tstruct strbuf msg = STRBUF_INIT;\n+\tconst char *socket_dir;\n \n \tswitch (reason) {\n \tcase FSMONITOR_REASON_UNTESTED:\n@@ -281,9 +283,11 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,\n \t\tgoto done;\n \n \tcase FSMONITOR_REASON_NOSOCKETS:\n+\t\tsocket_dir = dirname((char *)fsmonitor_ipc__get_path(r));\n \t\tstrbuf_addf(&msg,\n-\t\t\t    _(\"repository '%s' is incompatible with fsmonitor due to lack of Unix sockets\"),\n-\t\t\t    r->worktree);\n+\t\t\t    _(\"socket directory '%s' is incompatible with fsmonitor due\"\n+\t\t\t      \" to lack of Unix sockets support\"),\n+\t\t\t    socket_dir);\n \t\tgoto done;\n \t}\n \ndiff --git a/fsmonitor-settings.h b/fsmonitor-settings.h\nindex 0721617b95a..ab02e3995ee 100644\n--- a/fsmonitor-settings.h\n+++ b/fsmonitor-settings.h\n@@ -33,7 +33,7 @@ enum fsmonitor_mode fsm_settings__get_mode(struct repository *r);\n const char *fsm_settings__get_hook_path(struct repository *r);\n \n enum fsmonitor_reason fsm_settings__get_reason(struct repository *r);\n-char *fsm_settings__get_incompatible_msg(const struct repository *r,\n+char *fsm_settings__get_incompatible_msg(struct repository *r,\n \t\t\t\t\t enum fsmonitor_reason reason);\n \n struct fsmonitor_settings;\ndiff --git a/fsmonitor.c b/fsmonitor.c\nindex 57d6a483bee..540736b39fd 100644\n--- a/fsmonitor.c\n+++ b/fsmonitor.c\n@@ -295,6 +295,7 @@ static int fsmonitor_force_update_threshold = 100;\n \n void refresh_fsmonitor(struct index_state *istate)\n {\n+\tstatic int warn_once = 0;\n \tstruct strbuf query_result = STRBUF_INIT;\n \tint query_success = 0, hook_version = -1;\n \tsize_t bol = 0; /* beginning of line */\n@@ -305,6 +306,12 @@ void refresh_fsmonitor(struct index_state *istate)\n \tint is_trivial = 0;\n \tstruct repository *r = istate->repo ? istate->repo : the_repository;\n \tenum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);\n+\tenum fsmonitor_reason reason = fsm_settings__get_reason(r);\n+\n+\tif (!warn_once && reason > FSMONITOR_REASON_OK) {\n+\t\twarn_once = 1;\n+\t\twarning(\"%s\", fsm_settings__get_incompatible_msg(r, reason));\n+\t}\n \n \tif (fsm_mode <= FSMONITOR_MODE_DISABLED ||\n \t    istate->fsmonitor_has_run_once)\n-- \ngitgitgadget\n\n"},{"id":"464997","messageId":"664259ed57a3b39af295ba87ce668cab4d364a9e.1665783944.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 03/12] fsmonitor: avoid socket location check if using hook","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:35Z","receivedAt":"2022-10-14T21:46:11Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nIf monitoring is done via fsmonitor hook rather than IPC there is no\nneed to check if the location of the Unix Domain socket (UDS) file is\non a remote filesystem.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-settings-darwin.c | 10 ++++++----\n compat/fsmonitor/fsm-settings-win32.c  |  2 +-\n fsmonitor-settings.c                   |  8 ++++----\n fsmonitor-settings.h                   |  2 +-\n 4 files changed, 12 insertions(+), 10 deletions(-)\n\ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c\nindex 7241c4c22c9..6abbc7af3ab 100644\n--- a/compat/fsmonitor/fsm-settings-darwin.c\n+++ b/compat/fsmonitor/fsm-settings-darwin.c\n@@ -48,13 +48,15 @@ static enum fsmonitor_reason check_uds_volume(struct repository *r)\n \treturn FSMONITOR_REASON_OK;\n }\n \n-enum fsmonitor_reason fsm_os__incompatible(struct repository *r)\n+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n {\n \tenum fsmonitor_reason reason;\n \n-\treason = check_uds_volume(r);\n-\tif (reason != FSMONITOR_REASON_OK)\n-\t\treturn reason;\n+\tif (ipc) {\n+\t\treason = check_uds_volume(r);\n+\t\tif (reason != FSMONITOR_REASON_OK)\n+\t\t\treturn reason;\n+\t}\n \n \treturn FSMONITOR_REASON_OK;\n }\ndiff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c\nindex d88b06ae610..a8af31b71de 100644\n--- a/compat/fsmonitor/fsm-settings-win32.c\n+++ b/compat/fsmonitor/fsm-settings-win32.c\n@@ -25,7 +25,7 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r)\n \treturn FSMONITOR_REASON_OK;\n }\n \n-enum fsmonitor_reason fsm_os__incompatible(struct repository *r)\n+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n {\n \tenum fsmonitor_reason reason;\n \ndiff --git a/fsmonitor-settings.c b/fsmonitor-settings.c\nindex d288cbad479..531a1b6f956 100644\n--- a/fsmonitor-settings.c\n+++ b/fsmonitor-settings.c\n@@ -60,7 +60,7 @@ static enum fsmonitor_reason check_remote(struct repository *r)\n }\n #endif\n \n-static enum fsmonitor_reason check_for_incompatible(struct repository *r)\n+static enum fsmonitor_reason check_for_incompatible(struct repository *r, int ipc)\n {\n \tif (!r->worktree) {\n \t\t/*\n@@ -77,7 +77,7 @@ static enum fsmonitor_reason check_for_incompatible(struct repository *r)\n \t\treason = check_remote(r);\n \t\tif (reason != FSMONITOR_REASON_OK)\n \t\t\treturn reason;\n-\t\treason = fsm_os__incompatible(r);\n+\t\treason = fsm_os__incompatible(r, ipc);\n \t\tif (reason != FSMONITOR_REASON_OK)\n \t\t\treturn reason;\n \t}\n@@ -162,7 +162,7 @@ const char *fsm_settings__get_hook_path(struct repository *r)\n \n void fsm_settings__set_ipc(struct repository *r)\n {\n-\tenum fsmonitor_reason reason = check_for_incompatible(r);\n+\tenum fsmonitor_reason reason = check_for_incompatible(r, 1);\n \n \tif (reason != FSMONITOR_REASON_OK) {\n \t\tfsm_settings__set_incompatible(r, reason);\n@@ -185,7 +185,7 @@ void fsm_settings__set_ipc(struct repository *r)\n \n void fsm_settings__set_hook(struct repository *r, const char *path)\n {\n-\tenum fsmonitor_reason reason = check_for_incompatible(r);\n+\tenum fsmonitor_reason reason = check_for_incompatible(r, 0);\n \n \tif (reason != FSMONITOR_REASON_OK) {\n \t\tfsm_settings__set_incompatible(r, reason);\ndiff --git a/fsmonitor-settings.h b/fsmonitor-settings.h\nindex d9c2605197f..0721617b95a 100644\n--- a/fsmonitor-settings.h\n+++ b/fsmonitor-settings.h\n@@ -48,7 +48,7 @@ struct fsmonitor_settings;\n  * fsm_os__* routines should considered private to fsm_settings__\n  * routines.\n  */\n-enum fsmonitor_reason fsm_os__incompatible(struct repository *r);\n+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc);\n #endif /* HAVE_FSMONITOR_OS_SETTINGS */\n \n #endif /* FSMONITOR_SETTINGS_H */\n-- \ngitgitgadget\n\n"},{"id":"464998","messageId":"d8f20032d6b9e64ecd7e8cdd191cbbc516287dc8.1665783944.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 04/12] fsmonitor: deal with synthetic firmlinks on macOS","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:36Z","receivedAt":"2022-10-14T21:46:15Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nStarting with macOS 10.15 (Catalina), Apple introduced a new feature\ncalled 'firmlinks' in order to separate the boot volume into two\nvolumes, one read-only and one writable but still present them to the\nuser as a single volume. Along with this change, Apple removed the\nability to create symlinks in the root directory and replaced them with\n'synthetic firmlinks'. See 'man synthetic.conf'\n\nWhen FSEevents reports the path of changed files, if the path involves\na synthetic firmlink, the path is reported from the point of the\nsynthetic firmlink and not the real path. For example:\n\nReal path:\n/System/Volumes/Data/network/working/directory/foo.txt\n\nSynthetic firmlink:\n/network -> /System/Volumes/Data/network\n\nFSEvents path:\n/network/working/directory/foo.txt\n\nThis causes the FSEvents path to not match against the worktree\ndirectory.\n\nThere are several ways in which synthetic firmlinks can be created:\nthey can be defined in /etc/synthetic.conf, the automounter can create\nthem, and there may be other means. Simply reading /etc/synthetic.conf\nis insufficient. No matter what process creates synthetic firmlinks,\nthey all get created in the root directory.\n\nTherefore, in order to deal with synthetic firmlinks, the root directory\nis scanned and the first possible synthetic firmink that, when resolved,\nis a prefix of the worktree is used to map FSEvents paths to worktree\npaths.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n builtin/fsmonitor--daemon.c              |  8 +++\n compat/fsmonitor/fsm-listen-darwin.c     | 14 +++-\n compat/fsmonitor/fsm-path-utils-darwin.c | 92 ++++++++++++++++++++++++\n compat/fsmonitor/fsm-path-utils-win32.c  | 17 +++++\n fsmonitor--daemon.h                      |  3 +\n fsmonitor-path-utils.h                   | 36 +++++++++-\n 6 files changed, 167 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex 6e417846d8b..6f30a4f93a7 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -3,6 +3,7 @@\n #include \"parse-options.h\"\n #include \"fsmonitor.h\"\n #include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n #include \"compat/fsmonitor/fsm-health.h\"\n #include \"compat/fsmonitor/fsm-listen.h\"\n #include \"fsmonitor--daemon.h\"\n@@ -1282,6 +1283,11 @@ static int fsmonitor_run_daemon(void)\n \tstrbuf_addstr(&state.path_worktree_watch, absolute_path(get_git_work_tree()));\n \tstate.nr_paths_watching = 1;\n \n+\tstrbuf_init(&state.alias.alias, 0);\n+\tstrbuf_init(&state.alias.points_to, 0);\n+\tif ((err = fsmonitor__get_alias(state.path_worktree_watch.buf, &state.alias)))\n+\t\tgoto done;\n+\n \t/*\n \t * We create and delete cookie files somewhere inside the .git\n \t * directory to help us keep sync with the file system.  If\n@@ -1391,6 +1397,8 @@ done:\n \tstrbuf_release(&state.path_gitdir_watch);\n \tstrbuf_release(&state.path_cookie_prefix);\n \tstrbuf_release(&state.path_ipc);\n+\tstrbuf_release(&state.alias.alias);\n+\tstrbuf_release(&state.alias.points_to);\n \n \treturn err;\n }\ndiff --git a/compat/fsmonitor/fsm-listen-darwin.c b/compat/fsmonitor/fsm-listen-darwin.c\nindex 8e208e8289e..daeee4e465c 100644\n--- a/compat/fsmonitor/fsm-listen-darwin.c\n+++ b/compat/fsmonitor/fsm-listen-darwin.c\n@@ -26,6 +26,7 @@\n #include \"fsmonitor.h\"\n #include \"fsm-listen.h\"\n #include \"fsmonitor--daemon.h\"\n+#include \"fsmonitor-path-utils.h\"\n \n struct fsm_listen_data\n {\n@@ -198,8 +199,9 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,\n \tstruct string_list cookie_list = STRING_LIST_INIT_DUP;\n \tconst char *path_k;\n \tconst char *slash;\n-\tint k;\n+\tchar *resolved = NULL;\n \tstruct strbuf tmp = STRBUF_INIT;\n+\tint k;\n \n \t/*\n \t * Build a list of all filesystem changes into a private/local\n@@ -209,7 +211,12 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,\n \t\t/*\n \t\t * On Mac, we receive an array of absolute paths.\n \t\t */\n-\t\tpath_k = paths[k];\n+\t\tfree(resolved);\n+\t\tresolved = fsmonitor__resolve_alias(paths[k], &state->alias);\n+\t\tif (resolved)\n+\t\t\tpath_k = resolved;\n+\t\telse\n+\t\t\tpath_k = paths[k];\n \n \t\t/*\n \t\t * If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.\n@@ -238,6 +245,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,\n \t\t\tfsmonitor_force_resync(state);\n \t\t\tfsmonitor_batch__free_list(batch);\n \t\t\tstring_list_clear(&cookie_list, 0);\n+\t\t\tbatch = NULL;\n \n \t\t\t/*\n \t\t\t * We assume that any events that we received\n@@ -360,12 +368,14 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,\n \t\t}\n \t}\n \n+\tfree(resolved);\n \tfsmonitor_publish(state, batch, &cookie_list);\n \tstring_list_clear(&cookie_list, 0);\n \tstrbuf_release(&tmp);\n \treturn;\n \n force_shutdown:\n+\tfree(resolved);\n \tfsmonitor_batch__free_list(batch);\n \tstring_list_clear(&cookie_list, 0);\n \ndiff --git a/compat/fsmonitor/fsm-path-utils-darwin.c b/compat/fsmonitor/fsm-path-utils-darwin.c\nindex d46d7f13538..ce5a8febe09 100644\n--- a/compat/fsmonitor/fsm-path-utils-darwin.c\n+++ b/compat/fsmonitor/fsm-path-utils-darwin.c\n@@ -1,5 +1,8 @@\n #include \"fsmonitor.h\"\n #include \"fsmonitor-path-utils.h\"\n+#include <dirent.h>\n+#include <errno.h>\n+#include <fcntl.h>\n #include <sys/param.h>\n #include <sys/mount.h>\n \n@@ -41,3 +44,92 @@ int fsmonitor__is_fs_remote(const char *path)\n \n \treturn fs.is_remote;\n }\n+\n+/*\n+ * Scan the root directory for synthetic firmlinks that when resolved\n+ * are a prefix of the path, stopping at the first one found.\n+ *\n+ * Some information about firmlinks and synthetic firmlinks:\n+ * https://eclecticlight.co/2020/01/23/catalina-boot-volumes/\n+ *\n+ * macOS no longer allows symlinks in the root directory; any link found\n+ * there is therefore a synthetic firmlink.\n+ *\n+ * If this function gets called often, will want to cache all the firmlink\n+ * information, but for now there is only one caller of this function.\n+ *\n+ * If there is more than one alias for the path, that is another\n+ * matter altogether.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info)\n+{\n+\tDIR *dir;\n+\tint retval = -1;\n+\tconst char *const root = \"/\";\n+\tstruct stat st;\n+\tstruct dirent *de;\n+\tstruct strbuf alias;\n+\tstruct strbuf points_to = STRBUF_INIT;\n+\n+\tdir = opendir(root);\n+\tif (!dir)\n+\t\treturn error_errno(_(\"opendir('%s') failed\"), root);\n+\n+\tstrbuf_init(&alias, 256);\n+\n+\twhile ((de = readdir(dir)) != NULL) {\n+\t\tstrbuf_reset(&alias);\n+\t\tstrbuf_addf(&alias, \"%s%s\", root, de->d_name);\n+\n+\t\tif (lstat(alias.buf, &st) < 0) {\n+\t\t\terror_errno(_(\"lstat('%s') failed\"), alias.buf);\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tif (!S_ISLNK(st.st_mode))\n+\t\t\tcontinue;\n+\n+\t\tif (strbuf_readlink(&points_to, alias.buf, st.st_size) < 0) {\n+\t\t\terror_errno(_(\"strbuf_readlink('%s') failed\"), alias.buf);\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tif (!strncmp(points_to.buf, path, points_to.len) &&\n+\t\t\t(path[points_to.len] == '/')) {\n+\t\t\tstrbuf_addbuf(&info->alias, &alias);\n+\t\t\tstrbuf_addbuf(&info->points_to, &points_to);\n+\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"Found alias for '%s' : '%s' -> '%s'\",\n+\t\t\t\tpath, info->alias.buf, info->points_to.buf);\n+\t\t\tretval = 0;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\tretval = 0; /* no alias */\n+\n+done:\n+\tstrbuf_release(&alias);\n+\tstrbuf_release(&points_to);\n+\tif (closedir(dir) < 0)\n+\t\treturn error_errno(_(\"closedir('%s') failed\"), root);\n+\treturn retval;\n+}\n+\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info)\n+{\n+\tif (!info->alias.len)\n+\t\treturn NULL;\n+\n+\tif ((!strncmp(info->alias.buf, path, info->alias.len))\n+\t\t&& path[info->alias.len] == '/') {\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n+\t\tconst char *remainder = path + info->alias.len;\n+\n+\t\tstrbuf_addbuf(&tmp, &info->points_to);\n+\t\tstrbuf_add(&tmp, remainder, strlen(remainder));\n+\t\treturn strbuf_detach(&tmp, NULL);\n+\t}\n+\n+\treturn NULL;\n+}\ndiff --git a/compat/fsmonitor/fsm-path-utils-win32.c b/compat/fsmonitor/fsm-path-utils-win32.c\nindex a90b8f7925b..0d95bbb416f 100644\n--- a/compat/fsmonitor/fsm-path-utils-win32.c\n+++ b/compat/fsmonitor/fsm-path-utils-win32.c\n@@ -126,3 +126,20 @@ int fsmonitor__is_fs_remote(const char *path)\n \t\treturn -1;\n \treturn fs.is_remote;\n }\n+\n+/*\n+ * No-op for now.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info)\n+{\n+\treturn 0;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info)\n+{\n+\treturn NULL;\n+}\ndiff --git a/fsmonitor--daemon.h b/fsmonitor--daemon.h\nindex 2102a5c9ff5..e24838f9a86 100644\n--- a/fsmonitor--daemon.h\n+++ b/fsmonitor--daemon.h\n@@ -8,6 +8,7 @@\n #include \"run-command.h\"\n #include \"simple-ipc.h\"\n #include \"thread-utils.h\"\n+#include \"fsmonitor-path-utils.h\"\n \n struct fsmonitor_batch;\n struct fsmonitor_token_data;\n@@ -43,6 +44,7 @@ struct fsmonitor_daemon_state {\n \n \tstruct strbuf path_worktree_watch;\n \tstruct strbuf path_gitdir_watch;\n+\tstruct alias_info alias;\n \tint nr_paths_watching;\n \n \tstruct fsmonitor_token_data *current_token_data;\n@@ -59,6 +61,7 @@ struct fsmonitor_daemon_state {\n \n \tstruct ipc_server_data *ipc_server_data;\n \tstruct strbuf path_ipc;\n+\n };\n \n /*\ndiff --git a/fsmonitor-path-utils.h b/fsmonitor-path-utils.h\nindex 41edf5b934f..5bfdfb81c14 100644\n--- a/fsmonitor-path-utils.h\n+++ b/fsmonitor-path-utils.h\n@@ -1,13 +1,21 @@\n #ifndef FSM_PATH_UTILS_H\n #define FSM_PATH_UTILS_H\n \n+#include \"strbuf.h\"\n+\n+struct alias_info\n+{\n+\tstruct strbuf alias;\n+\tstruct strbuf points_to;\n+};\n+\n struct fs_info {\n \tint is_remote;\n \tchar *typename;\n };\n \n /*\n- * Get some basic filesystem informtion for the given path\n+ * Get some basic filesystem information for the given path\n  *\n  * The caller owns the storage that is occupied by fs_info and\n  * is responsible for releasing it.\n@@ -23,4 +31,30 @@ int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info);\n  */\n int fsmonitor__is_fs_remote(const char *path);\n \n+/*\n+ * Get the alias in given path, if any.\n+ *\n+ * Sets alias to the first alias that matches any part of the path.\n+ *\n+ * If an alias is found, info.alias and info.points_to are set to the\n+ * found mapping.\n+ *\n+ * Returns -1 on error, 0 otherwise.\n+ *\n+ * The caller owns the storage that is occupied by info.alias and\n+ * info.points_to and is responsible for releasing it.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info);\n+\n+/*\n+ * Resolve the path against the given alias.\n+ *\n+ * Returns the resolved path if there is one, NULL otherwise.\n+ *\n+ * The caller owns the storage that the returned string occupies and\n+ * is responsible for releasing it.\n+ */\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info);\n+\n #endif\n-- \ngitgitgadget\n\n"},{"id":"464999","messageId":"9c552239b57fb181189d3c6bb84a648a272cba62.1665783945.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 06/12] fsmonitor: add documentation for allowRemote and socketDir options","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:38Z","receivedAt":"2022-10-14T21:46:18Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nAdd documentation for 'fsmonitor.allowRemote' and 'fsmonitor.socketDir'.\nCall-out experimental nature of 'fsmonitor.allowRemote' and limited\nfilesystem support for 'fsmonitor.socketDir'.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Documentation/config.txt                   |  2 ++\n Documentation/config/fsmonitor--daemon.txt | 11 +++++++\n Documentation/git-fsmonitor--daemon.txt    | 37 ++++++++++++++++++++--\n 3 files changed, 47 insertions(+), 3 deletions(-)\n create mode 100644 Documentation/config/fsmonitor--daemon.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 5b5b9765699..1e205831656 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -423,6 +423,8 @@ include::config/filter.txt[]\n \n include::config/fsck.txt[]\n \n+include::config/fsmonitor--daemon.txt[]\n+\n include::config/gc.txt[]\n \n include::config/gitcvs.txt[]\ndiff --git a/Documentation/config/fsmonitor--daemon.txt b/Documentation/config/fsmonitor--daemon.txt\nnew file mode 100644\nindex 00000000000..c225c6c9e74\n--- /dev/null\n+++ b/Documentation/config/fsmonitor--daemon.txt\n@@ -0,0 +1,11 @@\n+fsmonitor.allowRemote::\n+    By default, the fsmonitor daemon refuses to work against network-mounted\n+    repositories. Setting `fsmonitor.allowRemote` to `true` overrides this\n+    behavior.  Only respected when `core.fsmonitor` is set to `true`.\n+\n+fsmonitor.socketDir::\n+    This Mac OS-specific option, if set, specifies the directory in\n+    which to create the Unix domain socket used for communication\n+    between the fsmonitor daemon and various Git commands. The directory must\n+    reside on a native Mac OS filesystem.  Only respected when `core.fsmonitor`\n+    is set to `true`.\ndiff --git a/Documentation/git-fsmonitor--daemon.txt b/Documentation/git-fsmonitor--daemon.txt\nindex cc142fb8612..8238eadb0e1 100644\n--- a/Documentation/git-fsmonitor--daemon.txt\n+++ b/Documentation/git-fsmonitor--daemon.txt\n@@ -3,7 +3,7 @@ git-fsmonitor{litdd}daemon(1)\n \n NAME\n ----\n-git-fsmonitor--daemon - A Built-in File System Monitor\n+git-fsmonitor--daemon - A Built-in Filesystem Monitor\n \n SYNOPSIS\n --------\n@@ -17,7 +17,7 @@ DESCRIPTION\n -----------\n \n A daemon to watch the working directory for file and directory\n-changes using platform-specific file system notification facilities.\n+changes using platform-specific filesystem notification facilities.\n \n This daemon communicates directly with commands like `git status`\n using the link:technical/api-simple-ipc.html[simple IPC] interface\n@@ -63,13 +63,44 @@ CAVEATS\n -------\n \n The fsmonitor daemon does not currently know about submodules and does\n-not know to filter out file system events that happen within a\n+not know to filter out filesystem events that happen within a\n submodule.  If fsmonitor daemon is watching a super repo and a file is\n modified within the working directory of a submodule, it will report\n the change (as happening against the super repo).  However, the client\n will properly ignore these extra events, so performance may be affected\n but it will not cause an incorrect result.\n \n+By default, the fsmonitor daemon refuses to work against network-mounted\n+repositories; this may be overridden by setting `fsmonitor.allowRemote` to\n+`true`. Note, however, that the fsmonitor daemon is not guaranteed to work\n+correctly with all network-mounted repositories and such use is considered\n+experimental.\n+\n+On Mac OS, the inter-process communication (IPC) between various Git\n+commands and the fsmonitor daemon is done via a Unix domain socket (UDS) -- a\n+special type of file -- which is supported by native Mac OS filesystems,\n+but not on network-mounted filesystems, NTFS, or FAT32.  Other filesystems\n+may or may not have the needed support; the fsmonitor daemon is not guaranteed\n+to work with these filesystems and such use is considered experimental.\n+\n+By default, the socket is created in the `.git` directory, however, if the\n+`.git` directory is on a network-mounted filesystem, it will be instead be\n+created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a\n+network-mounted filesystem in which case you must set the configuration\n+variable `fsmonitor.socketDir` to the path of a directory on a Mac OS native\n+filesystem in which to create the socket file.\n+\n+If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`)\n+is on a native Mac OS file filesystem the fsmonitor daemon will report an\n+error that will cause the daemon and the currently running command to exit.\n+\n+CONFIGURATION\n+-------------\n+\n+include::includes/cmd-config-section-all.txt[]\n+\n+include::config/fsmonitor--daemon.txt[]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n\n"},{"id":"465000","messageId":"295beb89ab10f001724fb64fa55944d05ee29fc7.1665783945.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 07/12] fsmonitor: prepare to share code between Mac OS and Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:39Z","receivedAt":"2022-10-14T21:46:22Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nLinux and Mac OS can share some of the code originally developed for Mac OS.\n\nMinor update to compat/fsmonitor/fsm-ipc-unix.c to make it cross-platform.\nMac OS and Linux can share fsm-ipc-unix.c\n\nBoth platforms can also share compat/fsmonitor/fsm-settings-unix.c but we\nwill leave room for future, platform-specific checks by having the platform-\nspecific implementations call into fsm-settings-unix.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Makefile                                      |  9 ++-\n compat/fsmonitor/fsm-health-linux.c           | 24 +++++++\n .../{fsm-ipc-darwin.c => fsm-ipc-unix.c}      |  8 +--\n compat/fsmonitor/fsm-settings-darwin.c        | 57 +----------------\n compat/fsmonitor/fsm-settings-linux.c         | 11 ++++\n compat/fsmonitor/fsm-settings-unix.c          | 62 +++++++++++++++++++\n compat/fsmonitor/fsm-settings-unix.h          | 11 ++++\n config.mak.uname                              |  1 +\n contrib/buildsystems/CMakeLists.txt           | 17 ++++-\n 9 files changed, 138 insertions(+), 62 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-health-linux.c\n rename compat/fsmonitor/{fsm-ipc-darwin.c => fsm-ipc-unix.c} (89%)\n create mode 100644 compat/fsmonitor/fsm-settings-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.h\n\ndiff --git a/Makefile b/Makefile\nindex 9553d590824..bbbde00ce49 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2036,13 +2036,20 @@ endif\n \n ifdef FSMONITOR_DAEMON_BACKEND\n \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n+\tifdef FSMONITOR_DAEMON_COMMON\n+\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_COMMON).o\n+\telse\n+\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n+\tendif\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o\n-\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n endif\n \n ifdef FSMONITOR_OS_SETTINGS\n \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n+\tifdef FSMONITOR_DAEMON_COMMON\n+\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_COMMON).o\n+\tendif\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o\n \tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o\n endif\ndiff --git a/compat/fsmonitor/fsm-health-linux.c b/compat/fsmonitor/fsm-health-linux.c\nnew file mode 100644\nindex 00000000000..b9f709e8548\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-health-linux.c\n@@ -0,0 +1,24 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsm-health.h\"\n+#include \"fsmonitor--daemon.h\"\n+\n+int fsm_health__ctor(struct fsmonitor_daemon_state *state)\n+{\n+\treturn 0;\n+}\n+\n+void fsm_health__dtor(struct fsmonitor_daemon_state *state)\n+{\n+\treturn;\n+}\n+\n+void fsm_health__loop(struct fsmonitor_daemon_state *state)\n+{\n+\treturn;\n+}\n+\n+void fsm_health__stop_async(struct fsmonitor_daemon_state *state)\n+{\n+}\ndiff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-unix.c\nsimilarity index 89%\nrename from compat/fsmonitor/fsm-ipc-darwin.c\nrename to compat/fsmonitor/fsm-ipc-unix.c\nindex ce843d63348..3ba3b9e17ed 100644\n--- a/compat/fsmonitor/fsm-ipc-darwin.c\n+++ b/compat/fsmonitor/fsm-ipc-unix.c\n@@ -10,7 +10,7 @@ static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n const char *fsmonitor_ipc__get_path(struct repository *r)\n {\n \tstatic const char *ipc_path = NULL;\n-\tSHA_CTX sha1ctx;\n+\tgit_SHA_CTX sha1ctx;\n \tchar *sock_dir = NULL;\n \tstruct strbuf ipc_file = STRBUF_INIT;\n \tunsigned char hash[SHA_DIGEST_LENGTH];\n@@ -28,9 +28,9 @@ const char *fsmonitor_ipc__get_path(struct repository *r)\n \t\treturn ipc_path;\n \t}\n \n-\tSHA1_Init(&sha1ctx);\n-\tSHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n-\tSHA1_Final(hash, &sha1ctx);\n+\tgit_SHA1_Init(&sha1ctx);\n+\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n+\tgit_SHA1_Final(hash, &sha1ctx);\n \n \trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n \ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c\nindex 6abbc7af3ab..2f5c6a88bf0 100644\n--- a/compat/fsmonitor/fsm-settings-darwin.c\n+++ b/compat/fsmonitor/fsm-settings-darwin.c\n@@ -1,62 +1,11 @@\n #include \"config.h\"\n #include \"fsmonitor.h\"\n #include \"fsmonitor-ipc.h\"\n-#include \"fsmonitor-settings.h\"\n #include \"fsmonitor-path-utils.h\"\n-\n- /*\n- * For the builtin FSMonitor, we create the Unix domain socket for the\n- * IPC in the .git directory.  If the working directory is remote,\n- * then the socket will be created on the remote file system.  This\n- * can fail if the remote file system does not support UDS file types\n- * (e.g. smbfs to a Windows server) or if the remote kernel does not\n- * allow a non-local process to bind() the socket.  (These problems\n- * could be fixed by moving the UDS out of the .git directory and to a\n- * well-known local directory on the client machine, but care should\n- * be taken to ensure that $HOME is actually local and not a managed\n- * file share.)\n- *\n- * FAT32 and NTFS working directories are problematic too.\n- *\n- * The builtin FSMonitor uses a Unix domain socket in the .git\n- * directory for IPC.  These Windows drive formats do not support\n- * Unix domain sockets, so mark them as incompatible for the daemon.\n- *\n- */\n-static enum fsmonitor_reason check_uds_volume(struct repository *r)\n-{\n-\tstruct fs_info fs;\n-\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n-\tstruct strbuf path = STRBUF_INIT;\n-\tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n-\n-\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n-\t\tstrbuf_release(&path);\n-\t\treturn FSMONITOR_REASON_ERROR;\n-\t}\n-\n-\tstrbuf_release(&path);\n-\n-\tif (fs.is_remote ||\n-\t\t!strcmp(fs.typename, \"msdos\") ||\n-\t\t!strcmp(fs.typename, \"ntfs\")) {\n-\t\tfree(fs.typename);\n-\t\treturn FSMONITOR_REASON_NOSOCKETS;\n-\t}\n-\n-\tfree(fs.typename);\n-\treturn FSMONITOR_REASON_OK;\n-}\n+#include \"fsmonitor-settings.h\"\n+#include \"fsm-settings-unix.h\"\n \n enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n {\n-\tenum fsmonitor_reason reason;\n-\n-\tif (ipc) {\n-\t\treason = check_uds_volume(r);\n-\t\tif (reason != FSMONITOR_REASON_OK)\n-\t\t\treturn reason;\n-\t}\n-\n-\treturn FSMONITOR_REASON_OK;\n+    return fsm_os__incompatible_unix(r, ipc);\n }\ndiff --git a/compat/fsmonitor/fsm-settings-linux.c b/compat/fsmonitor/fsm-settings-linux.c\nnew file mode 100644\nindex 00000000000..2f5c6a88bf0\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-settings-linux.c\n@@ -0,0 +1,11 @@\n+#include \"config.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n+#include \"fsmonitor-settings.h\"\n+#include \"fsm-settings-unix.h\"\n+\n+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n+{\n+    return fsm_os__incompatible_unix(r, ipc);\n+}\ndiff --git a/compat/fsmonitor/fsm-settings-unix.c b/compat/fsmonitor/fsm-settings-unix.c\nnew file mode 100644\nindex 00000000000..6b43e26720e\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-settings-unix.c\n@@ -0,0 +1,62 @@\n+#include \"config.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n+#include \"fsm-settings-unix.h\"\n+\n+ /*\n+ * For the builtin FSMonitor, we create the Unix domain socket for the\n+ * IPC in the .git directory.  If the working directory is remote,\n+ * then the socket will be created on the remote file system.  This\n+ * can fail if the remote file system does not support UDS file types\n+ * (e.g. smbfs to a Windows server) or if the remote kernel does not\n+ * allow a non-local process to bind() the socket.  (These problems\n+ * could be fixed by moving the UDS out of the .git directory and to a\n+ * well-known local directory on the client machine, but care should\n+ * be taken to ensure that $HOME is actually local and not a managed\n+ * file share.)\n+ *\n+ * FAT32 and NTFS working directories are problematic too.\n+ *\n+ * The builtin FSMonitor uses a Unix domain socket in the .git\n+ * directory for IPC.  These Windows drive formats do not support\n+ * Unix domain sockets, so mark them as incompatible for the daemon.\n+ *\n+ */\n+static enum fsmonitor_reason check_uds_volume(struct repository *r)\n+{\n+\tstruct fs_info fs;\n+\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n+\n+\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n+\t\tstrbuf_release(&path);\n+\t\treturn FSMONITOR_REASON_ERROR;\n+\t}\n+\n+\tstrbuf_release(&path);\n+\n+\tif (fs.is_remote ||\n+\t\t!strcmp(fs.typename, \"msdos\") ||\n+\t\t!strcmp(fs.typename, \"ntfs\")) {\n+\t\tfree(fs.typename);\n+\t\treturn FSMONITOR_REASON_NOSOCKETS;\n+\t}\n+\n+\tfree(fs.typename);\n+\treturn FSMONITOR_REASON_OK;\n+}\n+\n+enum fsmonitor_reason fsm_os__incompatible_unix(struct repository *r, int ipc)\n+{\n+\tenum fsmonitor_reason reason;\n+\n+\tif (ipc) {\n+\t\treason = check_uds_volume(r);\n+\t\tif (reason != FSMONITOR_REASON_OK)\n+\t\t\treturn reason;\n+\t}\n+\n+\treturn FSMONITOR_REASON_OK;\n+}\ndiff --git a/compat/fsmonitor/fsm-settings-unix.h b/compat/fsmonitor/fsm-settings-unix.h\nnew file mode 100644\nindex 00000000000..8a62971190b\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-settings-unix.h\n@@ -0,0 +1,11 @@\n+#ifndef FSM_SETTINGS_UNIX_H\n+#define FSM_SETTINGS_UNIX_H\n+\n+#ifdef HAVE_FSMONITOR_OS_SETTINGS\n+/*\n+ * Check for compatibility on unix-like systems (e.g. Darwin and Linux)\n+ */\n+enum fsmonitor_reason fsm_os__incompatible_unix(struct repository *r, int ipc);\n+#endif /* HAVE_FSMONITOR_OS_SETTINGS */\n+\n+#endif /* FSM_SETTINGS_UNIX_H */\ndiff --git a/config.mak.uname b/config.mak.uname\nindex d63629fe807..9f716cfba81 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -165,6 +165,7 @@ ifeq ($(uname_S),Darwin)\n \tifndef NO_UNIX_SOCKETS\n \tFSMONITOR_DAEMON_BACKEND = darwin\n \tFSMONITOR_OS_SETTINGS = darwin\n+\tFSMONITOR_DAEMON_COMMON = unix\n \tendif\n \tendif\n \ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 787738e6fa3..4c6e84da346 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -304,7 +304,17 @@ else()\n endif()\n \n if(SUPPORTS_SIMPLE_IPC)\n-\tif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n+\t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-unix.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-linux.c)\n+\n+\t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-unix.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-linux.c)\n+\telseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n@@ -315,12 +325,13 @@ if(SUPPORTS_SIMPLE_IPC)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c)\n \telseif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n-\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-unix.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)\n-\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-darwin.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c)\n \n \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-unix.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)\n \tendif()\n endif()\n-- \ngitgitgadget\n\n"},{"id":"465001","messageId":"07650ecd27b91d49a83427187a846c18424f9ab4.1665783945.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 10/12] fsmonitor: enable fsmonitor for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:42Z","receivedAt":"2022-10-14T21:46:41Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nUpdate build to enable fsmonitor for Linux.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n config.mak.uname | 9 +++++++++\n 1 file changed, 9 insertions(+)\n\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 9f716cfba81..d8889f8570a 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -68,6 +68,15 @@ ifeq ($(uname_S),Linux)\n \tifneq ($(findstring .el7.,$(uname_R)),)\n \t\tBASIC_CFLAGS += -std=c99\n \tendif\n+\t# The builtin FSMonitor on Linux builds upon Simple-IPC.  Both require\n+\t# Unix domain sockets and PThreads.\n+\tifndef NO_PTHREADS\n+\tifndef NO_UNIX_SOCKETS\n+\tFSMONITOR_DAEMON_BACKEND = linux\n+\tFSMONITOR_OS_SETTINGS = linux\n+\tFSMONITOR_DAEMON_COMMON = unix\n+\tendif\n+\tendif\n endif\n ifeq ($(uname_S),GNU/kFreeBSD)\n \tHAVE_ALLOCA_H = YesPlease\n-- \ngitgitgadget\n\n"},{"id":"465002","messageId":"6682938fff8a3bc7c0c4b08fdfd20effc4b2fc66.1665783945.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 11/12] fsmonitor: test updates","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:43Z","receivedAt":"2022-10-14T21:46:43Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nt7527-builtin-fsmonitor was leaking fsmonitor--daemon processes in some\ncases.\n\nAccomodate slight difference in the number of events generated on Linux.\n\nOn lower-powered systems, spin a little to give the daemon time\nto respond to and log filesystem events.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n t/t7527-builtin-fsmonitor.sh | 72 +++++++++++++++++++++++++++++-------\n 1 file changed, 58 insertions(+), 14 deletions(-)\n\ndiff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh\nindex 1746d30cf6a..d27bd3662a7 100755\n--- a/t/t7527-builtin-fsmonitor.sh\n+++ b/t/t7527-builtin-fsmonitor.sh\n@@ -13,7 +13,7 @@ fi\n stop_daemon_delete_repo () {\n \tr=$1 &&\n \ttest_might_fail git -C $r fsmonitor--daemon stop &&\n-\trm -rf $1\n+\trm -rf $r\n }\n \n start_daemon () {\n@@ -72,6 +72,32 @@ start_daemon () {\n \t)\n }\n \n+IMPLICIT_TIMEOUT=5\n+\n+wait_for_update () {\n+\tfunc=$1 &&\n+\tfile=$2 &&\n+\tsz=$(wc -c < \"$file\") &&\n+\tlast=0 &&\n+\t$func &&\n+\tk=0 &&\n+\twhile test \"$k\" -lt $IMPLICIT_TIMEOUT\n+\tdo\n+\t\tnsz=$(wc -c < \"$file\")\n+\t\tif test \"$nsz\" -gt \"$sz\"\n+\t\tthen\n+\t\t\tif test \"$last\" -eq \"$nsz\"\n+\t\t\tthen\n+\t\t\t\treturn 0\n+\t\t\tfi\n+\t\t\tlast=$nsz\n+\t\tfi\n+\t\tsleep 1\n+\t\tk=$(( $k + 1 ))\n+\tdone &&\n+\treturn 0\n+}\n+\n # Is a Trace2 data event present with the given catetory and key?\n # We do not care what the value is.\n #\n@@ -137,7 +163,6 @@ test_expect_success 'implicit daemon start' '\n # machines (where it might take a moment to wake and reschedule the\n # daemon process) to avoid false alarms during test runs.)\n #\n-IMPLICIT_TIMEOUT=5\n \n verify_implicit_shutdown () {\n \tr=$1 &&\n@@ -373,6 +398,10 @@ create_files () {\n \techo 3 >dir2/new\n }\n \n+rename_directory () {\n+\tmv dirtorename dirrenamed\n+}\n+\n rename_files () {\n \tmv rename renamed &&\n \tmv dir1/rename dir1/renamed &&\n@@ -427,10 +456,12 @@ test_expect_success 'edit some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tedit_files &&\n+\twait_for_update edit_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/modified$\"  .git/trace &&\n \tgrep \"^event: dir2/modified$\"  .git/trace &&\n \tgrep \"^event: modified$\"       .git/trace &&\n@@ -442,10 +473,12 @@ test_expect_success 'create some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tcreate_files &&\n+\twait_for_update create_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/new$\" .git/trace &&\n \tgrep \"^event: dir2/new$\" .git/trace &&\n \tgrep \"^event: new$\"      .git/trace\n@@ -456,10 +489,12 @@ test_expect_success 'delete some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tdelete_files &&\n+\twait_for_update delete_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/delete$\" .git/trace &&\n \tgrep \"^event: dir2/delete$\" .git/trace &&\n \tgrep \"^event: delete$\"      .git/trace\n@@ -470,10 +505,12 @@ test_expect_success 'rename some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\trename_files &&\n+\twait_for_update rename_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/rename$\"  .git/trace &&\n \tgrep \"^event: dir2/rename$\"  .git/trace &&\n \tgrep \"^event: rename$\"       .git/trace &&\n@@ -487,10 +524,12 @@ test_expect_success 'rename directory' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tmv dirtorename dirrenamed &&\n+\twait_for_update rename_directory \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dirtorename/*$\" .git/trace &&\n \tgrep \"^event: dirrenamed/*$\"  .git/trace\n '\n@@ -500,10 +539,12 @@ test_expect_success 'file changes to directory' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tfile_to_directory &&\n+\twait_for_update file_to_directory \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: delete$\"     .git/trace &&\n \tgrep \"^event: delete/new$\" .git/trace\n '\n@@ -513,10 +554,12 @@ test_expect_success 'directory changes to a file' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tdirectory_to_file &&\n+\twait_for_update directory_to_file \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1$\" .git/trace\n '\n \n@@ -561,7 +604,7 @@ test_expect_success 'flush cached data' '\n \ttest-tool -C test_flush fsmonitor-client query --token \"builtin:test_00000002:0\" >actual_2 &&\n \tnul_to_q <actual_2 >actual_q2 &&\n \n-\tgrep \"^builtin:test_00000002:0Q$\" actual_q2 &&\n+\tgrep \"^builtin:test_00000002:[0-1]Q$\" actual_q2 &&\n \n \t>test_flush/file_3 &&\n \n@@ -732,7 +775,8 @@ u_values=\"$u1 $u2\"\n for u in $u_values\n do\n \ttest_expect_success \"unicode in repo root path: $u\" '\n-\t\ttest_when_finished \"stop_daemon_delete_repo $u\" &&\n+\t\ttest_when_finished \\\n+\t\t\"stop_daemon_delete_repo `echo \"$u\" | sed 's:x:\\\\\\\\\\\\\\\\\\\\\\\\\\\\x:g'`\" &&\n \n \t\tgit init \"$u\" &&\n \t\techo 1 >\"$u\"/file1 &&\n@@ -814,8 +858,7 @@ my_match_and_clean () {\n }\n \n test_expect_success 'submodule always visited' '\n-\ttest_when_finished \"git -C super fsmonitor--daemon stop; \\\n-\t\t\t    rm -rf super; \\\n+\ttest_when_finished \"rm -rf super; \\\n \t\t\t    rm -rf sub\" &&\n \n \tcreate_super super &&\n@@ -883,7 +926,8 @@ have_t2_error_event () {\n }\n \n test_expect_success \"stray submodule super-prefix warning\" '\n-\ttest_when_finished \"rm -rf super; \\\n+\ttest_when_finished \"git -C super/dir_1/dir_2/sub fsmonitor--daemon stop; \\\n+\t\t\t    rm -rf super; \\\n \t\t\t    rm -rf sub;   \\\n \t\t\t    rm super-sub.trace\" &&\n \n-- \ngitgitgadget\n\n"},{"id":"465003","messageId":"4f9c5358475867af75acd865505884f99801ca17.1665783945.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 09/12] fsmonitor: implement filesystem change listener for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:41Z","receivedAt":"2022-10-14T21:46:45Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nImplement a filesystem change listener for Linux based on the inotify API:\nhttps://man7.org/linux/man-pages/man7/inotify.7.html\n\ninotify requires registering a watch on every directory in the worktree and\nspecial handling of moves/renames.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-listen-linux.c | 664 ++++++++++++++++++++++++++++\n 1 file changed, 664 insertions(+)\n create mode 100644 compat/fsmonitor/fsm-listen-linux.c\n\ndiff --git a/compat/fsmonitor/fsm-listen-linux.c b/compat/fsmonitor/fsm-listen-linux.c\nnew file mode 100644\nindex 00000000000..0ac131a9497\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-listen-linux.c\n@@ -0,0 +1,664 @@\n+#include \"cache.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsm-listen.h\"\n+#include \"fsmonitor--daemon.h\"\n+#include <dirent.h>\n+#include <fcntl.h>\n+#include <sys/inotify.h>\n+#include <sys/stat.h>\n+\n+/*\n+ * Safe value to bitwise OR with rest of mask for\n+ * kernels that do not support IN_MASK_CREATE\n+ */\n+#ifndef IN_MASK_CREATE\n+#define IN_MASK_CREATE 0x00000000\n+#endif\n+\n+enum shutdown_reason {\n+\tSHUTDOWN_CONTINUE = 0,\n+\tSHUTDOWN_STOP,\n+\tSHUTDOWN_ERROR,\n+\tSHUTDOWN_FORCE\n+};\n+\n+struct watch_entry {\n+\tstruct hashmap_entry ent;\n+\tint wd;\n+\tuint32_t cookie;\n+\tconst char *dir;\n+};\n+\n+struct rename_entry {\n+\tstruct hashmap_entry ent;\n+\ttime_t whence;\n+\tuint32_t cookie;\n+\tconst char *dir;\n+};\n+\n+struct fsm_listen_data {\n+\tint fd_inotify;\n+\tenum shutdown_reason shutdown;\n+\tstruct hashmap watches;\n+\tstruct hashmap renames;\n+\tstruct hashmap revwatches;\n+};\n+\n+static int watch_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct watch_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct watch_entry, ent);\n+\te2 = container_of(eptr, const struct watch_entry, ent);\n+\treturn e1->wd != e2->wd;\n+}\n+\n+static int revwatches_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct watch_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct watch_entry, ent);\n+\te2 = container_of(eptr, const struct watch_entry, ent);\n+\treturn strcmp(e1->dir, e2->dir);\n+}\n+\n+static int rename_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct rename_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct rename_entry, ent);\n+\te2 = container_of(eptr, const struct rename_entry, ent);\n+\treturn e1->cookie != e2->cookie;\n+}\n+\n+/*\n+ * Register an inotify watch, add watch descriptor to path mapping\n+ * and the reverse mapping.\n+ */\n+static int add_watch(const char *path, struct fsm_listen_data *data)\n+{\n+\tconst char *interned = strintern(path);\n+\tstruct watch_entry *w1, *w2;\n+\n+\t/* add the inotify watch, don't allow watches to be modified */\n+\tint wd = inotify_add_watch(data->fd_inotify, interned,\n+\t\t\t\t(IN_ALL_EVENTS | IN_ONLYDIR | IN_MASK_CREATE)\n+\t\t\t\t^ IN_ACCESS ^ IN_CLOSE ^ IN_OPEN);\n+\tif (wd < 0)\n+\t\treturn error_errno(\"inotify_add_watch('%s') failed\", interned);\n+\n+\t/* add watch descriptor -> directory mapping */\n+\tCALLOC_ARRAY(w1, 1);\n+\tw1->wd = wd;\n+\tw1->dir = interned;\n+\thashmap_entry_init(&w1->ent, memhash(&w1->wd, sizeof(int)));\n+\thashmap_add(&data->watches, &w1->ent);\n+\n+\t/* add directory -> watch descriptor mapping */\n+\tCALLOC_ARRAY(w2, 1);\n+\tw2->wd = wd;\n+\tw2->dir = interned;\n+\thashmap_entry_init(&w2->ent, memhash(w2->dir, strlen(w2->dir)));\n+\thashmap_add(&data->revwatches, &w2->ent);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove the inotify watch, the watch descriptor to path mapping\n+ * and the reverse mapping.\n+ */\n+static void remove_watch(struct watch_entry *w,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct watch_entry k1, k2, *w1, *w2;\n+\n+\t/* remove watch, ignore error if kernel already did it */\n+\tif (inotify_rm_watch(data->fd_inotify, w->wd) && errno != EINVAL)\n+\t\terror_errno(\"inotify_rm_watch() failed\");\n+\n+\thashmap_entry_init(&k1.ent, memhash(&w->wd, sizeof(int)));\n+\tw1 = hashmap_remove_entry(&data->watches, &k1, ent, NULL);\n+\tif (!w1)\n+\t\tBUG(\"Double remove of watch for '%s'\", w->dir);\n+\n+\tif (w1->cookie)\n+\t\tBUG(\"Removing watch for '%s' which has a pending rename\", w1->dir);\n+\n+\thashmap_entry_init(&k2.ent, memhash(w->dir, strlen(w->dir)));\n+\tw2 = hashmap_remove_entry(&data->revwatches, &k2, ent, NULL);\n+\tif (!w2)\n+\t\tBUG(\"Double remove of reverse watch for '%s'\", w->dir);\n+\n+\t/* w1->dir and w2->dir are interned strings, we don't own them */\n+\tfree(w1);\n+\tfree(w2);\n+}\n+\n+/*\n+ * Check for stale directory renames.\n+ *\n+ * https://man7.org/linux/man-pages/man7/inotify.7.html\n+ *\n+ * Allow for some small timeout to account for the fact that insertion of the\n+ * IN_MOVED_FROM+IN_MOVED_TO event pair is not atomic, and the possibility that\n+ * there may not be any IN_MOVED_TO event.\n+ *\n+ * If the IN_MOVED_TO event is not received within the timeout then events have\n+ * been missed and the monitor is in an inconsistent state with respect to the\n+ * filesystem.\n+ */\n+static int check_stale_dir_renames(struct hashmap *renames, time_t max_age)\n+{\n+\tstruct rename_entry *re;\n+\tstruct hashmap_iter iter;\n+\n+\thashmap_for_each_entry(renames, &iter, re, ent) {\n+\t\tif (re->whence <= max_age)\n+\t\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Track pending renames.\n+ *\n+ * Tracking is done via a event cookie to watch descriptor mapping.\n+ *\n+ * A rename is not complete until matching a IN_MOVED_TO event is received\n+ * for a corresponding IN_MOVED_FROM event.\n+ */\n+static void add_dir_rename(uint32_t cookie, const char *path,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct watch_entry k, *w;\n+\tstruct rename_entry *re;\n+\n+\t/* lookup the watch descriptor for the given path */\n+\thashmap_entry_init(&k.ent, memhash(path, strlen(path)));\n+\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n+\tif (!w) /* should never happen */\n+\t\tBUG(\"No watch for '%s'\", path);\n+\tw->cookie = cookie;\n+\n+\t/* add the pending rename to match against later */\n+\tCALLOC_ARRAY(re, 1);\n+\tre->dir = w->dir;\n+\tre->cookie = w->cookie;\n+\tre->whence = time(NULL);\n+\thashmap_entry_init(&re->ent, memhash(&re->cookie, sizeof(uint32_t)));\n+\thashmap_add(&data->renames, &re->ent);\n+}\n+\n+/*\n+ * Handle directory renames\n+ *\n+ * Once a IN_MOVED_TO event is received, lookup the rename tracking information\n+ * via the event cookie and use this information to update the watch.\n+ */\n+static void rename_dir(uint32_t cookie, const char *path,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct rename_entry rek, *re;\n+\tstruct watch_entry k, *w;\n+\n+\t/* lookup a pending rename to match */\n+\trek.cookie = cookie;\n+\thashmap_entry_init(&rek.ent, memhash(&rek.cookie, sizeof(uint32_t)));\n+\tre = hashmap_get_entry(&data->renames, &rek, ent, NULL);\n+\tif (re) {\n+\t\tk.dir = re->dir;\n+\t\thashmap_entry_init(&k.ent, memhash(k.dir, strlen(k.dir)));\n+\t\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n+\t\tif (w) {\n+\t\t\tw->cookie = 0; /* rename handled */\n+\t\t\tremove_watch(w, data);\n+\t\t\tadd_watch(path, data);\n+\t\t} else {\n+\t\t\tBUG(\"No matching watch\");\n+\t\t}\n+\t} else {\n+\t\tBUG(\"No matching cookie\");\n+\t}\n+}\n+\n+/*\n+ * Recursively add watches to every directory under path\n+ */\n+static int register_inotify(const char *path, struct fsm_listen_data *data)\n+{\n+\tDIR *dir;\n+\tstruct strbuf current = STRBUF_INIT;\n+\tstruct dirent *de;\n+\tstruct stat fs;\n+\tint ret = -1;\n+\n+\tdir = opendir(path);\n+\tif (!dir)\n+\t\treturn error_errno(\"opendir('%s') failed\", path);\n+\n+\twhile ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {\n+\t\tstrbuf_reset(&current);\n+\t\tstrbuf_addf(&current, \"%s/%s\", path, de->d_name);\n+\t\tif (lstat(current.buf, &fs)) {\n+\t\t\terror_errno(\"lstat('%s') failed\", current.buf);\n+\t\t\tgoto failed;\n+\t\t}\n+\n+\t\t/* recurse into directory */\n+\t\tif (S_ISDIR(fs.st_mode)) {\n+\t\t\tif (add_watch(current.buf, data))\n+\t\t\t\tgoto failed;\n+\t\t\tif (register_inotify(current.buf, data))\n+\t\t\t\tgoto failed;\n+\t\t}\n+\t}\n+\tret = 0;\n+\n+failed:\n+\tstrbuf_release(&current);\n+\tif (closedir(dir) < 0)\n+\t\treturn error_errno(\"closedir('%s') failed\", path);\n+\treturn ret;\n+}\n+\n+static int em_rename_dir_from(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVED_FROM));\n+}\n+\n+static int em_rename_dir_to(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVED_TO));\n+}\n+\n+static int em_remove_watch(u_int32_t mask)\n+{\n+\treturn (mask & IN_DELETE_SELF);\n+}\n+\n+static int em_dir_renamed(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVE));\n+}\n+\n+static int em_dir_created(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_CREATE));\n+}\n+\n+static int em_dir_deleted(uint32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_DELETE));\n+}\n+\n+static int em_force_shutdown(u_int32_t mask)\n+{\n+\treturn (mask & IN_UNMOUNT) || (mask & IN_Q_OVERFLOW);\n+}\n+\n+static int em_ignore(u_int32_t mask)\n+{\n+\treturn (mask & IN_IGNORED) || (mask & IN_MOVE_SELF);\n+}\n+\n+static void log_mask_set(const char *path, u_int32_t mask)\n+{\n+\tstruct strbuf msg = STRBUF_INIT;\n+\n+\tif (mask & IN_ACCESS)\n+\t\tstrbuf_addstr(&msg, \"IN_ACCESS|\");\n+\tif (mask & IN_MODIFY)\n+\t\tstrbuf_addstr(&msg, \"IN_MODIFY|\");\n+\tif (mask & IN_ATTRIB)\n+\t\tstrbuf_addstr(&msg, \"IN_ATTRIB|\");\n+\tif (mask & IN_CLOSE_WRITE)\n+\t\tstrbuf_addstr(&msg, \"IN_CLOSE_WRITE|\");\n+\tif (mask & IN_CLOSE_NOWRITE)\n+\t\tstrbuf_addstr(&msg, \"IN_CLOSE_NOWRITE|\");\n+\tif (mask & IN_OPEN)\n+\t\tstrbuf_addstr(&msg, \"IN_OPEN|\");\n+\tif (mask & IN_MOVED_FROM)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVED_FROM|\");\n+\tif (mask & IN_MOVED_TO)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVED_TO|\");\n+\tif (mask & IN_CREATE)\n+\t\tstrbuf_addstr(&msg, \"IN_CREATE|\");\n+\tif (mask & IN_DELETE)\n+\t\tstrbuf_addstr(&msg, \"IN_DELETE|\");\n+\tif (mask & IN_DELETE_SELF)\n+\t\tstrbuf_addstr(&msg, \"IN_DELETE_SELF|\");\n+\tif (mask & IN_MOVE_SELF)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVE_SELF|\");\n+\tif (mask & IN_UNMOUNT)\n+\t\tstrbuf_addstr(&msg, \"IN_UNMOUNT|\");\n+\tif (mask & IN_Q_OVERFLOW)\n+\t\tstrbuf_addstr(&msg, \"IN_Q_OVERFLOW|\");\n+\tif (mask & IN_IGNORED)\n+\t\tstrbuf_addstr(&msg, \"IN_IGNORED|\");\n+\tif (mask & IN_ISDIR)\n+\t\tstrbuf_addstr(&msg, \"IN_ISDIR|\");\n+\n+\ttrace_printf_key(&trace_fsmonitor, \"inotify_event: '%s', mask=%#8.8x %s\",\n+\t\t\t\tpath, mask, msg.buf);\n+\n+\tstrbuf_release(&msg);\n+}\n+\n+int fsm_listen__ctor(struct fsmonitor_daemon_state *state)\n+{\n+\tint fd;\n+\tint ret = 0;\n+\tstruct fsm_listen_data *data;\n+\n+\tCALLOC_ARRAY(data, 1);\n+\tstate->listen_data = data;\n+\tstate->listen_error_code = -1;\n+\tdata->shutdown = SHUTDOWN_ERROR;\n+\n+\tfd = inotify_init1(O_NONBLOCK);\n+\tif (fd < 0)\n+\t\treturn error_errno(\"inotify_init1() failed\");\n+\n+\tdata->fd_inotify = fd;\n+\n+\thashmap_init(&data->watches, watch_entry_cmp, NULL, 0);\n+\thashmap_init(&data->renames, rename_entry_cmp, NULL, 0);\n+\thashmap_init(&data->revwatches, revwatches_entry_cmp, NULL, 0);\n+\n+\tif (add_watch(state->path_worktree_watch.buf, data))\n+\t\tret = -1;\n+\telse if (register_inotify(state->path_worktree_watch.buf, data))\n+\t\tret = -1;\n+\telse if (state->nr_paths_watching > 1) {\n+\t\tif (add_watch(state->path_gitdir_watch.buf, data))\n+\t\t\tret = -1;\n+\t\telse if (register_inotify(state->path_gitdir_watch.buf, data))\n+\t\t\tret = -1;\n+\t}\n+\n+\tif (!ret) {\n+\t\tstate->listen_error_code = 0;\n+\t\tdata->shutdown = SHUTDOWN_CONTINUE;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+void fsm_listen__dtor(struct fsmonitor_daemon_state *state)\n+{\n+\tstruct fsm_listen_data *data;\n+\tstruct hashmap_iter iter;\n+\tstruct watch_entry *w;\n+\tint fd;\n+\n+\tif (!state || !state->listen_data)\n+\t\treturn;\n+\n+\tdata = state->listen_data;\n+\tfd = data->fd_inotify;\n+\n+\thashmap_for_each_entry(&data->watches, &iter, w, ent) {\n+\t\tw->cookie = 0; /* ignore any pending renames */\n+\t\tremove_watch(w, data);\n+\t}\n+\thashmap_clear(&data->watches);\n+\n+\thashmap_clear(&data->revwatches); /* remove_watch freed the entries */\n+\n+\thashmap_clear_and_free(&data->renames, struct rename_entry, ent);\n+\n+\tFREE_AND_NULL(state->listen_data);\n+\n+\tif (fd && (close(fd) < 0))\n+\t\terror_errno(_(\"closing inotify file descriptor failed\"));\n+}\n+\n+void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)\n+{\n+\tif (!state->listen_data->shutdown)\n+\t\tstate->listen_data->shutdown = SHUTDOWN_STOP;\n+}\n+\n+/*\n+ * Process a single inotify event and queue for publication.\n+ */\n+static int process_event(const char *path,\n+\tconst struct inotify_event *event,\n+\tstruct fsmonitor_batch *batch,\n+\tstruct string_list *cookie_list,\n+\tstruct fsmonitor_daemon_state *state)\n+{\n+\tconst char *rel;\n+\tconst char *last_sep;\n+\n+\tswitch (fsmonitor_classify_path_absolute(state, path)) {\n+\t\tcase IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:\n+\t\tcase IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:\n+\t\t\t/* Use just the filename of the cookie file. */\n+\t\t\tlast_sep = find_last_dir_sep(path);\n+\t\t\tstring_list_append(cookie_list,\n+\t\t\t\t\tlast_sep ? last_sep + 1 : path);\n+\t\t\tbreak;\n+\t\tcase IS_INSIDE_DOT_GIT:\n+\t\tcase IS_INSIDE_GITDIR:\n+\t\t\tbreak;\n+\t\tcase IS_DOT_GIT:\n+\t\tcase IS_GITDIR:\n+\t\t\t/*\n+\t\t\t* If .git directory is deleted or renamed away,\n+\t\t\t* we have to quit.\n+\t\t\t*/\n+\t\t\tif (em_dir_deleted(event->mask)) {\n+\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\"event: gitdir removed\");\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\tif (em_dir_renamed(event->mask)) {\n+\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\"event: gitdir renamed\");\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase IS_WORKDIR_PATH:\n+\t\t\t/* normal events in the working directory */\n+\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n+\t\t\t\tlog_mask_set(path, event->mask);\n+\n+\t\t\trel = path + state->path_worktree_watch.len + 1;\n+\t\t\tfsmonitor_batch__add_path(batch, rel);\n+\n+\t\t\tif (em_dir_deleted(event->mask))\n+\t\t\t\tbreak;\n+\n+\t\t\t/* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */\n+\t\t\tif (em_rename_dir_from(event->mask))\n+\t\t\t\tadd_dir_rename(event->cookie, path, state->listen_data);\n+\n+\t\t\t/* received IN_MOVE_TO, update watch to reflect new path */\n+\t\t\tif (em_rename_dir_to(event->mask))\n+\t\t\t\trename_dir(event->cookie, path, state->listen_data);\n+\n+\t\t\tif (em_dir_created(event->mask)) {\n+\t\t\t\tif (add_watch(path, state->listen_data)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t\tif (register_inotify(path, state->listen_data)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase IS_OUTSIDE_CONE:\n+\t\tdefault:\n+\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\"ignoring '%s'\", path);\n+\t\t\tbreak;\n+\t}\n+\treturn 0;\n+done:\n+\treturn -1;\n+}\n+\n+/*\n+ * Read the inotify event stream and pre-process events before further\n+ * processing and eventual publishing.\n+ */\n+static void handle_events(struct fsmonitor_daemon_state *state)\n+{\n+\t /* See https://man7.org/linux/man-pages/man7/inotify.7.html */\n+\tchar buf[4096]\n+\t\t__attribute__ ((aligned(__alignof__(struct inotify_event))));\n+\n+\tstruct hashmap watches = state->listen_data->watches;\n+\tstruct fsmonitor_batch *batch = NULL;\n+\tstruct string_list cookie_list = STRING_LIST_INIT_DUP;\n+\tstruct watch_entry k, *w;\n+\tstruct strbuf path;\n+\tconst struct inotify_event *event;\n+\tint fd = state->listen_data->fd_inotify;\n+\tssize_t len;\n+\tchar *ptr, *p;\n+\n+\tstrbuf_init(&path, PATH_MAX);\n+\n+\tfor(;;) {\n+\t\tlen = read(fd, buf, sizeof(buf));\n+\t\tif (len == -1 && errno != EAGAIN) {\n+\t\t\terror_errno(_(\"reading inotify message stream failed\"));\n+\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\t/* nothing to read */\n+\t\tif (len <= 0)\n+\t\t\tgoto done;\n+\n+\t\t/* Loop over all events in the buffer. */\n+\t\tfor (ptr = buf; ptr < buf + len;\n+\t\t\t ptr += sizeof(struct inotify_event) + event->len) {\n+\n+\t\t\tevent = (const struct inotify_event *) ptr;\n+\n+\t\t\tif (em_ignore(event->mask))\n+\t\t\t\tcontinue;\n+\n+\t\t\t/* File system was unmounted or event queue overflowed */\n+\t\t\tif (em_force_shutdown(event->mask)) {\n+\t\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n+\t\t\t\t\tlog_mask_set(\"Forcing shutdown\", event->mask);\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\thashmap_entry_init(&k.ent, memhash(&event->wd, sizeof(int)));\n+\t\t\tk.wd = event->wd;\n+\n+\t\t\tw = hashmap_get_entry(&watches, &k, ent, NULL);\n+\t\t\tif (!w) /* should never happen */\n+\t\t\t\tBUG(\"No watch for '%s'\", event->name);\n+\n+\t\t\t/* directory watch was removed */\n+\t\t\tif (em_remove_watch(event->mask)) {\n+\t\t\t\tremove_watch(w, state->listen_data);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tstrbuf_reset(&path);\n+\t\t\tstrbuf_add(&path, w->dir, strlen(w->dir));\n+\t\t\tstrbuf_addch(&path, '/');\n+\t\t\tstrbuf_add(&path, event->name,  strlen(event->name));\n+\n+\t\t\tp = fsmonitor__resolve_alias(path.buf, &state->alias);\n+\t\t\tif (!p)\n+\t\t\t\tp = strbuf_detach(&path, NULL);\n+\n+\t\t\tif (!batch)\n+\t\t\t\tbatch = fsmonitor_batch__new();\n+\n+\t\t\tif (process_event(p, event, batch, &cookie_list, state)) {\n+\t\t\t\tfree(p);\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tfree(p);\n+\t\t}\n+\t\tstrbuf_reset(&path);\n+\t\tfsmonitor_publish(state, batch, &cookie_list);\n+\t\tstring_list_clear(&cookie_list, 0);\n+\t\tbatch = NULL;\n+\t}\n+done:\n+\tstrbuf_release(&path);\n+\tfsmonitor_batch__free_list(batch);\n+\tstring_list_clear(&cookie_list, 0);\n+}\n+\n+/*\n+ * Non-blocking read of the inotify events stream. The inotify fd is polled\n+ * frequently to help minimize the number of queue overflows.\n+ */\n+void fsm_listen__loop(struct fsmonitor_daemon_state *state)\n+{\n+\tint poll_num;\n+\tconst int interval = 1000;\n+\ttime_t checked = time(NULL);\n+\tstruct pollfd fds[1];\n+\tfds[0].fd = state->listen_data->fd_inotify;\n+\tfds[0].events = POLLIN;\n+\n+\tfor(;;) {\n+\t\tswitch (state->listen_data->shutdown) {\n+\t\t\tcase SHUTDOWN_CONTINUE:\n+\t\t\t\tpoll_num = poll(fds, 1, 1);\n+\t\t\t\tif (poll_num == -1) {\n+\t\t\t\t\tif (errno == EINTR)\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\terror_errno(_(\"polling inotify message stream failed\"));\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif ((time(NULL) - checked) >= interval) {\n+\t\t\t\t\tchecked = time(NULL);\n+\t\t\t\t\tif (check_stale_dir_renames(&state->listen_data->renames,\n+\t\t\t\t\t\tchecked - interval)) {\n+\t\t\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\t\"Missed IN_MOVED_TO events, forcing shutdown\");\n+\t\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\n+\t\t\t\tif (poll_num > 0 && (fds[0].revents & POLLIN))\n+\t\t\t\t\thandle_events(state);\n+\n+\t\t\t\tcontinue;\n+\t\t\tcase SHUTDOWN_ERROR:\n+\t\t\t\tstate->listen_error_code = -1;\n+\t\t\t\tipc_server_stop_async(state->ipc_server_data);\n+\t\t\t\tbreak;\n+\t\t\tcase SHUTDOWN_FORCE:\n+\t\t\t\tstate->listen_error_code = 0;\n+\t\t\t\tipc_server_stop_async(state->ipc_server_data);\n+\t\t\t\tbreak;\n+\t\t\tcase SHUTDOWN_STOP:\n+\t\t\tdefault:\n+\t\t\t\tstate->listen_error_code = 0;\n+\t\t\t\tbreak;\n+\t\t}\n+\t\treturn;\n+\t}\n+}\n-- \ngitgitgadget\n\n"},{"id":"465004","messageId":"7d7ef78728f890db68166b3b5b6b7dd33f20e62c.1665783945.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 08/12] fsmonitor: determine if filesystem is local or remote","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:40Z","receivedAt":"2022-10-14T21:46:47Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nCompare the given path to the mounted filesystems. Find the mount that is\nthe longest prefix of the path (if any) and determine if that mount is on a\nlocal or remote filesystem.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-path-utils-linux.c | 169 ++++++++++++++++++++++++\n 1 file changed, 169 insertions(+)\n create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n\ndiff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c\nnew file mode 100644\nindex 00000000000..039f044b670\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-path-utils-linux.c\n@@ -0,0 +1,169 @@\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-path-utils.h\"\n+#include <errno.h>\n+#include <mntent.h>\n+#include <sys/mount.h>\n+#include <sys/statvfs.h>\n+\n+/*\n+ * https://github.com/coreutils/gnulib/blob/master/lib/mountlist.c\n+ */\n+#ifndef ME_REMOTE\n+/* A file system is \"remote\" if its Fs_name contains a ':'\n+   or if (it is of type (smbfs or cifs) and its Fs_name starts with '//')\n+   or if it is of any other of the listed types\n+   or Fs_name is equal to \"-hosts\" (used by autofs to mount remote fs).\n+   \"VM\" file systems like prl_fs or vboxsf are not considered remote here. */\n+# define ME_REMOTE(Fs_name, Fs_type)            \\\n+\t(strchr (Fs_name, ':') != NULL              \\\n+\t || ((Fs_name)[0] == '/'                    \\\n+\t\t && (Fs_name)[1] == '/'                 \\\n+\t\t && (strcmp (Fs_type, \"smbfs\") == 0     \\\n+\t\t\t || strcmp (Fs_type, \"smb3\") == 0   \\\n+\t\t\t || strcmp (Fs_type, \"cifs\") == 0)) \\\n+\t || strcmp (Fs_type, \"acfs\") == 0           \\\n+\t || strcmp (Fs_type, \"afs\") == 0            \\\n+\t || strcmp (Fs_type, \"coda\") == 0           \\\n+\t || strcmp (Fs_type, \"auristorfs\") == 0     \\\n+\t || strcmp (Fs_type, \"fhgfs\") == 0          \\\n+\t || strcmp (Fs_type, \"gpfs\") == 0           \\\n+\t || strcmp (Fs_type, \"ibrix\") == 0          \\\n+\t || strcmp (Fs_type, \"ocfs2\") == 0          \\\n+\t || strcmp (Fs_type, \"vxfs\") == 0           \\\n+\t || strcmp (\"-hosts\", Fs_name) == 0)\n+#endif\n+\n+static int find_mount(const char *path, const struct statvfs *fs,\n+\tstruct mntent *ent)\n+{\n+\tconst char *const mounts = \"/proc/mounts\";\n+\tconst char *rp = real_pathdup(path, 1);\n+\tstruct mntent *ment = NULL;\n+\tstruct statvfs mntfs;\n+\tFILE *fp;\n+\tint found = 0;\n+\tint dlen, plen, flen = 0;\n+\n+\tent->mnt_fsname = NULL;\n+\tent->mnt_dir = NULL;\n+\tent->mnt_type = NULL;\n+\n+\tfp = setmntent(mounts, \"r\");\n+\tif (!fp) {\n+\t\terror_errno(_(\"setmntent('%s') failed\"), mounts);\n+\t\treturn -1;\n+\t}\n+\n+\tplen = strlen(rp);\n+\n+\t/* read all the mount information and compare to path */\n+\twhile ((ment = getmntent(fp)) != NULL) {\n+\t\tif (statvfs(ment->mnt_dir, &mntfs)) {\n+\t\t\tswitch (errno) {\n+\t\t\tcase EPERM:\n+\t\t\tcase ESRCH:\n+\t\t\tcase EACCES:\n+\t\t\t\tcontinue;\n+\t\t\tdefault:\n+\t\t\t\terror_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n+\t\t\t\tendmntent(fp);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* is mount on the same filesystem and is a prefix of the path */\n+\t\tif ((fs->f_fsid == mntfs.f_fsid) &&\n+\t\t\t!strncmp(ment->mnt_dir, rp, strlen(ment->mnt_dir))) {\n+\t\t\tdlen = strlen(ment->mnt_dir);\n+\t\t\tif (dlen > plen)\n+\t\t\t\tcontinue;\n+\t\t\t/*\n+\t\t\t * root is always a potential match; otherwise look for\n+\t\t\t * directory prefix\n+\t\t\t */\n+\t\t\tif ((dlen == 1 && ment->mnt_dir[0] == '/') ||\n+\t\t\t\t(dlen > flen && (!rp[dlen] || rp[dlen] == '/'))) {\n+\t\t\t\tflen = dlen;\n+\t\t\t\t/*\n+\t\t\t\t * https://man7.org/linux/man-pages/man3/getmntent.3.html\n+\t\t\t\t *\n+\t\t\t\t * The pointer points to a static area of memory which is\n+\t\t\t\t * overwritten by subsequent calls to getmntent().\n+\t\t\t\t */\n+\t\t\t\tfound = 1;\n+\t\t\t\tfree(ent->mnt_fsname);\n+\t\t\t\tfree(ent->mnt_dir);\n+\t\t\t\tfree(ent->mnt_type);\n+\t\t\t\tent->mnt_fsname = xstrdup(ment->mnt_fsname);\n+\t\t\t\tent->mnt_dir = xstrdup(ment->mnt_dir);\n+\t\t\t\tent->mnt_type = xstrdup(ment->mnt_type);\n+\t\t\t}\n+\t\t}\n+\t}\n+\tendmntent(fp);\n+\n+\tif (!found)\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n+{\n+\tstruct mntent ment;\n+\tstruct statvfs fs;\n+\n+\tif (statvfs(path, &fs))\n+\t\treturn error_errno(_(\"statvfs('%s') failed\"), path);\n+\n+\n+\tif (find_mount(path, &fs, &ment) < 0) {\n+\t\tfree(ment.mnt_fsname);\n+\t\tfree(ment.mnt_dir);\n+\t\tfree(ment.mnt_type);\n+\t\treturn -1;\n+\t}\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n+\t\t\t path, fs.f_flag, ment.mnt_type, ment.mnt_fsname);\n+\n+\tfs_info->is_remote = ME_REMOTE(ment.mnt_fsname, ment.mnt_type);\n+\tfs_info->typename = ment.mnt_fsname;\n+\tfree(ment.mnt_dir);\n+\tfree(ment.mnt_type);\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"'%s' is_remote: %d\",\n+\t\t\t\tpath, fs_info->is_remote);\n+\treturn 0;\n+}\n+\n+int fsmonitor__is_fs_remote(const char *path)\n+{\n+\tstruct fs_info fs;\n+\n+\tif (fsmonitor__get_fs_info(path, &fs))\n+\t\treturn -1;\n+\n+\tfree(fs.typename);\n+\n+\treturn fs.is_remote;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info)\n+{\n+\treturn 0;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info)\n+{\n+\treturn NULL;\n+}\n-- \ngitgitgadget\n\n"},{"id":"465005","messageId":"dd73e126810615dac744e7097df66a921336018f.1665783945.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v2 12/12] fsmonitor: update doc for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-14T21:45:44Z","receivedAt":"2022-10-14T21:46:49Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nUpdate the documentation for Linux.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Documentation/config/fsmonitor--daemon.txt |  4 ++--\n Documentation/git-fsmonitor--daemon.txt    | 24 ++++++++++++++--------\n 2 files changed, 18 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config/fsmonitor--daemon.txt b/Documentation/config/fsmonitor--daemon.txt\nindex c225c6c9e74..2cafb040d96 100644\n--- a/Documentation/config/fsmonitor--daemon.txt\n+++ b/Documentation/config/fsmonitor--daemon.txt\n@@ -4,8 +4,8 @@ fsmonitor.allowRemote::\n     behavior.  Only respected when `core.fsmonitor` is set to `true`.\n \n fsmonitor.socketDir::\n-    This Mac OS-specific option, if set, specifies the directory in\n+    Mac OS and Linux-specific option. If set, specifies the directory in\n     which to create the Unix domain socket used for communication\n     between the fsmonitor daemon and various Git commands. The directory must\n-    reside on a native Mac OS filesystem.  Only respected when `core.fsmonitor`\n+    reside on a native filesystem.  Only respected when `core.fsmonitor`\n     is set to `true`.\ndiff --git a/Documentation/git-fsmonitor--daemon.txt b/Documentation/git-fsmonitor--daemon.txt\nindex 8238eadb0e1..c2b08229c74 100644\n--- a/Documentation/git-fsmonitor--daemon.txt\n+++ b/Documentation/git-fsmonitor--daemon.txt\n@@ -76,23 +76,31 @@ repositories; this may be overridden by setting `fsmonitor.allowRemote` to\n correctly with all network-mounted repositories and such use is considered\n experimental.\n \n-On Mac OS, the inter-process communication (IPC) between various Git\n+On Linux and Mac OS, the inter-process communication (IPC) between various Git\n commands and the fsmonitor daemon is done via a Unix domain socket (UDS) -- a\n-special type of file -- which is supported by native Mac OS filesystems,\n-but not on network-mounted filesystems, NTFS, or FAT32.  Other filesystems\n-may or may not have the needed support; the fsmonitor daemon is not guaranteed\n-to work with these filesystems and such use is considered experimental.\n+special type of file -- which is supported by many native Linux and Mac OS\n+filesystems, but not on network-mounted filesystems, NTFS, or FAT32.  Other\n+filesystems may or may not have the needed support; the fsmonitor daemon is not\n+guaranteed to work with these filesystems and such use is considered\n+experimental.\n \n By default, the socket is created in the `.git` directory, however, if the\n `.git` directory is on a network-mounted filesystem, it will be instead be\n created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a\n network-mounted filesystem in which case you must set the configuration\n-variable `fsmonitor.socketDir` to the path of a directory on a Mac OS native\n+variable `fsmonitor.socketDir` to the path of a directory on a native\n filesystem in which to create the socket file.\n \n If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`)\n-is on a native Mac OS file filesystem the fsmonitor daemon will report an\n-error that will cause the daemon and the currently running command to exit.\n+is on a native Linux or Mac OS filesystem the fsmonitor daemon will report\n+an error that will cause the daemon to exit and the currently running command\n+to issue a warning.\n+\n+On Linux, the fsmonitor daemon registers a watch for each directory in the\n+repository.  The default per-user limit for the number of watches on most Linux\n+systems is 8192.  This may not be sufficient for large repositories or if\n+multiple instances of the fsmonitor daemon are running.\n+See https://watchexec.github.io/docs/inotify-limits.html[Linux inotify limits] for more information.\n \n CONFIGURATION\n -------------\n-- \ngitgitgadget\n"},{"id":"465010","messageId":"xmqqwn9256cw.fsf@gitster.g","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-14T23:32:31Z","receivedAt":"2022-10-14T23:32:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\n> Windows and Mac OS.\n>\n> This patch set builds upon previous work for done for Windows and Mac OS\n> (first 6 patches) to implement a fsmonitor back-end for Linux based on the\n> Linux inotify API.\n\nAgain, the first six patches are a part of what is queued as\ned/fsmonitor-on-networked-macos that is now in 'next' but lacks a\nfix-up commit from Jeff King.\n\nI understand that it might not be easy/possible (e.g. perhaps it is\na limitation of GGG?), but I really prefer not to see them re-posted\nas part of this series, as I have to apply them and make sure there\nare no changes from the last one before discarding them.\n\nAnyway, thanks for an update.  Will requeue.\n"},{"id":"465011","messageId":"xmqqmt9y55py.fsf@gitster.g","threadId":"58588","inReplyTo":"295beb89ab10f001724fb64fa55944d05ee29fc7.1665783945.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 07/12] fsmonitor: prepare to share code between Mac OS and Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-14T23:46:17Z","receivedAt":"2022-10-14T23:46:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  ifdef FSMONITOR_DAEMON_BACKEND\n>  \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n> +\tifdef FSMONITOR_DAEMON_COMMON\n> +\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_COMMON).o\n> +\telse\n> +\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n> +\tendif\n>  \tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o\n>  \tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o\n> -\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n>  endif\n>  \n>  ifdef FSMONITOR_OS_SETTINGS\n>  \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n> +\tifdef FSMONITOR_DAEMON_COMMON\n> +\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_COMMON).o\n> +\tendif\n>  \tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o\n>  \tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o\n>  endif\n\nUgly.  \n\nOne overrides backend with common, while the other one doesn't.\nThat asymmetry alone should stop us and wonder if there is something\nfishy in the approach that can be improved.  It makes it look like\nthe word \"common\" means something quite different between the -ipc\nand the -settings world.\n\nI suspect that in both, you should not expose \"unix\" to this part of\nthe Makefile.  Linux and macOS occasionally being similar in some\nplaces does not have to be exposed here.  INstead you can use\nbackend \"linux\" and \"macos\", whose C sources may include from a\nseparate C source file whose name may contain \"unix\".  That would\nallow you to get rid of FSMONITOR_DAEMON_COMMON in a cleaner way.\n"},{"id":"465143","messageId":"BL0PR05MB557147E13EFF24F664A14832D9299@BL0PR05MB5571.namprd05.prod.outlook.com","threadId":"58588","inReplyTo":"xmqqmt9y55py.fsf@gitster.g","subject":"RE: [PATCH v2 07/12] fsmonitor: prepare to share code between Mac OS and Linux","fromName":"Eric DeCosta","fromEmail":"edecosta@mathworks.com","sentAt":"2022-10-17T21:30:26Z","receivedAt":"2022-10-17T21:30:37Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"\n\n> -----Original Message-----\n> From: Junio C Hamano <gitster@pobox.com>\n> Sent: Friday, October 14, 2022 7:46 PM\n> To: Eric DeCosta via GitGitGadget <gitgitgadget@gmail.com>\n> Cc: git@vger.kernel.org; Eric Sunshine <sunshine@sunshineco.com>; Ævar\n> Arnfjörð Bjarmason <avarab@gmail.com>; Eric DeCosta\n> <edecosta@mathworks.com>\n> Subject: Re: [PATCH v2 07/12] fsmonitor: prepare to share code between\n> Mac OS and Linux\n> \n> \"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n> >  ifdef FSMONITOR_DAEMON_BACKEND\n> >  \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n> > +\tifdef FSMONITOR_DAEMON_COMMON\n> > +\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-\n> $(FSMONITOR_DAEMON_COMMON).o\n> > +\telse\n> > +\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-\n> $(FSMONITOR_DAEMON_BACKEND).o\n> > +\tendif\n> >  \tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-\n> $(FSMONITOR_DAEMON_BACKEND).o\n> >  \tCOMPAT_OBJS += compat/fsmonitor/fsm-health-\n> $(FSMONITOR_DAEMON_BACKEND).o\n> > -\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-\n> $(FSMONITOR_DAEMON_BACKEND).o\n> >  endif\n> >\n> >  ifdef FSMONITOR_OS_SETTINGS\n> >  \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n> > +\tifdef FSMONITOR_DAEMON_COMMON\n> > +\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-\n> $(FSMONITOR_DAEMON_COMMON).o\n> > +\tendif\n> >  \tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-\n> $(FSMONITOR_OS_SETTINGS).o\n> >  \tCOMPAT_OBJS +=\n> > compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o\n> >  endif\n> \n> Ugly.\n> \n> One overrides backend with common, while the other one doesn't.\n> That asymmetry alone should stop us and wonder if there is something fishy\n> in the approach that can be improved.  It makes it look like the word\n> \"common\" means something quite different between the -ipc and the -\n> settings world.\n> \n> I suspect that in both, you should not expose \"unix\" to this part of the\n> Makefile.  Linux and macOS occasionally being similar in some places does\n> not have to be exposed here.  INstead you can use backend \"linux\" and\n> \"macos\", whose C sources may include from a separate C source file whose\n> name may contain \"unix\".  That would allow you to get rid of\n> FSMONITOR_DAEMON_COMMON in a cleaner way.\n\nLet me see if I am understanding you correctly. Are you suggesting something like:\n\nifdef FSMONITOR_DAEMON_BACKEND_LINUX\n\tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-unix.o\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND_LINUX).o\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND_LINUX).o\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND_LINUX).o\nendif\n\nifdef FSMONITOR_DAEMON_BACKEND_DARWIN\n\tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-unix.o\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND_DARWIN).o\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND_DARWIN).o\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND_DARWIN).o\nendif\n\nAnd similarly for settings:\n\nifdef FSMONITOR_DAEMON_SETTINGS_LINUX\n\tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-unix.o\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_SETTINGS_LINUX).o\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_DAEMON_SETTINGS_LINUX).o\nendif\n\nifdef FSMONITOR_DAEMON_SETTINGS_DARWIN\n\tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-unix.o\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_SETTINGS_DARWIN).o\n\tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_DAEMON_SETTINGS_DARWIN).o\nendif\n\nAnd the rest of the platforms remain as was, with FSMONITOR_DAEMON_BACKEND and FSMONITOR_OS_SETTINGS\n\n-Eric\n\n"},{"id":"465144","messageId":"BL0PR05MB557123F87157E0E091A43494D9299@BL0PR05MB5571.namprd05.prod.outlook.com","threadId":"58588","inReplyTo":"xmqqwn9256cw.fsf@gitster.g","subject":"RE: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric DeCosta","fromEmail":"edecosta@mathworks.com","sentAt":"2022-10-17T21:32:08Z","receivedAt":"2022-10-17T21:32:16Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"\n\n> -----Original Message-----\n> From: Junio C Hamano <gitster@pobox.com>\n> Sent: Friday, October 14, 2022 7:33 PM\n> To: Eric DeCosta via GitGitGadget <gitgitgadget@gmail.com>\n> Cc: git@vger.kernel.org; Eric Sunshine <sunshine@sunshineco.com>; Ævar\n> Arnfjörð Bjarmason <avarab@gmail.com>; Eric DeCosta\n> <edecosta@mathworks.com>\n> Subject: Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux\n> \n> \"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n> > Goal is to deliver fsmonitor for Linux that is on par with fsmonitor\n> > for Windows and Mac OS.\n> >\n> > This patch set builds upon previous work for done for Windows and Mac\n> > OS (first 6 patches) to implement a fsmonitor back-end for Linux based\n> > on the Linux inotify API.\n> \n> Again, the first six patches are a part of what is queued as ed/fsmonitor-on-\n> networked-macos that is now in 'next' but lacks a fix-up commit from Jeff\n> King.\n> \n> I understand that it might not be easy/possible (e.g. perhaps it is a limitation\n> of GGG?), but I really prefer not to see them re-posted as part of this series,\n> as I have to apply them and make sure there are no changes from the last\n> one before discarding them.\n> \n> Anyway, thanks for an update.  Will requeue.\n\nI looks like I can use GGG with the next branch, but I will have to open a new PR (and close the existing one).\n\n-Eric\n\n"},{"id":"465146","messageId":"kl6l7d0yyu6r.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-10-17T22:14:04Z","receivedAt":"2022-10-17T22:14:21Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"At $DAYJOB, we observed that this topic breaks MacOS builds with sha1dc:\n\n  $ make NO_APPLE_COMMON_CRYPTO=1 DC_SHA1=1 NO_OPENSSL=1 compat/fsmonitor/fsm-ipc-darwin.o  \n\n      CC compat/fsmonitor/fsm-ipc-darwin.o\n    compat/fsmonitor/fsm-ipc-darwin.c:13:2: error: unknown type name 'SHA_CTX'; did you mean 'SHA1_CTX'?\n            SHA_CTX sha1ctx;\n            ^~~~~~~\n            SHA1_CTX\n    ./sha1dc/sha1.h:55:3: note: 'SHA1_CTX' declared here\n    } SHA1_CTX;\n      ^\n    compat/fsmonitor/fsm-ipc-darwin.c:16:21: error: use of undeclared identifier 'SHA_DIGEST_LENGTH'\n            unsigned char hash[SHA_DIGEST_LENGTH];\n                              ^\n    compat/fsmonitor/fsm-ipc-darwin.c:31:2: error: implicit declaration of function 'SHA1_Init' is invalid in C99 [-Werror,-Wimplicit-function-declaration]\n            SHA1_Init(&sha1ctx);\n            ^\n    compat/fsmonitor/fsm-ipc-darwin.c:31:2: note: did you mean 'SHA1DCInit'?\n    ./sha1dc/sha1.h:58:6: note: 'SHA1DCInit' declared here\n    void SHA1DCInit(SHA1_CTX*);\n        ^\n    compat/fsmonitor/fsm-ipc-darwin.c:32:2: error: implicit declaration of function 'SHA1_Update' is invalid in C99 [-Werror,-Wimplicit-function-declaration]\n            SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n            ^\n    compat/fsmonitor/fsm-ipc-darwin.c:32:2: note: did you mean 'SHA1DCUpdate'?\n    ./sha1dc/sha1.h:96:6: note: 'SHA1DCUpdate' declared here\n    void SHA1DCUpdate(SHA1_CTX*, const char*, size_t);\n        ^\n    compat/fsmonitor/fsm-ipc-darwin.c:33:2: error: implicit declaration of function 'SHA1_Final' is invalid in C99 [-Werror,-Wimplicit-function-declaration]\n            SHA1_Final(hash, &sha1ctx);\n            ^\n    compat/fsmonitor/fsm-ipc-darwin.c:33:2: note: did you mean 'SHA1DCFinal'?\n    ./sha1dc/sha1.h:100:6: note: 'SHA1DCFinal' declared here\n    int  SHA1DCFinal(unsigned char[20], SHA1_CTX*);\n        ^\n    5 errors generated.\n    make: *** [compat/fsmonitor/fsm-ipc-darwin.o] Error 1\n\n\nWithout NO_OPENSSL, this still fails, but with slightly different error\nmessages.\n\n  $ make NO_APPLE_COMMON_CRYPTO=1 DC_SHA1=1 compat/fsmonitor/fsm-ipc-darwin.o\n\n        CC compat/fsmonitor/fsm-ipc-darwin.o\n    compat/fsmonitor/fsm-ipc-darwin.c:31:2: error: 'SHA1_Init' is deprecated [-Werror,-Wdeprecated-declarations]\n            SHA1_Init(&sha1ctx);\n            ^\n    /opt/local/include/openssl/sha.h:49:1: note: 'SHA1_Init' has been explicitly marked deprecated here\n    OSSL_DEPRECATEDIN_3_0 int SHA1_Init(SHA_CTX *c);\n    ^\n    /opt/local/include/openssl/macros.h:182:49: note: expanded from macro 'OSSL_DEPRECATEDIN_3_0'\n    #   define OSSL_DEPRECATEDIN_3_0                OSSL_DEPRECATED(3.0)\n                                                    ^\n    /opt/local/include/openssl/macros.h:62:52: note: expanded from macro 'OSSL_DEPRECATED'\n    #     define OSSL_DEPRECATED(since) __attribute__((deprecated))\n                                                      ^\n    compat/fsmonitor/fsm-ipc-darwin.c:32:2: error: 'SHA1_Update' is deprecated [-Werror,-Wdeprecated-declarations]\n            SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n            ^\n    /opt/local/include/openssl/sha.h:50:1: note: 'SHA1_Update' has been explicitly marked deprecated here\n    OSSL_DEPRECATEDIN_3_0 int SHA1_Update(SHA_CTX *c, const void *data, size_t len);\n    ^\n    /opt/local/include/openssl/macros.h:182:49: note: expanded from macro 'OSSL_DEPRECATEDIN_3_0'\n    #   define OSSL_DEPRECATEDIN_3_0                OSSL_DEPRECATED(3.0)\n                                                    ^\n    /opt/local/include/openssl/macros.h:62:52: note: expanded from macro 'OSSL_DEPRECATED'\n    #     define OSSL_DEPRECATED(since) __attribute__((deprecated))\n                                                      ^\n    compat/fsmonitor/fsm-ipc-darwin.c:33:2: error: 'SHA1_Final' is deprecated [-Werror,-Wdeprecated-declarations]\n            SHA1_Final(hash, &sha1ctx);\n            ^\n    /opt/local/include/openssl/sha.h:51:1: note: 'SHA1_Final' has been explicitly marked deprecated here\n    OSSL_DEPRECATEDIN_3_0 int SHA1_Final(unsigned char *md, SHA_CTX *c);\n    ^\n    /opt/local/include/openssl/macros.h:182:49: note: expanded from macro 'OSSL_DEPRECATEDIN_3_0'\n    #   define OSSL_DEPRECATEDIN_3_0                OSSL_DEPRECATED(3.0)\n                                                    ^\n    /opt/local/include/openssl/macros.h:62:52: note: expanded from macro 'OSSL_DEPRECATED'\n    #     define OSSL_DEPRECATED(since) __attribute__((deprecated))\n                                                      ^\n    3 errors generated.\n    make: *** [compat/fsmonitor/fsm-ipc-darwin.o] Error 1\n\n\n\"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\n> Windows and Mac OS.\n>\n> This patch set builds upon previous work for done for Windows and Mac OS\n> (first 6 patches) to implement a fsmonitor back-end for Linux based on the\n> Linux inotify API. inotify differs significantly from the equivalent Windows\n> and Mac OS APIs in that a watch must be registered for every directory of\n> interest (rather than a singular watch at the root of the directory tree)\n> and special care must be taken to handle directory renames correctly.\n>\n> More information about inotify:\n> https://man7.org/linux/man-pages/man7/inotify.7.html\n>\n> v1 differs from v0:\n>\n>  * Code review feedback\n>  * Update how and which code can be shared between Mac OS and Linux\n>  * Increase polling frequency to every 1ms (matches Mac OS)\n>  * Updates to t7527 to improve test stability\n>\n"},{"id":"465147","messageId":"xmqqk04yxf93.fsf@gitster.g","threadId":"58588","inReplyTo":"BL0PR05MB557123F87157E0E091A43494D9299@BL0PR05MB5571.namprd05.prod.outlook.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-17T22:22:00Z","receivedAt":"2022-10-17T22:22:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric DeCosta <edecosta@mathworks.com> writes:\n\n>> I understand that it might not be easy/possible (e.g. perhaps it is a limitation\n>> of GGG?), but I really prefer not to see them re-posted as part of this series,\n>> as I have to apply them and make sure there are no changes from the last\n>> one before discarding them.\n>> \n>> Anyway, thanks for an update.  Will requeue.\n>\n> I looks like I can use GGG with the next branch, but I will have\n> to open a new PR (and close the existing one).\n\nPlease don't.\n\nThe macOS topic (thanks for working on it, by the way) is about to\ngraduate to 'master', and the situation would probably \"improve\"\nanyway when it happens, I hope.\n\nThanks.\n"},{"id":"465172","messageId":"xmqqo7u9wyt7.fsf@gitster.g","threadId":"58588","inReplyTo":"kl6l7d0yyu6r.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-18T04:17:08Z","receivedAt":"2022-10-18T04:17:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> At $DAYJOB, we observed that this topic breaks MacOS builds with sha1dc:\n\nThanks for a report.\n\nHow dissapointing.  The thing is that the topic has been in 'next'\nsince the 11th (i.e. early last week), and I know that you guys rely\non the tip of 'next' in working order to cut your internal releases,\nbut we did not hear about this until now.  What makes it taste even\nworse is that nobody else caught this, even though we seem to have a\ncouple of macOS jobs at GitHub Actions CI, there we didn't see any\nbreakage related to this.\n\nPossible action items:\n\n * See what configurations our two macOS jobs are using.  If neither\n   is using sha1dc, I would say that is criminal [*] and at least\n   one of them should be updated to do so right away.\n\n * The \"two macOS CI jobs sharing too many configuration knobs\" may\n   not be limited to just SHA-1 implementation, but unlike Linux\n   builds and tests, we may have similar \"monoculture\" issue in our\n   macOS CI builds.  Those users, who depend on macOS port being\n   healthy, should help identify unnecessary overlaps between the\n   two, and more importantly, make sure we have CI builds with\n   configuration similar to what they actually use.\n\n * Adding a few build-only-without-tests CI jobs also might help.\n\n * Those who depend on working macOS port, especially those with\n   corporate backing who choose to use configurations that are\n   different from what we have CI builds for, are requested to\n   arrange a more frequent build test to catch a problem like this\n   much earlier.\n\nAnything else I forgot that we can do to improve the situation?  I\npersonally do not use macOS, I am not interested in using one, but\nI do value those who choose to use macOS have happy git working on\ntheir platform, so the stakeholders need to chip in.\n\nThanks.\n\n\n[Footnote]\n\n * Until the world migrates over to SHA-256, the collision detecting\n   SHA-1 implementation is what we must use unless there is a strong\n   reason not to.  If we are not testing something that ought to be\n   the default, we are not doing a very good job.\n\n"},{"id":"465174","messageId":"xmqqa65twsln.fsf@gitster.g","threadId":"58588","inReplyTo":"BL0PR05MB557147E13EFF24F664A14832D9299@BL0PR05MB5571.namprd05.prod.outlook.com","subject":"Re: [PATCH v2 07/12] fsmonitor: prepare to share code between Mac OS and Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-18T06:31:16Z","receivedAt":"2022-10-18T06:31:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric DeCosta <edecosta@mathworks.com> writes:\n\n>> Ugly.\n>> \n>> One overrides backend with common, while the other one doesn't.\n>> That asymmetry alone should stop us and wonder if there is something fishy\n>> in the approach that can be improved.  It makes it look like the word\n>> \"common\" means something quite different between the -ipc and the -\n>> settings world.\n>> \n>> I suspect that in both, you should not expose \"unix\" to this part of the\n>> Makefile.  Linux and macOS occasionally being similar in some places does\n>> not have to be exposed here.  INstead you can use backend \"linux\" and\n>> \"macos\", whose C sources may include from a separate C source file whose\n>> name may contain \"unix\".  That would allow you to get rid of\n>> FSMONITOR_DAEMON_COMMON in a cleaner way.\n>\n> Let me see if I am understanding you correctly. Are you suggesting something like:\n\nNot really.\n\nWhat I had in mind was an arrangement more like\n\n    $ for variant in linux macos; do grep include fsm-ipc-$variant.c; done\n    #include \"fsm-ipc-common-unix.cinclude\"\n    #include \"fsm-ipc-common-unix.cinclude\"\n\nand COMPAT_OBJS knowning only about fsm-ipc-linux.o or\nfsm-ipc-macos.o, depending on which platform you are building.\n\n"},{"id":"465186","messageId":"221018.86zgdtibif.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"4f9c5358475867af75acd865505884f99801ca17.1665783945.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 09/12] fsmonitor: implement filesystem change listener for Linux","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-10-18T11:59:39Z","receivedAt":"2022-10-18T12:05:50Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Oct 14 2022, Eric DeCosta via GitGitGadget wrote:\n\n> From: Eric DeCosta <edecosta@mathworks.com>\n> [...]\n> +\t\t\tstrbuf_add(&path, w->dir, strlen(w->dir));\n> +\t\t\tstrbuf_addch(&path, '/');\n> +\t\t\tstrbuf_add(&path, event->name,  strlen(event->name));\n\nDon't do strbuf_add(&buf, x, strlen(x), just use strbuf_addstr(&buf, x)\ninstead.\n\nThe same goes for a couple of existing occurances that hit \"master\"\nalready in the just-merged fsmonitor topic, but in this case we can\nchange it in-flight still. Thanks!\n"},{"id":"465187","messageId":"221018.86v8ohiaib.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"21d114bda4b515170ae43f01f9bb14befec611f2.1665783944.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 02/12] fsmonitor: relocate socket file if .git directory is remote","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-10-18T12:12:57Z","receivedAt":"2022-10-18T12:25:55Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Oct 14 2022, Eric DeCosta via GitGitGadget wrote:\n\n> From: Eric DeCosta <edecosta@mathworks.com>\n> [..]\n> +const char *fsmonitor_ipc__get_path(struct repository *r)\n> +{\n> +\tstatic const char *ipc_path = NULL;\n\nUrm, I see some of this interface made it into master already with\n6beb2688d33 (fsmonitor: relocate socket file if .git directory is\nremote, 2022-10-04), but this is just weird. We already have \"struct\nfsmonitor_settings\" which \"lazily loads\" these various settings.\n\nSo e.g. in refresh_fsmonitor() we might get the hook path, which we\n\"lazy load\" and cache there.\n\nAnd here we have an extra layer of another type of lazy loading, and\nthis will e.g. be called from fsmonitor-settings.c.\n\nIs there really a good reason for why the relevant codepaths can't\neither not-lazy-load this (e.g. when we get the socket), or why we can't\njust use fsmonitor-settings.c's existing caching?\n\nJust wondering...\n\n> +\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n> +\n> +\t/* Create the socket file in either socketDir or $HOME */\n> +\tif (sock_dir && *sock_dir) {\n> +\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n> +\t\t\t\t\tsock_dir, hash_to_hex(hash));\n> +\t} else {\n> +\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n> +\t}\n> +\tfree(sock_dir);\n\nInstead of this init sock_dir to NULL & then check if it's non-NULL\netc. Just do:\n\n\tchar *sock_dir;\n\n\tif (!repo_config_get_string_tmp(r, \"fsmonitor.socketdir\", &sock_dir) &&\n\t    *sock_dir)\n\t\t...\n\telse\n\t\t...\n\nNote also the *_tmp(), then you won't have to free() it.\n\n\n> +\n> +\tipc_path = interpolate_path(ipc_file.buf, 1);\n> +\tif (!ipc_path)\n> +\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n> +\n> +\tstrbuf_release(&ipc_file);\n> +\treturn ipc_path;\n\nWhy can't we just do something more simpler like this in the *nix case?\n\nI think because you're creating this socket for a NFS-mounted repo, so\nof course we might have multiple clients.\n\nBut you also seem to be making it \"unique\" by hashing r->worktree, which\nis the path to the repo work tree. At least in my use-cases for NFS\nmounted repos (which have been limited) all of those paths would be the\nsame for me across my systems, because I'd have them at\n/home/avar/g/some-git-tree/...\n\nIn builtin/gc.c we create a \"unique for system\" lock file using\nxgethostname(), isn't that a lot simpler & closer to what you actually\nwant?\n\nIn any case, some details about motivations, why etc. are sorely missing\nin the commit message. We can see *what* you're doing, but not *why*, or\nwhat other simpler solutions etc. might have been tried & dismissed...\n"},{"id":"465188","messageId":"221018.86r0z5i9tn.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"ec49a74086dee4545a3bb7b24c8da22e77aef579.1665326258.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/12] fsmonitor: refactor filesystem checks to common interface","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-10-18T12:29:55Z","receivedAt":"2022-10-18T12:40:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Oct 09 2022, Eric DeCosta via GitGitGadget wrote:\n\n> From: Eric DeCosta <edecosta@mathworks.com>\n> [...]\n> +int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n> +{\n> +\tstruct statfs fs;\n> +\tif (statfs(path, &fs) == -1) {\n> +\t\tint saved_errno = errno;\n> +\t\ttrace_printf_key(&trace_fsmonitor, \"statfs('%s') failed: %s\",\n> +\t\t\t\t path, strerror(saved_errno));\n> +\t\terrno = saved_errno;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\ttrace_printf_key(&trace_fsmonitor,\n> +\t\t\t \"statfs('%s') [type 0x%08x][flags 0x%08x] '%s'\",\n> +\t\t\t path, fs.f_type, fs.f_flags, fs.f_fstypename);\n> +\n> +\tif (!(fs.f_flags & MNT_LOCAL))\n> +\t\tfs_info->is_remote = 1;\n> +\telse\n> +\t\tfs_info->is_remote = 0;\n\nOdd to go through the trouble of inverting the flag check like this just\nto assign to it manually. I'd think:\n\n\tif (flags & LOCAL)\n\t\tis_remote = 0;\n\telse\n\t\tis_remote = 1;\n\nWould make more sense, or actually if you do invert it:\n\n\tfs_info->is_remote = !(fs.f_flags & MNT_LOCAL);\n\nNo?\n\n> +\tfs_info->typename = xstrdup(fs.f_fstypename);\n\nInstead of xstrdup()-ing this...\n> +\n> +\ttrace_printf_key(&trace_fsmonitor,\n> +\t\t\t\t\"'%s' is_remote: %d\",\n> +\t\t\t\tpath, fs_info->is_remote);\n> +\treturn 0;\n> +}\n> +\n> +int fsmonitor__is_fs_remote(const char *path)\n> +{\n> +\tstruct fs_info fs;\n> +\tif (fsmonitor__get_fs_info(path, &fs))\n> +\t\treturn -1;\n> +\n> +\tfree(fs.typename);\n\n...just to have some callers free() it again, maybe it makes sense to\nsay that return a const char *, and if you need it past the call to\nfsmonitor__get_fs_info() you should do the xstrdup() yourself.\n\nSkimming the end-state the OSX one gives you a copy of your own, as it's\nstuck into the struct you provided.\n\nFor Linux we don't get a copy, but it's filled per getmntent() call.\n\nAnyway, small potatoes...\n\n> +\tif (h == INVALID_HANDLE_VALUE) {\n> +\t\terror(_(\"[GLE %ld] unable to open for read '%ls'\"),\n> +\t\t      GetLastError(), wpath);\n> +\t\treturn -1;\n> +\t}\n\nDo away with the braces and just \"return error(...\"\n\n> +\n> +\tif (!GetFileInformationByHandleEx(h, FileRemoteProtocolInfo,\n> +\t\t&proto_info, sizeof(proto_info))) {\n> +\t\terror(_(\"[GLE %ld] unable to get protocol information for '%ls'\"),\n> +\t\t      GetLastError(), wpath);\n> +\t\tCloseHandle(h);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tCloseHandle(h);\n\nAnd this duplication you can avoid with an earlier:\n\n\tint ret = 0;\n\n\nThen\n\t\tret = error(...)\n\t\tgoto cleanup;\n\nand then at the end:\n\ncleanup:\n\tCloseHandle(h)\n\treturn ret;\n\n> +\t/*\n> +\t * Do everything in wide chars because the drive letter might be\n> +\t * a multi-byte sequence.  See win32_has_dos_drive_prefix().\n> +\t */\n> +\tif (xutftowcs_path(wpath, path) < 0) {\n> +\t\treturn -1;\n> +\t}\n\nDon't need the braces.\n> +\n> +\t/*\n> +\t * GetDriveTypeW() requires a final slash.  We assume that the\n> +\t * worktree pathname points to an actual directory.\n> +\t */\n> +\twlen = wcslen(wpath);\n> +\tif (wpath[wlen - 1] != L'\\\\' && wpath[wlen - 1] != L'/') {\n> +\t\twpath[wlen++] = L'\\\\';\n> +\t\twpath[wlen] = 0;\n> +\t}\n> +\n> +\t/*\n> +\t * Normalize the path.  If nothing else, this converts forward\n> +\t * slashes to backslashes.  This is essential to get GetDriveTypeW()\n> +\t * correctly handle some UNC \"\\\\server\\share\\...\" paths.\n> +\t */\n> +\tif (!GetFullPathNameW(wpath, MAX_PATH, wfullpath, NULL)) {\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tdriveType = GetDriveTypeW(wfullpath);\n\nCan't you just do away with this driveType variable and assign directly\nto fs_info->is_remote if it's == DRIVE_REMOTE?...\n\n> +\ttrace_printf_key(&trace_fsmonitor,\n> +\t\t\t \"DriveType '%s' L'%ls' (%u)\",\n> +\t\t\t path, wfullpath, driveType);\n\n...well, not due to this, but do we really need to log the raw value &\nis that meaningful? A user of the trace2 logs would need to reverse map\nthat, doesn't the API provide some \"what is it then?\" function?\n\n> +\n> +\tif (driveType == DRIVE_REMOTE) {\n> +\t\tfs_info->is_remote = 1;\n\nHere we do that assignment...\n\n> +\t\tif (check_remote_protocol(wfullpath) < 0)\n> +\t\t\treturn -1;\n> +\t} else {\n> +\t\tfs_info->is_remote = 0;\n> +\t}\n> +\n> +\ttrace_printf_key(&trace_fsmonitor,\n> +\t\t\t\t\"'%s' is_remote: %d\",\n> +\t\t\t\tpath, fs_info->is_remote);\n\nditto just \"is remote?\"\n"},{"id":"465190","messageId":"221018.86mt9ti9oq.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"743bdacded5183b5b11c9f19c24b07fa26eded24.1665326258.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 11/12] fsmonitor: test updates","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-10-18T12:42:15Z","receivedAt":"2022-10-18T12:43:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Oct 09 2022, Eric DeCosta via GitGitGadget wrote:\n\n> From: Eric DeCosta <edecosta@mathworks.com>\n\n> +wait_for_update () {\n> +\tfunc=$1 &&\n> +\tfile=$2 &&\n> +\tsz=$(wc -c < \"$file\") &&\n> +\tlast=0 &&\n> +\t$func &&\n\nOdd not to quote \"$func\" here, but anyway...\n\n> +rename_directory () {\n> +\tmv dirtorename dirrenamed\n> +}\n\n...uh, we need to wrap \"mv\"? Why?\n\n\n> -\tmv dirtorename dirrenamed &&\n> +\twait_for_update rename_directory \"$PWD/.git/trace\" &&\n\nJust to get around some quoting issue here?\n"},{"id":"465191","messageId":"221018.86ilkhi9je.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"77ed35b3b80de9ecfea179357115428ab89d9864.1665326258.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 12/12] fsmonitor: update doc for Linux","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-10-18T12:43:43Z","receivedAt":"2022-10-18T12:46:51Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Oct 09 2022, Eric DeCosta via GitGitGadget wrote:\n\n> From: Eric DeCosta <edecosta@mathworks.com>\n>\n> Update the documentation for Linux.\n>\n> Signed-off-by: Eric DeCosta <edecosta@mathworks.com>\n> ---\n>  Documentation/config/fsmonitor--daemon.txt |  4 ++--\n>  Documentation/git-fsmonitor--daemon.txt    | 24 ++++++++++++++--------\n>  2 files changed, 18 insertions(+), 10 deletions(-)\n>\n> diff --git a/Documentation/config/fsmonitor--daemon.txt b/Documentation/config/fsmonitor--daemon.txt\n> index c225c6c9e74..2cafb040d96 100644\n> --- a/Documentation/config/fsmonitor--daemon.txt\n> +++ b/Documentation/config/fsmonitor--daemon.txt\n> @@ -4,8 +4,8 @@ fsmonitor.allowRemote::\n>      behavior.  Only respected when `core.fsmonitor` is set to `true`.\n>  \n>  fsmonitor.socketDir::\n> -    This Mac OS-specific option, if set, specifies the directory in\n> +    Mac OS and Linux-specific option. If set, specifies the directory in\n>      which to create the Unix domain socket used for communication\n>      between the fsmonitor daemon and various Git commands. The directory must\n> -    reside on a native Mac OS filesystem.  Only respected when `core.fsmonitor`\n> +    reside on a native filesystem.  Only respected when `core.fsmonitor`\n>      is set to `true`.\n\nI think this should be squashed into the relevant commit(s) when we\nstart supporting this fsmonitor.socketDir for that platform.\n\nBut on the content: Shouldn't we just reword this to say something like\n\"*nix\"-specific, we only have one platform which is likely to not have\n\"sockets\", i.e. Windows, no?\n\nSo rather than having an ever growing list of OS's that'll inevitably\ngrow to something like:\n\n\tMac OS, Linux, FreeBSD, NetBSD, OpenBSD, GNU/Hurd-specific\n\toption, if set...\n\nLet's find some way to refer to the platforms we do support, and instead\nmention the only one we don't support sockets on?\n\n> diff --git a/Documentation/git-fsmonitor--daemon.txt b/Documentation/git-fsmonitor--daemon.txt\n> index 8238eadb0e1..c2b08229c74 100644\n> --- a/Documentation/git-fsmonitor--daemon.txt\n> +++ b/Documentation/git-fsmonitor--daemon.txt\n> @@ -76,23 +76,31 @@ repositories; this may be overridden by setting `fsmonitor.allowRemote` to\n>  correctly with all network-mounted repositories and such use is considered\n>  experimental.\n>  \n> -On Mac OS, the inter-process communication (IPC) between various Git\n> +On Linux and Mac OS, the inter-process communication (IPC) between various Git\n\nDitto.\n\n"},{"id":"465192","messageId":"221018.86edv5i94z.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"pull.1352.git.git.1665326258.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-10-18T12:47:24Z","receivedAt":"2022-10-18T12:55:31Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Oct 09 2022, Eric DeCosta via GitGitGadget wrote:\n\n> Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\n> Windows and Mac OS.\n\nI applaud the effort, I gave this some light reviewing, I hope it helps.\n\n> This patch set builds upon previous work for done for Windows and Mac OS\n> (first 6 patches) to implement a fsmonitor back-end for Linux based on the\n> Linux inotify API. inotify differs significantly from the equivalent Windows\n> and Mac OS APIs in that a watch must be registered for every directory of\n> interest (rather than a singular watch at the root of the directory tree)\n> and special care must be taken to handle directory renames correctly.\n>\n> More information about inotify:\n> https://man7.org/linux/man-pages/man7/inotify.7.html\n\nYou haven't said why/if you considered using fanotify() instead of\ninotify(), which seems like a more natural target to me. It's unlikely\nthat anyone who cares to use this isn't also using a new enough kernel.\n\nSee previous on-list discussions at:\nhttps://lore.kernel.org/git/?q=fanotify\n\nI think it would address some of the rac econditions you're mentioning\nhere.\n"},{"id":"465198","messageId":"s2r033rp-5r9n-48op-57qs-n1s2833n049n@tzk.qr","threadId":"58588","inReplyTo":"BL0PR05MB557123F87157E0E091A43494D9299@BL0PR05MB5571.namprd05.prod.outlook.com","subject":"RE: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-10-18T14:02:43Z","receivedAt":"2022-10-18T14:48:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Mon, 17 Oct 2022, Eric DeCosta wrote:\n\n> > -----Original Message-----\n> > From: Junio C Hamano <gitster@pobox.com>\n> > Sent: Friday, October 14, 2022 7:33 PM\n> > To: Eric DeCosta via GitGitGadget <gitgitgadget@gmail.com>\n> > Cc: git@vger.kernel.org; Eric Sunshine <sunshine@sunshineco.com>; Ævar\n> > Arnfjörð Bjarmason <avarab@gmail.com>; Eric DeCosta\n> > <edecosta@mathworks.com>\n> > Subject: Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux\n> >\n> > \"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n> > > Goal is to deliver fsmonitor for Linux that is on par with fsmonitor\n> > > for Windows and Mac OS.\n> > >\n> > > This patch set builds upon previous work for done for Windows and Mac\n> > > OS (first 6 patches) to implement a fsmonitor back-end for Linux based\n> > > on the Linux inotify API.\n> >\n> > Again, the first six patches are a part of what is queued as ed/fsmonitor-on-\n> > networked-macos that is now in 'next' but lacks a fix-up commit from Jeff\n> > King.\n> >\n> > I understand that it might not be easy/possible (e.g. perhaps it is a limitation\n> > of GGG?), but I really prefer not to see them re-posted as part of this series,\n> > as I have to apply them and make sure there are no changes from the last\n> > one before discarding them.\n\nGitGitGadget mirrors all branches from gitster/git to gitgitgadget/git, so\nif you open a PR in the latter repository, you can use all of those\nbranches as targets.\n\nBut this PR is in git/git, which does not offer that.\n\n> > Anyway, thanks for an update.  Will requeue.\n>\n> I looks like I can use GGG with the next branch, but I will have to open a new PR (and close the existing one).\n\nNo, you do not have to open a new PR for that.\n\nYou just hit the Edit button on the top (as if you wanted to change the\ntitle) and then select the target (\"base\") branch in the drop-down box\nunder the title.\n\nCiao,\nDscho\n"},{"id":"465210","messageId":"kl6l4jw1yshm.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58588","inReplyTo":"xmqqo7u9wyt7.fsf@gitster.g","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-10-18T17:03:01Z","receivedAt":"2022-10-18T17:03:08Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Cc-ed Johannes, who would know a lot more about CI than I do.\n\nJunio C Hamano <gitster@pobox.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>\n>> At $DAYJOB, we observed that this topic breaks MacOS builds with sha1dc:\n>\n> Thanks for a report.\n>\n> How dissapointing.  The thing is that the topic has been in 'next'\n> since the 11th (i.e. early last week), and I know that you guys rely\n> on the tip of 'next' in working order to cut your internal releases,\n> but we did not hear about this until now.\n\nYes. Unfortunately, we (Google's Git team) release on a weekly cadence;\nwe merge on Fridays and build on Mondays (PST), which makes this timing\nextremely unfortunate.\n\n> Possible action items:\n>\n>  * See what configurations our two macOS jobs are using.  If neither\n>    is using sha1dc, I would say that is criminal [*] and at least\n>    one of them should be updated to do so right away.\n\nI'm not too familiar with the CI, but I took a quick peek at ci/lib.sh\nand noticed that none of the jobs build with sha1dc, not even the Linux\nor Windows ones, so..\n\n>  * The \"two macOS CI jobs sharing too many configuration knobs\" may\n>    not be limited to just SHA-1 implementation, but unlike Linux\n>    builds and tests, we may have similar \"monoculture\" issue in our\n>    macOS CI builds.  Those users, who depend on macOS port being\n>    healthy, should help identify unnecessary overlaps between the\n>    two, and more importantly, make sure we have CI builds with\n>    configuration similar to what they actually use.\n\nI don't think this is a macOS-specific issue; our CI just doesn't do a\ngood job with testing various build configurations.\n\n>  * Adding a few build-only-without-tests CI jobs also might help.\n\nThis sounds like the way to go IMO. It might be too expensive to run the\nfull test suite on every build configuration, but build-without-test\nmight be ok.\n\n>  * Those who depend on working macOS port, especially those with\n>    corporate backing who choose to use configurations that are\n>    different from what we have CI builds for, are requested to\n>    arrange a more frequent build test to catch a problem like this\n>    much earlier.\n\nI wished we had caught it sooner too. The folks here generally agree\nthat our weekly release cycle is not ideal for reasons such as this.\nHopefully this is good motivation to move that work forward, though I\ncan't promise anything right now.\n\n> Anything else I forgot that we can do to improve the situation?  I\n> personally do not use macOS, I am not interested in using one, but\n> I do value those who choose to use macOS have happy git working on\n> their platform, so the stakeholders need to chip in.\n\nThere's nothing else I can think of at the moment. Thanks for your\npatience and for moving the conversation along.\n\n>\n> Thanks.\n>\n>\n> [Footnote]\n>\n>  * Until the world migrates over to SHA-256, the collision detecting\n>    SHA-1 implementation is what we must use unless there is a strong\n>    reason not to.  If we are not testing something that ought to be\n>    the default, we are not doing a very good job.\n"},{"id":"465213","messageId":"xmqqzgdtt5tj.fsf@gitster.g","threadId":"58588","inReplyTo":"kl6l4jw1yshm.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-18T17:11:36Z","receivedAt":"2022-10-18T17:11:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> I wished we had caught it sooner too. The folks here generally agree\n> that our weekly release cycle is not ideal for reasons such as this.\n> Hopefully this is good motivation to move that work forward, though I\n> can't promise anything right now.\n\nIt is perfectly OK to have an automated trial build job that runs\nmore frequently than your weekly release cycle, though.  It should\nusually yield only a single bit of usable information (e.g. \"there\nis no 'does not even build from the source' issue in upstream\") that\nmay give you assurance (e.g. \"if we maintain the course, the next\nreal build for release would hopefully go smoothly\"), but when it\nbreaks, you have more time to react.\n\nThanks.\n\n\n\n"},{"id":"465235","messageId":"221019.86fsfkhb14.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"xmqqo7u9wyt7.fsf@gitster.g","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-10-19T01:04:41Z","receivedAt":"2022-10-19T01:12:14Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Oct 17 2022, Junio C Hamano wrote:\n\n> Glen Choo <chooglen@google.com> writes:\n>\n>> At $DAYJOB, we observed that this topic breaks MacOS builds with sha1dc:\n>\n> Thanks for a report.\n>\n> How dissapointing.  The thing is that the topic has been in 'next'\n> since the 11th (i.e. early last week), and I know that you guys rely\n> on the tip of 'next' in working order to cut your internal releases,\n> but we did not hear about this until now.  What makes it taste even\n> worse is that nobody else caught this, even though we seem to have a\n> couple of macOS jobs at GitHub Actions CI, there we didn't see any\n> breakage related to this.\n\nFWIW I see you caught it on the 9th in\nhttps://lore.kernel.org/git/xmqqh70c62w0.fsf@gitster.g/, but then the\nbase topic was merged down on the 17th.\n\n> Possible action items:\n>\n>  * See what configurations our two macOS jobs are using.  If neither\n>    is using sha1dc, I would say that is criminal [*] and at least\n>    one of them should be updated to do so right away.\n\nI submitted a v2 of my series to finally make OSX use SHA1DC by default:\nhttps://lore.kernel.org/git/cover-v2-0.4-00000000000-20221019T010222Z-avarab@gmail.com/\n\nAs part of that we'll CI that & the current \"apple common crypto\"\nimplementations, currently we just do the latter, which is why we didn't\ncatch this.\n\nWe could follow-up and CI the OpenSSL one too, but that was a larger\nchange, so I punted on it.\n\nIn any case,\nhttps://lore.kernel.org/git/patch-v2-1.4-392fabdb456-20221019T010222Z-avarab@gmail.com/\nin that series un-breaks master, maybe you're interested in peeling it\noff & fast-tracking it? I didn't submit it separately because I don't\nknow how much of a rush we're in there (given that CI isn't broken).\n"},{"id":"465237","messageId":"221019.86bkq8hake.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"kl6l4jw1yshm.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-10-19T01:19:15Z","receivedAt":"2022-10-19T01:22:18Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Oct 18 2022, Glen Choo wrote:\n\n> Cc-ed Johannes, who would know a lot more about CI than I do.\n>\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Glen Choo <chooglen@google.com> writes:\n>>\n>>> At $DAYJOB, we observed that this topic breaks MacOS builds with sha1dc:\n>>\n>> Thanks for a report.\n>>\n>> How dissapointing.  The thing is that the topic has been in 'next'\n>> since the 11th (i.e. early last week), and I know that you guys rely\n>> on the tip of 'next' in working order to cut your internal releases,\n>> but we did not hear about this until now.\n>\n> Yes. Unfortunately, we (Google's Git team) release on a weekly cadence;\n> we merge on Fridays and build on Mondays (PST), which makes this timing\n> extremely unfortunate.\n>\n>> Possible action items:\n>>\n>>  * See what configurations our two macOS jobs are using.  If neither\n>>    is using sha1dc, I would say that is criminal [*] and at least\n>>    one of them should be updated to do so right away.\n>\n> I'm not too familiar with the CI, but I took a quick peek at ci/lib.sh\n> and noticed that none of the jobs build with sha1dc, not even the Linux\n> or Windows ones, so..\n\nAll of our jobs except the OSX one build with SHA1_DC, because it's the\ndefault.\n\nPer my just-sent\nhttps://lore.kernel.org/git/cover-v2-0.4-00000000000-20221019T010222Z-avarab@gmail.com/\nthe blind spot has been lack fo SHA1_DC on OSX, for others it's the\nreverse, we don't test e.g. BLK_SHA1.\n\nIn practice we've been catching SHA-implementation specific code early\nbecause the OSX implementation was different, but in this case it's\nOSX-only code, so it only supported the Apple Common Crypto backend.\n\n>>  * The \"two macOS CI jobs sharing too many configuration knobs\" may\n>>    not be limited to just SHA-1 implementation, but unlike Linux\n>>    builds and tests, we may have similar \"monoculture\" issue in our\n>>    macOS CI builds.  Those users, who depend on macOS port being\n>>    healthy, should help identify unnecessary overlaps between the\n>>    two, and more importantly, make sure we have CI builds with\n>>    configuration similar to what they actually use.\n>\n> I don't think this is a macOS-specific issue; our CI just doesn't do a\n> good job with testing various build configurations.\n\nYes, definitely.\n\n>>  * Adding a few build-only-without-tests CI jobs also might help.\n>\n> This sounds like the way to go IMO. It might be too expensive to run the\n> full test suite on every build configuration, but build-without-test\n> might be ok.\n\nYes, there's not much point in running the full tests for some of these\nvariants.\n\nA lot of it we can also get for free, e.g. we run some linux jobs with\nclang, some with gcc etc., we could also run one with BLK_SHA1, one with\nOPENSSL_SHA1 etc.\n"},{"id":"465238","messageId":"CAPig+cRi8RVdmPbDdTzDCpXKjUsbOJNc5Pr+d02OQ9jENHUjfQ@mail.gmail.com","threadId":"58588","inReplyTo":"221019.86bkq8hake.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2022-10-19T02:28:14Z","receivedAt":"2022-10-19T02:28:35Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Oct 18, 2022 at 9:22 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n> On Tue, Oct 18 2022, Glen Choo wrote:\n> > I'm not too familiar with the CI, but I took a quick peek at ci/lib.sh\n> > and noticed that none of the jobs build with sha1dc, not even the Linux\n> > or Windows ones, so..\n>\n> All of our jobs except the OSX one build with SHA1_DC, because it's the\n> default.\n>\n> Per my just-sent\n> https://lore.kernel.org/git/cover-v2-0.4-00000000000-20221019T010222Z-avarab@gmail.com/\n> the blind spot has been lack fo SHA1_DC on OSX, for others it's the\n> reverse, we don't test e.g. BLK_SHA1.\n>\n> In practice we've been catching SHA-implementation specific code early\n> because the OSX implementation was different, but in this case it's\n> OSX-only code, so it only supported the Apple Common Crypto backend.\n\nI don't know how germane it is to the current thread, but previous\ndiscussions[1,2,3,4] favored dropping use of Apple's Common Crypto\naltogether since it doesn't seem to buy us much (or anything) and is\nincomplete; it doesn't support all of the OpenSSL API Git uses.\n\n[1]: https://lore.kernel.org/git/CAPig+cTfMx_kwUAxBRHp6kNSOtXsdsv=odUQSRYVpV21DnRuvA@mail.gmail.com/\n[2]: https://lore.kernel.org/git/CAMYxyaVQyVRQb-b0nVv412tMZ3rEnOfUPRakg2dEREg5_Ba5Ag@mail.gmail.com/T/\n[3]: https://lore.kernel.org/git/20160102234923.GA14424@gmail.com/\n[4]: https://lore.kernel.org/git/CAPig+cQ5kKAt2_RQnqT7Rn=uGmHV9VvxpQ+UgDPOj=D=pq6arg@mail.gmail.com/\n"},{"id":"465257","messageId":"xmqqk04vsrhs.fsf@gitster.g","threadId":"58588","inReplyTo":"221019.86fsfkhb14.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-19T16:33:19Z","receivedAt":"2022-10-19T16:33:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Mon, Oct 17 2022, Junio C Hamano wrote:\n>\n>> Glen Choo <chooglen@google.com> writes:\n>>\n>>> At $DAYJOB, we observed that this topic breaks MacOS builds with sha1dc:\n>>\n>> Thanks for a report.\n>>\n>> How dissapointing.  The thing is that the topic has been in 'next'\n>> since the 11th (i.e. early last week), and I know that you guys rely\n>> on the tip of 'next' in working order to cut your internal releases,\n>> but we did not hear about this until now.  What makes it taste even\n>> worse is that nobody else caught this, even though we seem to have a\n>> couple of macOS jobs at GitHub Actions CI, there we didn't see any\n>> breakage related to this.\n>\n> FWIW I see you caught it on the 9th in\n> https://lore.kernel.org/git/xmqqh70c62w0.fsf@gitster.g/, but then the\n> base topic was merged down on the 17th.\n\nHeh, I already forgot I made that comment myself ;-) Thanks for\nreminding, and thanks for picking the single step to fast-track the\nfix.\n"},{"id":"465258","messageId":"xmqqedv3sqc1.fsf@gitster.g","threadId":"58588","inReplyTo":"CAPig+cRi8RVdmPbDdTzDCpXKjUsbOJNc5Pr+d02OQ9jENHUjfQ@mail.gmail.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-19T16:58:22Z","receivedAt":"2022-10-19T16:58:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> I don't know how germane it is to the current thread, but previous\n> discussions[1,2,3,4] favored dropping use of Apple's Common Crypto\n> altogether since it doesn't seem to buy us much (or anything) and is\n> incomplete; it doesn't support all of the OpenSSL API Git uses.\n\nYeah, that matches my recollection.  Unless the situation has much\nchanged, dropping it may not be a bad thing to do.\n\nBut\n\n * Fixing the fsmonitor code so that it can also be used with things\n   other than Common Crypto is the most urgent.  The topic gave us a\n   grave regression (those who used to successfully build Git can no\n   longer build with their favourite configuration).\n\n * Updating the build procedure so that sha1dc is used by default\n   everywhere is a good idea, but that is less urgent and should be\n   done separately, preferrably long after the dust settles from the\n   above.\n\n * Removing Common Crypto support may not be a bad idea, but that is\n   even less urgent, unless the support burden is slowing us down or\n   forcing us to settle on common set of features that is too\n   limiting.\n\nThanks.\n"},{"id":"465264","messageId":"221019.8635bjhbds.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"CAPig+cRi8RVdmPbDdTzDCpXKjUsbOJNc5Pr+d02OQ9jENHUjfQ@mail.gmail.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-10-19T19:11:57Z","receivedAt":"2022-10-19T19:17:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Oct 18 2022, Eric Sunshine wrote:\n\n> On Tue, Oct 18, 2022 at 9:22 PM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>> On Tue, Oct 18 2022, Glen Choo wrote:\n>> > I'm not too familiar with the CI, but I took a quick peek at ci/lib.sh\n>> > and noticed that none of the jobs build with sha1dc, not even the Linux\n>> > or Windows ones, so..\n>>\n>> All of our jobs except the OSX one build with SHA1_DC, because it's the\n>> default.\n>>\n>> Per my just-sent\n>> https://lore.kernel.org/git/cover-v2-0.4-00000000000-20221019T010222Z-avarab@gmail.com/\n>> the blind spot has been lack fo SHA1_DC on OSX, for others it's the\n>> reverse, we don't test e.g. BLK_SHA1.\n>>\n>> In practice we've been catching SHA-implementation specific code early\n>> because the OSX implementation was different, but in this case it's\n>> OSX-only code, so it only supported the Apple Common Crypto backend.\n>\n> I don't know how germane it is to the current thread, but previous\n> discussions[1,2,3,4] favored dropping use of Apple's Common Crypto\n> altogether since it doesn't seem to buy us much (or anything) and is\n> incomplete; it doesn't support all of the OpenSSL API Git uses.\n>\n> [1]: https://lore.kernel.org/git/CAPig+cTfMx_kwUAxBRHp6kNSOtXsdsv=odUQSRYVpV21DnRuvA@mail.gmail.com/\n> [2]: https://lore.kernel.org/git/CAMYxyaVQyVRQb-b0nVv412tMZ3rEnOfUPRakg2dEREg5_Ba5Ag@mail.gmail.com/T/\n> [3]: https://lore.kernel.org/git/20160102234923.GA14424@gmail.com/\n> [4]: https://lore.kernel.org/git/CAPig+cQ5kKAt2_RQnqT7Rn=uGmHV9VvxpQ+UgDPOj=D=pq6arg@mail.gmail.com/\n\nYeah, maybe. I'd be 100% OK with that happening, but I don't use OSX\noutside fo CI really, so I wanted to avoid scope creep to \"non-SHA stuff\nwe use OpenSSL/AppleCommonCrypto/whatever\" for, in the cases where we\ncan/do use OpenSSL/AppleCommonCrypto/whatever for SHA also.\n\nI.e. that's basically a question about what https://, git-imap-send\netc. should be using if not vanilla OpenSSL & the like.\n\nBut if you/someone can come up with a patch that confidently asserts\nthat it's useless & drops it I'd be happy to either integrate it &\nsubmit it as part of this series if it makes things simpler, or\nalternatively re-roll on top of it.\n\nI'm just not confident in making that case myself, since I hardly use\nthe platform, am not too familiar with the various concerns (aside from\nskimming the links above), and don't really have interest in pursuing\nthat.\n\nOTOH if you do want to make the case for dropping Apple Common Crypto\nthat would also presumably be much easier after this series, once we're\npast justifying the hurdle of \"wait, we're switching the thing we use\nfor SHA-1 by default, which we build practically everything in git on?\"\n:)\n\n1. https://lore.kernel.org/git/CAPig+cQ5kKAt2_RQnqT7Rn=uGmHV9VvxpQ+UgDPOj=D=pq6arg@mail.gmail.com/\n"},{"id":"465267","messageId":"CAPig+cSCb6tA+Ox=xK+LuC+Hf=JRRBr3=xx9cJ0yQaguHm+HSQ@mail.gmail.com","threadId":"58588","inReplyTo":"221019.8635bjhbds.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2022-10-19T20:14:46Z","receivedAt":"2022-10-19T20:15:05Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Oct 19, 2022 at 3:16 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n> On Tue, Oct 18 2022, Eric Sunshine wrote:\n> > On Tue, Oct 18, 2022 at 9:22 PM Ævar Arnfjörð Bjarmason\n> > <avarab@gmail.com> wrote:\n> >> In practice we've been catching SHA-implementation specific code early\n> >> because the OSX implementation was different, but in this case it's\n> >> OSX-only code, so it only supported the Apple Common Crypto backend.\n> >\n> > I don't know how germane it is to the current thread, but previous\n> > discussions[1,2,3,4] favored dropping use of Apple's Common Crypto\n> > altogether since it doesn't seem to buy us much (or anything) and is\n> > incomplete; it doesn't support all of the OpenSSL API Git uses.\n>\n> Yeah, maybe. I'd be 100% OK with that happening, but I don't use OSX\n> outside fo CI really, so I wanted to avoid scope creep to \"non-SHA stuff\n> we use OpenSSL/AppleCommonCrypto/whatever\" for, in the cases where we\n> can/do use OpenSSL/AppleCommonCrypto/whatever for SHA also.\n\nIndeed, I was not suggesting that retirement of AppleCommonCrypto be\ntackled by you or by the series you posted to fix the build failure;\ndoing so is well outside the scope of the immediate problem. I brought\nup those old discussions only as reference and as a pointer that\nwhatever fix is eventually adopted for the immediate build problem\nneed not necessarily bend over backward for AppleCommonCrypto support\nif a long-term goal is to drop AppleCommonCrypto (i.e. do as little as\nnecessary to keep AppleCommonCrypto in a working state, but don't go\noverboard trying to give it first-class support since it will never be\na complete replacement for OpenSSL).\n\n> But if you/someone can come up with a patch that confidently asserts\n> that it's useless & drops it I'd be happy to either integrate it &\n> submit it as part of this series if it makes things simpler, or\n> alternatively re-roll on top of it.\n>\n> I'm just not confident in making that case myself, since I hardly use\n> the platform, am not too familiar with the various concerns (aside from\n> skimming the links above), and don't really have interest in pursuing\n> that.\n\nI was not suggesting holding up your series or integrating retirement\nof AppleCommonCrypto with it; they are separate concerns.\n\n> OTOH if you do want to make the case for dropping Apple Common Crypto\n> that would also presumably be much easier after this series, once we're\n> past justifying the hurdle of \"wait, we're switching the thing we use\n> for SHA-1 by default, which we build practically everything in git on?\"\n\nMy \"don't know how germane it is to the current thread\" observation\nwas in response to the `fsmonitor` thread, before I ever saw the\nseries you posted for fixing the build failure, so it wasn't given as\nany sort of feedback on your series, and wasn't asking you to\nincorporate retirement of AppleCommonCrypto into your series. That\nsaid, it does appear that dropping AppleCommonCrypto should become\neasier after your changes land if someone opts to tackle the task.\n"},{"id":"465297","messageId":"xmqqczammrf5.fsf@gitster.g","threadId":"58588","inReplyTo":"kl6l7d0yyu6r.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-20T15:43:42Z","receivedAt":"2022-10-20T15:43:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> At $DAYJOB, we observed that this topic breaks MacOS builds with sha1dc:\n>\n>   $ make NO_APPLE_COMMON_CRYPTO=1 DC_SHA1=1 NO_OPENSSL=1 compat/fsmonitor/fsm-ipc-darwin.o  \n\nGlen, Ævar has cherry-picked the SHA_CTX -> git_SHA_CTX build fix\nand I have merged it to 'next'.  Can you test-build to see if that\nchange is sufficient to fix \"does not even build from the source\"\nissue for you guys?  You do not have to go through any official full\nqualification cycle or release procedure---just checking that you\nwould be OK when you need to do the build the next time before it\nhas to happen is sufficient.\n\nThanks.\n"},{"id":"465301","messageId":"xmqq8rlamq29.fsf@gitster.g","threadId":"58588","inReplyTo":"xmqqo7u9wyt7.fsf@gitster.g","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-20T16:13:02Z","receivedAt":"2022-10-20T16:13:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Possible action items:\n>\n>  * See what configurations our two macOS jobs are using.  If neither\n>    is using sha1dc, I would say that is criminal [*] and at least\n>    one of them should be updated to do so right away.\n\nSo here is my \"panda-see-panda-do\" attempt.\n\n----- >8 --------- >8 --------- >8 --------- >8 --------- >8 -----\nSubject: [PATCH] ci: use DC_SHA1=1 on osx-clang job for CI\n\nAll other jobs were using the default DC_SHA1 (which is a\nrecommended practice), but the default for macOS jobs being Apple's\ncommon crypt, we didn't catch recent breakage soon enough.\n\nWe may want to give similar diversity for Linux jobs so that some of\nthem build with other implementations of SHA-1, but let's start\nsmall and fill only the immediate need.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n ci/lib.sh | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/ci/lib.sh b/ci/lib.sh\nindex 1b0cc2b57d..5a115704cb 100755\n--- a/ci/lib.sh\n+++ b/ci/lib.sh\n@@ -259,6 +259,8 @@ macos-latest)\n \t\tMAKEFLAGS=\"$MAKEFLAGS PYTHON_PATH=$(which python3)\"\n \telse\n \t\tMAKEFLAGS=\"$MAKEFLAGS PYTHON_PATH=$(which python2)\"\n+\t\tMAKEFLAGS=\"$MAKEFLAGS NO_APPLE_COMMON_CRYPTO=NoThanks\"\n+\t\tMAKEFLAGS=\"$MAKEFLAGS DC_SHA1=1 NO_OPENSSL=1\"\n \tfi\n \t;;\n esac\n-- \n2.38.1-267-g82836053dd\n\n"},{"id":"465302","messageId":"CAPig+cRkQLyy8X7y=1jhrVwYERk5ucagAfJkTAOPNODofiZr1g@mail.gmail.com","threadId":"58588","inReplyTo":"xmqq8rlamq29.fsf@gitster.g","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2022-10-20T16:37:48Z","receivedAt":"2022-10-20T16:38:03Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Oct 20, 2022 at 12:13 PM Junio C Hamano <gitster@pobox.com> wrote:\n> So here is my \"panda-see-panda-do\" attempt.\n>\n> ----- >8 --------- >8 --------- >8 --------- >8 --------- >8 -----\n> Subject: [PATCH] ci: use DC_SHA1=1 on osx-clang job for CI\n>\n> All other jobs were using the default DC_SHA1 (which is a\n> recommended practice), but the default for macOS jobs being Apple's\n> common crypt, we didn't catch recent breakage soon enough.\n\n\"recent breakage\" is quite vague and probably won't help future\nreaders understand what this is actually fixing. Possible\nimprovements: (1) a prose description of the breakage; (2) the actual\ncompiler error message; (3) a pointer[1] to the email reporting the\nproblem. One or more of the above improvements to the commit message\nwould help future readers.\n\n[1]: https://lore.kernel.org/git/kl6l7d0yyu6r.fsf@chooglen-macbookpro.roam.corp.google.com/\n\n> We may want to give similar diversity for Linux jobs so that some of\n> them build with other implementations of SHA-1, but let's start\n> small and fill only the immediate need.\n>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n> diff --git a/ci/lib.sh b/ci/lib.sh\n> index 1b0cc2b57d..5a115704cb 100755\n> --- a/ci/lib.sh\n> +++ b/ci/lib.sh\n> @@ -259,6 +259,8 @@ macos-latest)\n>                 MAKEFLAGS=\"$MAKEFLAGS PYTHON_PATH=$(which python3)\"\n>         else\n>                 MAKEFLAGS=\"$MAKEFLAGS PYTHON_PATH=$(which python2)\"\n> +               MAKEFLAGS=\"$MAKEFLAGS NO_APPLE_COMMON_CRYPTO=NoThanks\"\n> +               MAKEFLAGS=\"$MAKEFLAGS DC_SHA1=1 NO_OPENSSL=1\"\n>         fi\n>         ;;\n>  esac\n"},{"id":"465303","messageId":"xmqq35bitooc.fsf@gitster.g","threadId":"58588","inReplyTo":"CAPig+cRkQLyy8X7y=1jhrVwYERk5ucagAfJkTAOPNODofiZr1g@mail.gmail.com","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-20T17:01:07Z","receivedAt":"2022-10-20T17:01:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> On Thu, Oct 20, 2022 at 12:13 PM Junio C Hamano <gitster@pobox.com> wrote:\n>> So here is my \"panda-see-panda-do\" attempt.\n>>\n>> ----- >8 --------- >8 --------- >8 --------- >8 --------- >8 -----\n>> Subject: [PATCH] ci: use DC_SHA1=1 on osx-clang job for CI\n>>\n>> All other jobs were using the default DC_SHA1 (which is a\n>> recommended practice), but the default for macOS jobs being Apple's\n>> common crypt, we didn't catch recent breakage soon enough.\n>\n> \"recent breakage\" is quite vague and probably won't help future\n> readers understand what this is actually fixing. Possible\n> improvements: (1) a prose description of the breakage; (2) the actual\n> compiler error message; (3) a pointer[1] to the email reporting the\n> problem. One or more of the above improvements to the commit message\n> would help future readers.\n\nI do not think (2) or (3) would help all that much.  A finger that\npoints at the exact commit that broke the build (with the condition\nunder which the build breaks) would probably be the most useful to\nthose who read \"git log\" output.\n\nThanks.\n\n----- >8 --------- >8 --------- >8 --------- >8 --------- >8 -----\nSubject: [PATCH] ci: use DC_SHA1=YesPlease on osx-clang job for CI\n\n7b8cfe34 (Merge branch 'ed/fsmonitor-on-networked-macos',\n2022-10-17) broke the build on macOS with sha1dc by bypassing our\nhash abstraction (git_SHA_CTX etc.), but it wasn't caught before the\nproblematic topic was merged down to the 'master' branch.  Nobody\nwas even compile testing with DC_SHA1 set, although it is the\nrecommended choice in these days for folks when they use SHA-1.\n\nThis was because the default for macOS uses Apple Common Crypto, and\nboth of the two CI jobs did not override the default.  Tweak one of\nthem to use DC_SHA1 to improve the coverage.\n\nWe may want to give similar diversity for Linux jobs so that some of\nthem build with other implementations of SHA-1; they currently all\nbuild and test with DC_SHA1 as that is the default on everywhere\nother than macOS.\n\nBut let's start small to fill only the immediate need.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n ci/lib.sh | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/ci/lib.sh b/ci/lib.sh\nindex 1b0cc2b57d..51e47aa5d6 100755\n--- a/ci/lib.sh\n+++ b/ci/lib.sh\n@@ -259,6 +259,8 @@ macos-latest)\n \t\tMAKEFLAGS=\"$MAKEFLAGS PYTHON_PATH=$(which python3)\"\n \telse\n \t\tMAKEFLAGS=\"$MAKEFLAGS PYTHON_PATH=$(which python2)\"\n+\t\tMAKEFLAGS=\"$MAKEFLAGS NO_APPLE_COMMON_CRYPTO=NoThanks\"\n+\t\tMAKEFLAGS=\"$MAKEFLAGS DC_SHA1=YesPlease NO_OPENSSL=NoThanks\"\n \tfi\n \t;;\n esac\n-- \n2.38.1-271-ge78cd6c470\n\n"},{"id":"465311","messageId":"CAPig+cQHTvv3pYEnDVer6JT21Jd7kADuF5LG+nt=hd6K_5PoXA@mail.gmail.com","threadId":"58588","inReplyTo":"xmqq35bitooc.fsf@gitster.g","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2022-10-20T17:54:46Z","receivedAt":"2022-10-20T17:55:07Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Oct 20, 2022 at 1:01 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> > On Thu, Oct 20, 2022 at 12:13 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >> All other jobs were using the default DC_SHA1 (which is a\n> >> recommended practice), but the default for macOS jobs being Apple's\n> >> common crypt, we didn't catch recent breakage soon enough.\n> >\n> > \"recent breakage\" is quite vague and probably won't help future\n> > readers understand what this is actually fixing. Possible\n> > improvements: (1) a prose description of the breakage; (2) the actual\n> > compiler error message; (3) a pointer[1] to the email reporting the\n> > problem. One or more of the above improvements to the commit message\n> > would help future readers.\n>\n> I do not think (2) or (3) would help all that much.  A finger that\n> points at the exact commit that broke the build (with the condition\n> under which the build breaks) would probably be the most useful to\n> those who read \"git log\" output.\n>\n> ----- >8 --------- >8 --------- >8 --------- >8 --------- >8 -----\n> Subject: [PATCH] ci: use DC_SHA1=YesPlease on osx-clang job for CI\n>\n> 7b8cfe34 (Merge branch 'ed/fsmonitor-on-networked-macos',\n> 2022-10-17) broke the build on macOS with sha1dc by bypassing our\n> hash abstraction (git_SHA_CTX etc.), but it wasn't caught before the\n> problematic topic was merged down to the 'master' branch.  Nobody\n> was even compile testing with DC_SHA1 set, although it is the\n> recommended choice in these days for folks when they use SHA-1.\n>\n> This was because the default for macOS uses Apple Common Crypto, and\n> both of the two CI jobs did not override the default.  Tweak one of\n> them to use DC_SHA1 to improve the coverage.\n\nThanks. This revised commit message does a much better job of\nexplaining the problem the patch is addressing.\n"},{"id":"465354","messageId":"xmqqtu3yqhm2.fsf@gitster.g","threadId":"58588","inReplyTo":"xmqqczammrf5.fsf@gitster.g","subject":"Re: [PATCH v2 00/12] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-20T22:01:57Z","receivedAt":"2022-10-20T22:02:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>\n>> At $DAYJOB, we observed that this topic breaks MacOS builds with sha1dc:\n>>\n>>   $ make NO_APPLE_COMMON_CRYPTO=1 DC_SHA1=1 NO_OPENSSL=1 compat/fsmonitor/fsm-ipc-darwin.o  \n>\n> Glen, Ævar has cherry-picked the SHA_CTX -> git_SHA_CTX build fix\n> and I have merged it to 'next'.  Can you test-build to see if that\n> change is sufficient to fix \"does not even build from the source\"\n> issue for you guys?  You do not have to go through any official full\n> qualification cycle or release procedure---just checking that you\n> would be OK when you need to do the build the next time before it\n> has to happen is sufficient.\n>\n> Thanks.\n\nFYI, https://github.com/git/git/actions/runs/3292710187 is a\nsuccessful (wow, what's a rare event these days) run of CI on 'seen'\nthat includes\n\n * Peff's -O0 fix for unconfusing LSan to prevent it from giving\n   false positives\n\n * Eric DeCosta's change, cherry-picked by Ævar, to let fsmonitor\n   stuff compile with sha1dc as well as Apple's Common Crypto.\n\n * A tweak to make one of the macOS CI jobs to build with sha1dc.\n\namong all the other topics in flight.  I plan to merge all of them\nto 'next' (I think the first two are already in 'next') and fast\ntrack them down to 'master'.\n"},{"id":"467402","messageId":"pull.1352.v3.git.git.1668641019.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v2.git.git.1665783944.gitgitgadget@gmail.com","subject":"[PATCH v3 0/6] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-16T23:23:33Z","receivedAt":"2022-11-16T23:23:50Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\nWindows and Mac OS.\n\nThis patch set builds upon previous work for done for Windows and Mac OS to\nimplement a fsmonitor back-end for Linux based on the Linux inotify API.\ninotify differs significantly from the equivalent Windows and Mac OS APIs in\nthat a watch must be registered for every directory of interest (rather than\na singular watch at the root of the directory tree) and special care must be\ntaken to handle directory renames correctly.\n\nMore information about inotify:\nhttps://man7.org/linux/man-pages/man7/inotify.7.html\n\nv2 differs from v1:\n\n * Prior work for Windows and Mac OS has been merged to master, reducing the\n   patch set from 12 to 6 patches\n * Code review feedback\n * Identified and resolved race condition revealed by CI test system, see\n   \"Limitations and caveats\" regarding monitoring of directory trees from\n   the man page, above\n * Apologies for being away from this for so long, but my attention was\n   needed elsewhere\n\nv1 differs from v0:\n\n * Code review feedback\n * Update how and which code can be shared between Mac OS and Linux\n * Increase polling frequency to every 1ms (matches Mac OS)\n * Updates to t7527 to improve test stability\n\nEric DeCosta (6):\n  fsmonitor: prepare to share code between Mac OS and Linux\n  fsmonitor: determine if filesystem is local or remote\n  fsmonitor: implement filesystem change listener for Linux\n  fsmonitor: enable fsmonitor for Linux\n  fsmonitor: test updates\n  fsmonitor: update doc for Linux\n\n Documentation/config/fsmonitor--daemon.txt |   4 +-\n Documentation/git-fsmonitor--daemon.txt    |  24 +-\n compat/fsmonitor/fsm-health-linux.c        |  24 +\n compat/fsmonitor/fsm-ipc-darwin.c          |  53 +-\n compat/fsmonitor/fsm-ipc-linux.c           |   1 +\n compat/fsmonitor/fsm-ipc-unix.c            |  52 ++\n compat/fsmonitor/fsm-listen-linux.c        | 676 +++++++++++++++++++++\n compat/fsmonitor/fsm-path-utils-linux.c    | 169 ++++++\n compat/fsmonitor/fsm-settings-darwin.c     |  63 +-\n compat/fsmonitor/fsm-settings-linux.c      |   1 +\n compat/fsmonitor/fsm-settings-unix.c       |  61 ++\n config.mak.uname                           |   8 +\n contrib/buildsystems/CMakeLists.txt        |  11 +-\n t/t7527-builtin-fsmonitor.sh               |  94 ++-\n 14 files changed, 1102 insertions(+), 139 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-health-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-unix.c\n create mode 100644 compat/fsmonitor/fsm-listen-linux.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.c\n\n\nbase-commit: 319605f8f00e402f3ea758a02c63534ff800a711\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1352%2Fedecosta-mw%2Ffsmonitor_linux-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1352/edecosta-mw/fsmonitor_linux-v3\nPull-Request: https://github.com/git/git/pull/1352\n\nRange-diff vs v2:\n\n  1:  cd46bed37c3 <  -:  ----------- fsmonitor: refactor filesystem checks to common interface\n  2:  21d114bda4b <  -:  ----------- fsmonitor: relocate socket file if .git directory is remote\n  3:  664259ed57a <  -:  ----------- fsmonitor: avoid socket location check if using hook\n  4:  d8f20032d6b <  -:  ----------- fsmonitor: deal with synthetic firmlinks on macOS\n  5:  ab1e0ab967c <  -:  ----------- fsmonitor: check for compatability before communicating with fsmonitor\n  6:  9c552239b57 <  -:  ----------- fsmonitor: add documentation for allowRemote and socketDir options\n  7:  295beb89ab1 !  1:  99d684c7bdf fsmonitor: prepare to share code between Mac OS and Linux\n     @@ Commit message\n      \n          Linux and Mac OS can share some of the code originally developed for Mac OS.\n      \n     -    Minor update to compat/fsmonitor/fsm-ipc-unix.c to make it cross-platform.\n     -    Mac OS and Linux can share fsm-ipc-unix.c\n     -\n     -    Both platforms can also share compat/fsmonitor/fsm-settings-unix.c but we\n     -    will leave room for future, platform-specific checks by having the platform-\n     -    specific implementations call into fsm-settings-unix.\n     +    Mac OS and Linux can share fsm-ipc-unix.c and fsm-settings-unix.c\n      \n          Signed-off-by: Eric DeCosta <edecosta@mathworks.com>\n      \n     - ## Makefile ##\n     -@@ Makefile: endif\n     - \n     - ifdef FSMONITOR_DAEMON_BACKEND\n     - \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND\n     -+\tifdef FSMONITOR_DAEMON_COMMON\n     -+\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_COMMON).o\n     -+\telse\n     -+\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n     -+\tendif\n     - \tCOMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o\n     - \tCOMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o\n     --\tCOMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o\n     - endif\n     - \n     - ifdef FSMONITOR_OS_SETTINGS\n     - \tCOMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS\n     -+\tifdef FSMONITOR_DAEMON_COMMON\n     -+\t\tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_DAEMON_COMMON).o\n     -+\tendif\n     - \tCOMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o\n     - \tCOMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o\n     - endif\n     -\n       ## compat/fsmonitor/fsm-health-linux.c (new) ##\n      @@\n      +#include \"cache.h\"\n     @@ compat/fsmonitor/fsm-health-linux.c (new)\n      +{\n      +}\n      \n     - ## compat/fsmonitor/fsm-ipc-darwin.c => compat/fsmonitor/fsm-ipc-unix.c ##\n     -@@ compat/fsmonitor/fsm-ipc-unix.c: static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n     - const char *fsmonitor_ipc__get_path(struct repository *r)\n     - {\n     - \tstatic const char *ipc_path = NULL;\n     --\tSHA_CTX sha1ctx;\n     + ## compat/fsmonitor/fsm-ipc-darwin.c ##\n     +@@\n     +-#include \"cache.h\"\n     +-#include \"config.h\"\n     +-#include \"strbuf.h\"\n     +-#include \"fsmonitor.h\"\n     +-#include \"fsmonitor-ipc.h\"\n     +-#include \"fsmonitor-path-utils.h\"\n     +-\n     +-static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n     +-\n     +-const char *fsmonitor_ipc__get_path(struct repository *r)\n     +-{\n     +-\tstatic const char *ipc_path = NULL;\n     +-\tgit_SHA_CTX sha1ctx;\n     +-\tchar *sock_dir = NULL;\n     +-\tstruct strbuf ipc_file = STRBUF_INIT;\n     +-\tunsigned char hash[GIT_MAX_RAWSZ];\n     +-\n     +-\tif (!r)\n     +-\t\tBUG(\"No repository passed into fsmonitor_ipc__get_path\");\n     +-\n     +-\tif (ipc_path)\n     +-\t\treturn ipc_path;\n     +-\n     +-\n     +-\t/* By default the socket file is created in the .git directory */\n     +-\tif (fsmonitor__is_fs_remote(r->gitdir) < 1) {\n     +-\t\tipc_path = fsmonitor_ipc__get_default_path();\n     +-\t\treturn ipc_path;\n     +-\t}\n     +-\n     +-\tgit_SHA1_Init(&sha1ctx);\n     +-\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n     +-\tgit_SHA1_Final(hash, &sha1ctx);\n     +-\n     +-\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n     +-\n     +-\t/* Create the socket file in either socketDir or $HOME */\n     +-\tif (sock_dir && *sock_dir) {\n     +-\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n     +-\t\t\t\t\tsock_dir, hash_to_hex(hash));\n     +-\t} else {\n     +-\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n     +-\t}\n     +-\tfree(sock_dir);\n     +-\n     +-\tipc_path = interpolate_path(ipc_file.buf, 1);\n     +-\tif (!ipc_path)\n     +-\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n     +-\n     +-\tstrbuf_release(&ipc_file);\n     +-\treturn ipc_path;\n     +-}\n     ++#include \"fsm-ipc-unix.c\"\n     +\n     + ## compat/fsmonitor/fsm-ipc-linux.c (new) ##\n     +@@\n     ++#include \"fsm-ipc-unix.c\"\n     +\n     + ## compat/fsmonitor/fsm-ipc-unix.c (new) ##\n     +@@\n     ++#include \"cache.h\"\n     ++#include \"config.h\"\n     ++#include \"strbuf.h\"\n     ++#include \"fsmonitor.h\"\n     ++#include \"fsmonitor-ipc.h\"\n     ++#include \"fsmonitor-path-utils.h\"\n     ++\n     ++static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n     ++\n     ++const char *fsmonitor_ipc__get_path(struct repository *r)\n     ++{\n     ++\tstatic const char *ipc_path = NULL;\n      +\tgit_SHA_CTX sha1ctx;\n     - \tchar *sock_dir = NULL;\n     - \tstruct strbuf ipc_file = STRBUF_INIT;\n     - \tunsigned char hash[SHA_DIGEST_LENGTH];\n     -@@ compat/fsmonitor/fsm-ipc-unix.c: const char *fsmonitor_ipc__get_path(struct repository *r)\n     - \t\treturn ipc_path;\n     - \t}\n     - \n     --\tSHA1_Init(&sha1ctx);\n     --\tSHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n     --\tSHA1_Final(hash, &sha1ctx);\n     ++\tchar *sock_dir = NULL;\n     ++\tstruct strbuf ipc_file = STRBUF_INIT;\n     ++\tunsigned char hash[GIT_MAX_RAWSZ];\n     ++\n     ++\tif (!r)\n     ++\t\tBUG(\"No repository passed into fsmonitor_ipc__get_path\");\n     ++\n     ++\tif (ipc_path)\n     ++\t\treturn ipc_path;\n     ++\n     ++\n     ++\t/* By default the socket file is created in the .git directory */\n     ++\tif (fsmonitor__is_fs_remote(r->gitdir) < 1) {\n     ++\t\tipc_path = fsmonitor_ipc__get_default_path();\n     ++\t\treturn ipc_path;\n     ++\t}\n     ++\n      +\tgit_SHA1_Init(&sha1ctx);\n      +\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n      +\tgit_SHA1_Final(hash, &sha1ctx);\n     - \n     - \trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n     - \n     ++\n     ++\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n     ++\n     ++\t/* Create the socket file in either socketDir or $HOME */\n     ++\tif (sock_dir && *sock_dir) {\n     ++\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n     ++\t\t\t\t\tsock_dir, hash_to_hex(hash));\n     ++\t} else {\n     ++\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n     ++\t}\n     ++\tfree(sock_dir);\n     ++\n     ++\tipc_path = interpolate_path(ipc_file.buf, 1);\n     ++\tif (!ipc_path)\n     ++\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n     ++\n     ++\tstrbuf_release(&ipc_file);\n     ++\treturn ipc_path;\n     ++}\n      \n       ## compat/fsmonitor/fsm-settings-darwin.c ##\n      @@\n     - #include \"config.h\"\n     - #include \"fsmonitor.h\"\n     - #include \"fsmonitor-ipc.h\"\n     +-#include \"config.h\"\n     +-#include \"fsmonitor.h\"\n     +-#include \"fsmonitor-ipc.h\"\n      -#include \"fsmonitor-settings.h\"\n     - #include \"fsmonitor-path-utils.h\"\n     +-#include \"fsmonitor-path-utils.h\"\n      -\n      - /*\n      - * For the builtin FSMonitor, we create the Unix domain socket for the\n     @@ compat/fsmonitor/fsm-settings-darwin.c\n      -\tfree(fs.typename);\n      -\treturn FSMONITOR_REASON_OK;\n      -}\n     -+#include \"fsmonitor-settings.h\"\n     -+#include \"fsm-settings-unix.h\"\n     - \n     - enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n     - {\n     +-\n     +-enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n     +-{\n      -\tenum fsmonitor_reason reason;\n      -\n      -\tif (ipc) {\n     @@ compat/fsmonitor/fsm-settings-darwin.c\n      -\t}\n      -\n      -\treturn FSMONITOR_REASON_OK;\n     -+    return fsm_os__incompatible_unix(r, ipc);\n     - }\n     +-}\n     ++#include \"fsm-settings-unix.c\"\n      \n       ## compat/fsmonitor/fsm-settings-linux.c (new) ##\n      @@\n     -+#include \"config.h\"\n     -+#include \"fsmonitor.h\"\n     -+#include \"fsmonitor-ipc.h\"\n     -+#include \"fsmonitor-path-utils.h\"\n     -+#include \"fsmonitor-settings.h\"\n     -+#include \"fsm-settings-unix.h\"\n     -+\n     -+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n     -+{\n     -+    return fsm_os__incompatible_unix(r, ipc);\n     -+}\n     ++#include \"fsm-settings-unix.c\"\n      \n       ## compat/fsmonitor/fsm-settings-unix.c (new) ##\n      @@\n     @@ compat/fsmonitor/fsm-settings-unix.c (new)\n      +#include \"fsmonitor.h\"\n      +#include \"fsmonitor-ipc.h\"\n      +#include \"fsmonitor-path-utils.h\"\n     -+#include \"fsm-settings-unix.h\"\n      +\n      + /*\n      + * For the builtin FSMonitor, we create the Unix domain socket for the\n     @@ compat/fsmonitor/fsm-settings-unix.c (new)\n      +\tstruct fs_info fs;\n      +\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n      +\tstruct strbuf path = STRBUF_INIT;\n     -+\tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n     ++\tstrbuf_addstr(&path, ipc_path);\n      +\n      +\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n      +\t\tstrbuf_release(&path);\n     @@ compat/fsmonitor/fsm-settings-unix.c (new)\n      +\treturn FSMONITOR_REASON_OK;\n      +}\n      +\n     -+enum fsmonitor_reason fsm_os__incompatible_unix(struct repository *r, int ipc)\n     ++enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n      +{\n      +\tenum fsmonitor_reason reason;\n      +\n     @@ compat/fsmonitor/fsm-settings-unix.c (new)\n      +\n      +\treturn FSMONITOR_REASON_OK;\n      +}\n     -\n     - ## compat/fsmonitor/fsm-settings-unix.h (new) ##\n     -@@\n     -+#ifndef FSM_SETTINGS_UNIX_H\n     -+#define FSM_SETTINGS_UNIX_H\n     -+\n     -+#ifdef HAVE_FSMONITOR_OS_SETTINGS\n     -+/*\n     -+ * Check for compatibility on unix-like systems (e.g. Darwin and Linux)\n     -+ */\n     -+enum fsmonitor_reason fsm_os__incompatible_unix(struct repository *r, int ipc);\n     -+#endif /* HAVE_FSMONITOR_OS_SETTINGS */\n     -+\n     -+#endif /* FSM_SETTINGS_UNIX_H */\n     -\n     - ## config.mak.uname ##\n     -@@ config.mak.uname: ifeq ($(uname_S),Darwin)\n     - \tifndef NO_UNIX_SOCKETS\n     - \tFSMONITOR_DAEMON_BACKEND = darwin\n     - \tFSMONITOR_OS_SETTINGS = darwin\n     -+\tFSMONITOR_DAEMON_COMMON = unix\n     - \tendif\n     - \tendif\n     - \n     -\n     - ## contrib/buildsystems/CMakeLists.txt ##\n     -@@ contrib/buildsystems/CMakeLists.txt: else()\n     - endif()\n     - \n     - if(SUPPORTS_SIMPLE_IPC)\n     --\tif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n     -+\tif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n     -+\t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-unix.c)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-linux.c)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-linux.c)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-linux.c)\n     -+\n     -+\t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-unix.c)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-linux.c)\n     -+\telseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n     - \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n     - \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n     - \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n     -@@ contrib/buildsystems/CMakeLists.txt: if(SUPPORTS_SIMPLE_IPC)\n     - \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c)\n     - \telseif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n     - \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n     --\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-unix.c)\n     - \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c)\n     --\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-darwin.c)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)\n     - \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c)\n     - \n     - \t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n     -+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-unix.c)\n     - \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)\n     - \tendif()\n     - endif()\n  8:  7d7ef78728f =  2:  aa405379891 fsmonitor: determine if filesystem is local or remote\n  9:  4f9c5358475 !  3:  c2e5a7201aa fsmonitor: implement filesystem change listener for Linux\n     @@ compat/fsmonitor/fsm-listen-linux.c (new)\n      +/*\n      + * Recursively add watches to every directory under path\n      + */\n     -+static int register_inotify(const char *path, struct fsm_listen_data *data)\n     ++static int register_inotify(const char *path,\n     ++\tstruct fsmonitor_daemon_state *state,\n     ++\tstruct fsmonitor_batch *batch)\n      +{\n      +\tDIR *dir;\n     ++\tconst char *rel;\n      +\tstruct strbuf current = STRBUF_INIT;\n      +\tstruct dirent *de;\n      +\tstruct stat fs;\n     @@ compat/fsmonitor/fsm-listen-linux.c (new)\n      +\n      +\t\t/* recurse into directory */\n      +\t\tif (S_ISDIR(fs.st_mode)) {\n     -+\t\t\tif (add_watch(current.buf, data))\n     ++\t\t\tif (add_watch(current.buf, state->listen_data))\n      +\t\t\t\tgoto failed;\n     -+\t\t\tif (register_inotify(current.buf, data))\n     ++\t\t\tif (register_inotify(current.buf, state, batch))\n      +\t\t\t\tgoto failed;\n     ++\t\t} else if (batch) {\n     ++\t\t\trel = current.buf + state->path_worktree_watch.len + 1;\n     ++\t\t\ttrace_printf_key(&trace_fsmonitor, \"explicitly adding '%s'\", rel);\n     ++\t\t\tfsmonitor_batch__add_path(batch, rel);\n      +\t\t}\n      +\t}\n      +\tret = 0;\n     @@ compat/fsmonitor/fsm-listen-linux.c (new)\n      +\n      +\tif (add_watch(state->path_worktree_watch.buf, data))\n      +\t\tret = -1;\n     -+\telse if (register_inotify(state->path_worktree_watch.buf, data))\n     ++\telse if (register_inotify(state->path_worktree_watch.buf, state, NULL))\n      +\t\tret = -1;\n      +\telse if (state->nr_paths_watching > 1) {\n      +\t\tif (add_watch(state->path_gitdir_watch.buf, data))\n      +\t\t\tret = -1;\n     -+\t\telse if (register_inotify(state->path_gitdir_watch.buf, data))\n     ++\t\telse if (register_inotify(state->path_gitdir_watch.buf, state, NULL))\n      +\t\t\tret = -1;\n      +\t}\n      +\n     @@ compat/fsmonitor/fsm-listen-linux.c (new)\n      +\t\t\t\tadd_dir_rename(event->cookie, path, state->listen_data);\n      +\n      +\t\t\t/* received IN_MOVE_TO, update watch to reflect new path */\n     -+\t\t\tif (em_rename_dir_to(event->mask))\n     ++\t\t\tif (em_rename_dir_to(event->mask)) {\n      +\t\t\t\trename_dir(event->cookie, path, state->listen_data);\n     ++\t\t\t\tif (register_inotify(path, state, batch)) {\n     ++\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n     ++\t\t\t\t\tgoto done;\n     ++\t\t\t\t}\n     ++\t\t\t}\n      +\n      +\t\t\tif (em_dir_created(event->mask)) {\n      +\t\t\t\tif (add_watch(path, state->listen_data)) {\n      +\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n      +\t\t\t\t\tgoto done;\n      +\t\t\t\t}\n     -+\t\t\t\tif (register_inotify(path, state->listen_data)) {\n     ++\t\t\t\tif (register_inotify(path, state, batch)) {\n      +\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n      +\t\t\t\t\tgoto done;\n      +\t\t\t\t}\n     @@ compat/fsmonitor/fsm-listen-linux.c (new)\n      +\t\t\tstrbuf_reset(&path);\n      +\t\t\tstrbuf_add(&path, w->dir, strlen(w->dir));\n      +\t\t\tstrbuf_addch(&path, '/');\n     -+\t\t\tstrbuf_add(&path, event->name,  strlen(event->name));\n     ++\t\t\tstrbuf_addstr(&path, event->name);\n      +\n      +\t\t\tp = fsmonitor__resolve_alias(path.buf, &state->alias);\n      +\t\t\tif (!p)\n 10:  07650ecd27b !  4:  05f5b2dd4fb fsmonitor: enable fsmonitor for Linux\n     @@ config.mak.uname: ifeq ($(uname_S),Linux)\n      +\tifndef NO_UNIX_SOCKETS\n      +\tFSMONITOR_DAEMON_BACKEND = linux\n      +\tFSMONITOR_OS_SETTINGS = linux\n     -+\tFSMONITOR_DAEMON_COMMON = unix\n      +\tendif\n      +\tendif\n       endif\n       ifeq ($(uname_S),GNU/kFreeBSD)\n       \tHAVE_ALLOCA_H = YesPlease\n     +\n     + ## contrib/buildsystems/CMakeLists.txt ##\n     +@@ contrib/buildsystems/CMakeLists.txt: else()\n     + endif()\n     + \n     + if(SUPPORTS_SIMPLE_IPC)\n     +-\tif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n     ++\tif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n     ++\t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-linux.c)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-linux.c)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-linux.c)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-linux.c)\n     ++\n     ++\t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n     ++\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-linux.c)\n     ++\telseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n     + \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n     + \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n     + \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n 11:  6682938fff8 !  5:  754355ca44f fsmonitor: test updates\n     @@ t/t7527-builtin-fsmonitor.sh: start_daemon () {\n      +\t\tthen\n      +\t\t\tif test \"$last\" -eq \"$nsz\"\n      +\t\t\tthen\n     ++\t\t\t\tcat \"$file\" &&\n      +\t\t\t\treturn 0\n      +\t\t\tfi\n      +\t\t\tlast=$nsz\n     @@ t/t7527-builtin-fsmonitor.sh: start_daemon () {\n      +\t\tsleep 1\n      +\t\tk=$(( $k + 1 ))\n      +\tdone &&\n     ++\tcat \"$file\" &&\n      +\treturn 0\n      +}\n      +\n     @@ t/t7527-builtin-fsmonitor.sh: create_files () {\n      +rename_directory () {\n      +\tmv dirtorename dirrenamed\n      +}\n     ++\n     ++rename_directory_file () {\n     ++\tmv dirtorename dirrenamed &&\n     ++\techo 1 > dirrenamed/new\n     ++}\n      +\n       rename_files () {\n       \tmv rename renamed &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'rename directory' '\n       \tgrep \"^event: dirtorename/*$\" .git/trace &&\n       \tgrep \"^event: dirrenamed/*$\"  .git/trace\n       '\n     -@@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'file changes to directory' '\n     + \n     ++test_expect_success 'rename directory file' '\n     ++\ttest_when_finished clean_up_repo_and_stop_daemon &&\n     ++\n     ++\tstart_daemon --tf \"$PWD/.git/trace\" &&\n     ++\n     ++\twait_for_update rename_directory_file \"$PWD/.git/trace\" &&\n     ++\n     ++\ttest-tool fsmonitor-client query --token 0 &&\n     ++\n     ++\ttest_might_fail git fsmonitor--daemon stop &&\n     ++\n     ++\tgrep \"^event: dirtorename/*$\" .git/trace &&\n     ++\tgrep \"^event: dirrenamed/*$\"  .git/trace &&\n     ++\tgrep \"^event: dirrenamed/new$\"  .git/trace\n     ++'\n     + test_expect_success 'file changes to directory' '\n     + \ttest_when_finished clean_up_repo_and_stop_daemon &&\n       \n       \tstart_daemon --tf \"$PWD/.git/trace\" &&\n       \n     @@ t/t7527-builtin-fsmonitor.sh: u_values=\"$u1 $u2\"\n       \n       \t\tgit init \"$u\" &&\n       \t\techo 1 >\"$u\"/file1 &&\n     -@@ t/t7527-builtin-fsmonitor.sh: my_match_and_clean () {\n     - }\n     +@@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'submodule setup' '\n     + '\n       \n       test_expect_success 'submodule always visited' '\n      -\ttest_when_finished \"git -C super fsmonitor--daemon stop; \\\n 12:  dd73e126810 =  6:  f56175e097a fsmonitor: update doc for Linux\n\n-- \ngitgitgadget\n"},{"id":"467403","messageId":"99d684c7bdfac9db9cbf74bae1d21f9dc20ac825.1668641019.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v3.git.git.1668641019.gitgitgadget@gmail.com","subject":"[PATCH v3 1/6] fsmonitor: prepare to share code between Mac OS and Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-16T23:23:34Z","receivedAt":"2022-11-16T23:23:52Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nLinux and Mac OS can share some of the code originally developed for Mac OS.\n\nMac OS and Linux can share fsm-ipc-unix.c and fsm-settings-unix.c\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-health-linux.c    | 24 ++++++++++\n compat/fsmonitor/fsm-ipc-darwin.c      | 53 +---------------------\n compat/fsmonitor/fsm-ipc-linux.c       |  1 +\n compat/fsmonitor/fsm-ipc-unix.c        | 52 +++++++++++++++++++++\n compat/fsmonitor/fsm-settings-darwin.c | 63 +-------------------------\n compat/fsmonitor/fsm-settings-linux.c  |  1 +\n compat/fsmonitor/fsm-settings-unix.c   | 61 +++++++++++++++++++++++++\n 7 files changed, 141 insertions(+), 114 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-health-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-unix.c\n create mode 100644 compat/fsmonitor/fsm-settings-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.c\n\ndiff --git a/compat/fsmonitor/fsm-health-linux.c b/compat/fsmonitor/fsm-health-linux.c\nnew file mode 100644\nindex 00000000000..b9f709e8548\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-health-linux.c\n@@ -0,0 +1,24 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsm-health.h\"\n+#include \"fsmonitor--daemon.h\"\n+\n+int fsm_health__ctor(struct fsmonitor_daemon_state *state)\n+{\n+\treturn 0;\n+}\n+\n+void fsm_health__dtor(struct fsmonitor_daemon_state *state)\n+{\n+\treturn;\n+}\n+\n+void fsm_health__loop(struct fsmonitor_daemon_state *state)\n+{\n+\treturn;\n+}\n+\n+void fsm_health__stop_async(struct fsmonitor_daemon_state *state)\n+{\n+}\ndiff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-darwin.c\nindex d67b0ee50d3..4c3c92081ee 100644\n--- a/compat/fsmonitor/fsm-ipc-darwin.c\n+++ b/compat/fsmonitor/fsm-ipc-darwin.c\n@@ -1,52 +1 @@\n-#include \"cache.h\"\n-#include \"config.h\"\n-#include \"strbuf.h\"\n-#include \"fsmonitor.h\"\n-#include \"fsmonitor-ipc.h\"\n-#include \"fsmonitor-path-utils.h\"\n-\n-static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n-\n-const char *fsmonitor_ipc__get_path(struct repository *r)\n-{\n-\tstatic const char *ipc_path = NULL;\n-\tgit_SHA_CTX sha1ctx;\n-\tchar *sock_dir = NULL;\n-\tstruct strbuf ipc_file = STRBUF_INIT;\n-\tunsigned char hash[GIT_MAX_RAWSZ];\n-\n-\tif (!r)\n-\t\tBUG(\"No repository passed into fsmonitor_ipc__get_path\");\n-\n-\tif (ipc_path)\n-\t\treturn ipc_path;\n-\n-\n-\t/* By default the socket file is created in the .git directory */\n-\tif (fsmonitor__is_fs_remote(r->gitdir) < 1) {\n-\t\tipc_path = fsmonitor_ipc__get_default_path();\n-\t\treturn ipc_path;\n-\t}\n-\n-\tgit_SHA1_Init(&sha1ctx);\n-\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n-\tgit_SHA1_Final(hash, &sha1ctx);\n-\n-\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n-\n-\t/* Create the socket file in either socketDir or $HOME */\n-\tif (sock_dir && *sock_dir) {\n-\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n-\t\t\t\t\tsock_dir, hash_to_hex(hash));\n-\t} else {\n-\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n-\t}\n-\tfree(sock_dir);\n-\n-\tipc_path = interpolate_path(ipc_file.buf, 1);\n-\tif (!ipc_path)\n-\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n-\n-\tstrbuf_release(&ipc_file);\n-\treturn ipc_path;\n-}\n+#include \"fsm-ipc-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-ipc-linux.c b/compat/fsmonitor/fsm-ipc-linux.c\nnew file mode 100644\nindex 00000000000..4c3c92081ee\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-ipc-linux.c\n@@ -0,0 +1 @@\n+#include \"fsm-ipc-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-ipc-unix.c b/compat/fsmonitor/fsm-ipc-unix.c\nnew file mode 100644\nindex 00000000000..d67b0ee50d3\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-ipc-unix.c\n@@ -0,0 +1,52 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"strbuf.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n+\n+static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n+\n+const char *fsmonitor_ipc__get_path(struct repository *r)\n+{\n+\tstatic const char *ipc_path = NULL;\n+\tgit_SHA_CTX sha1ctx;\n+\tchar *sock_dir = NULL;\n+\tstruct strbuf ipc_file = STRBUF_INIT;\n+\tunsigned char hash[GIT_MAX_RAWSZ];\n+\n+\tif (!r)\n+\t\tBUG(\"No repository passed into fsmonitor_ipc__get_path\");\n+\n+\tif (ipc_path)\n+\t\treturn ipc_path;\n+\n+\n+\t/* By default the socket file is created in the .git directory */\n+\tif (fsmonitor__is_fs_remote(r->gitdir) < 1) {\n+\t\tipc_path = fsmonitor_ipc__get_default_path();\n+\t\treturn ipc_path;\n+\t}\n+\n+\tgit_SHA1_Init(&sha1ctx);\n+\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n+\tgit_SHA1_Final(hash, &sha1ctx);\n+\n+\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n+\n+\t/* Create the socket file in either socketDir or $HOME */\n+\tif (sock_dir && *sock_dir) {\n+\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n+\t\t\t\t\tsock_dir, hash_to_hex(hash));\n+\t} else {\n+\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n+\t}\n+\tfree(sock_dir);\n+\n+\tipc_path = interpolate_path(ipc_file.buf, 1);\n+\tif (!ipc_path)\n+\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n+\n+\tstrbuf_release(&ipc_file);\n+\treturn ipc_path;\n+}\ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c\nindex 6abbc7af3ab..14baf9f0603 100644\n--- a/compat/fsmonitor/fsm-settings-darwin.c\n+++ b/compat/fsmonitor/fsm-settings-darwin.c\n@@ -1,62 +1 @@\n-#include \"config.h\"\n-#include \"fsmonitor.h\"\n-#include \"fsmonitor-ipc.h\"\n-#include \"fsmonitor-settings.h\"\n-#include \"fsmonitor-path-utils.h\"\n-\n- /*\n- * For the builtin FSMonitor, we create the Unix domain socket for the\n- * IPC in the .git directory.  If the working directory is remote,\n- * then the socket will be created on the remote file system.  This\n- * can fail if the remote file system does not support UDS file types\n- * (e.g. smbfs to a Windows server) or if the remote kernel does not\n- * allow a non-local process to bind() the socket.  (These problems\n- * could be fixed by moving the UDS out of the .git directory and to a\n- * well-known local directory on the client machine, but care should\n- * be taken to ensure that $HOME is actually local and not a managed\n- * file share.)\n- *\n- * FAT32 and NTFS working directories are problematic too.\n- *\n- * The builtin FSMonitor uses a Unix domain socket in the .git\n- * directory for IPC.  These Windows drive formats do not support\n- * Unix domain sockets, so mark them as incompatible for the daemon.\n- *\n- */\n-static enum fsmonitor_reason check_uds_volume(struct repository *r)\n-{\n-\tstruct fs_info fs;\n-\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n-\tstruct strbuf path = STRBUF_INIT;\n-\tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n-\n-\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n-\t\tstrbuf_release(&path);\n-\t\treturn FSMONITOR_REASON_ERROR;\n-\t}\n-\n-\tstrbuf_release(&path);\n-\n-\tif (fs.is_remote ||\n-\t\t!strcmp(fs.typename, \"msdos\") ||\n-\t\t!strcmp(fs.typename, \"ntfs\")) {\n-\t\tfree(fs.typename);\n-\t\treturn FSMONITOR_REASON_NOSOCKETS;\n-\t}\n-\n-\tfree(fs.typename);\n-\treturn FSMONITOR_REASON_OK;\n-}\n-\n-enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n-{\n-\tenum fsmonitor_reason reason;\n-\n-\tif (ipc) {\n-\t\treason = check_uds_volume(r);\n-\t\tif (reason != FSMONITOR_REASON_OK)\n-\t\t\treturn reason;\n-\t}\n-\n-\treturn FSMONITOR_REASON_OK;\n-}\n+#include \"fsm-settings-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-settings-linux.c b/compat/fsmonitor/fsm-settings-linux.c\nnew file mode 100644\nindex 00000000000..14baf9f0603\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-settings-linux.c\n@@ -0,0 +1 @@\n+#include \"fsm-settings-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-settings-unix.c b/compat/fsmonitor/fsm-settings-unix.c\nnew file mode 100644\nindex 00000000000..a6ed32575a9\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-settings-unix.c\n@@ -0,0 +1,61 @@\n+#include \"config.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n+\n+ /*\n+ * For the builtin FSMonitor, we create the Unix domain socket for the\n+ * IPC in the .git directory.  If the working directory is remote,\n+ * then the socket will be created on the remote file system.  This\n+ * can fail if the remote file system does not support UDS file types\n+ * (e.g. smbfs to a Windows server) or if the remote kernel does not\n+ * allow a non-local process to bind() the socket.  (These problems\n+ * could be fixed by moving the UDS out of the .git directory and to a\n+ * well-known local directory on the client machine, but care should\n+ * be taken to ensure that $HOME is actually local and not a managed\n+ * file share.)\n+ *\n+ * FAT32 and NTFS working directories are problematic too.\n+ *\n+ * The builtin FSMonitor uses a Unix domain socket in the .git\n+ * directory for IPC.  These Windows drive formats do not support\n+ * Unix domain sockets, so mark them as incompatible for the daemon.\n+ *\n+ */\n+static enum fsmonitor_reason check_uds_volume(struct repository *r)\n+{\n+\tstruct fs_info fs;\n+\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstrbuf_addstr(&path, ipc_path);\n+\n+\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n+\t\tstrbuf_release(&path);\n+\t\treturn FSMONITOR_REASON_ERROR;\n+\t}\n+\n+\tstrbuf_release(&path);\n+\n+\tif (fs.is_remote ||\n+\t\t!strcmp(fs.typename, \"msdos\") ||\n+\t\t!strcmp(fs.typename, \"ntfs\")) {\n+\t\tfree(fs.typename);\n+\t\treturn FSMONITOR_REASON_NOSOCKETS;\n+\t}\n+\n+\tfree(fs.typename);\n+\treturn FSMONITOR_REASON_OK;\n+}\n+\n+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n+{\n+\tenum fsmonitor_reason reason;\n+\n+\tif (ipc) {\n+\t\treason = check_uds_volume(r);\n+\t\tif (reason != FSMONITOR_REASON_OK)\n+\t\t\treturn reason;\n+\t}\n+\n+\treturn FSMONITOR_REASON_OK;\n+}\n-- \ngitgitgadget\n\n"},{"id":"467404","messageId":"aa405379891bdb1a8fcad750767aedd965db77fa.1668641019.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v3.git.git.1668641019.gitgitgadget@gmail.com","subject":"[PATCH v3 2/6] fsmonitor: determine if filesystem is local or remote","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-16T23:23:35Z","receivedAt":"2022-11-16T23:23:56Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nCompare the given path to the mounted filesystems. Find the mount that is\nthe longest prefix of the path (if any) and determine if that mount is on a\nlocal or remote filesystem.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-path-utils-linux.c | 169 ++++++++++++++++++++++++\n 1 file changed, 169 insertions(+)\n create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n\ndiff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c\nnew file mode 100644\nindex 00000000000..039f044b670\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-path-utils-linux.c\n@@ -0,0 +1,169 @@\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-path-utils.h\"\n+#include <errno.h>\n+#include <mntent.h>\n+#include <sys/mount.h>\n+#include <sys/statvfs.h>\n+\n+/*\n+ * https://github.com/coreutils/gnulib/blob/master/lib/mountlist.c\n+ */\n+#ifndef ME_REMOTE\n+/* A file system is \"remote\" if its Fs_name contains a ':'\n+   or if (it is of type (smbfs or cifs) and its Fs_name starts with '//')\n+   or if it is of any other of the listed types\n+   or Fs_name is equal to \"-hosts\" (used by autofs to mount remote fs).\n+   \"VM\" file systems like prl_fs or vboxsf are not considered remote here. */\n+# define ME_REMOTE(Fs_name, Fs_type)            \\\n+\t(strchr (Fs_name, ':') != NULL              \\\n+\t || ((Fs_name)[0] == '/'                    \\\n+\t\t && (Fs_name)[1] == '/'                 \\\n+\t\t && (strcmp (Fs_type, \"smbfs\") == 0     \\\n+\t\t\t || strcmp (Fs_type, \"smb3\") == 0   \\\n+\t\t\t || strcmp (Fs_type, \"cifs\") == 0)) \\\n+\t || strcmp (Fs_type, \"acfs\") == 0           \\\n+\t || strcmp (Fs_type, \"afs\") == 0            \\\n+\t || strcmp (Fs_type, \"coda\") == 0           \\\n+\t || strcmp (Fs_type, \"auristorfs\") == 0     \\\n+\t || strcmp (Fs_type, \"fhgfs\") == 0          \\\n+\t || strcmp (Fs_type, \"gpfs\") == 0           \\\n+\t || strcmp (Fs_type, \"ibrix\") == 0          \\\n+\t || strcmp (Fs_type, \"ocfs2\") == 0          \\\n+\t || strcmp (Fs_type, \"vxfs\") == 0           \\\n+\t || strcmp (\"-hosts\", Fs_name) == 0)\n+#endif\n+\n+static int find_mount(const char *path, const struct statvfs *fs,\n+\tstruct mntent *ent)\n+{\n+\tconst char *const mounts = \"/proc/mounts\";\n+\tconst char *rp = real_pathdup(path, 1);\n+\tstruct mntent *ment = NULL;\n+\tstruct statvfs mntfs;\n+\tFILE *fp;\n+\tint found = 0;\n+\tint dlen, plen, flen = 0;\n+\n+\tent->mnt_fsname = NULL;\n+\tent->mnt_dir = NULL;\n+\tent->mnt_type = NULL;\n+\n+\tfp = setmntent(mounts, \"r\");\n+\tif (!fp) {\n+\t\terror_errno(_(\"setmntent('%s') failed\"), mounts);\n+\t\treturn -1;\n+\t}\n+\n+\tplen = strlen(rp);\n+\n+\t/* read all the mount information and compare to path */\n+\twhile ((ment = getmntent(fp)) != NULL) {\n+\t\tif (statvfs(ment->mnt_dir, &mntfs)) {\n+\t\t\tswitch (errno) {\n+\t\t\tcase EPERM:\n+\t\t\tcase ESRCH:\n+\t\t\tcase EACCES:\n+\t\t\t\tcontinue;\n+\t\t\tdefault:\n+\t\t\t\terror_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n+\t\t\t\tendmntent(fp);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* is mount on the same filesystem and is a prefix of the path */\n+\t\tif ((fs->f_fsid == mntfs.f_fsid) &&\n+\t\t\t!strncmp(ment->mnt_dir, rp, strlen(ment->mnt_dir))) {\n+\t\t\tdlen = strlen(ment->mnt_dir);\n+\t\t\tif (dlen > plen)\n+\t\t\t\tcontinue;\n+\t\t\t/*\n+\t\t\t * root is always a potential match; otherwise look for\n+\t\t\t * directory prefix\n+\t\t\t */\n+\t\t\tif ((dlen == 1 && ment->mnt_dir[0] == '/') ||\n+\t\t\t\t(dlen > flen && (!rp[dlen] || rp[dlen] == '/'))) {\n+\t\t\t\tflen = dlen;\n+\t\t\t\t/*\n+\t\t\t\t * https://man7.org/linux/man-pages/man3/getmntent.3.html\n+\t\t\t\t *\n+\t\t\t\t * The pointer points to a static area of memory which is\n+\t\t\t\t * overwritten by subsequent calls to getmntent().\n+\t\t\t\t */\n+\t\t\t\tfound = 1;\n+\t\t\t\tfree(ent->mnt_fsname);\n+\t\t\t\tfree(ent->mnt_dir);\n+\t\t\t\tfree(ent->mnt_type);\n+\t\t\t\tent->mnt_fsname = xstrdup(ment->mnt_fsname);\n+\t\t\t\tent->mnt_dir = xstrdup(ment->mnt_dir);\n+\t\t\t\tent->mnt_type = xstrdup(ment->mnt_type);\n+\t\t\t}\n+\t\t}\n+\t}\n+\tendmntent(fp);\n+\n+\tif (!found)\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n+{\n+\tstruct mntent ment;\n+\tstruct statvfs fs;\n+\n+\tif (statvfs(path, &fs))\n+\t\treturn error_errno(_(\"statvfs('%s') failed\"), path);\n+\n+\n+\tif (find_mount(path, &fs, &ment) < 0) {\n+\t\tfree(ment.mnt_fsname);\n+\t\tfree(ment.mnt_dir);\n+\t\tfree(ment.mnt_type);\n+\t\treturn -1;\n+\t}\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n+\t\t\t path, fs.f_flag, ment.mnt_type, ment.mnt_fsname);\n+\n+\tfs_info->is_remote = ME_REMOTE(ment.mnt_fsname, ment.mnt_type);\n+\tfs_info->typename = ment.mnt_fsname;\n+\tfree(ment.mnt_dir);\n+\tfree(ment.mnt_type);\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"'%s' is_remote: %d\",\n+\t\t\t\tpath, fs_info->is_remote);\n+\treturn 0;\n+}\n+\n+int fsmonitor__is_fs_remote(const char *path)\n+{\n+\tstruct fs_info fs;\n+\n+\tif (fsmonitor__get_fs_info(path, &fs))\n+\t\treturn -1;\n+\n+\tfree(fs.typename);\n+\n+\treturn fs.is_remote;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info)\n+{\n+\treturn 0;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info)\n+{\n+\treturn NULL;\n+}\n-- \ngitgitgadget\n\n"},{"id":"467405","messageId":"c2e5a7201aa7d1ea3896cdcadb949f9fa30415aa.1668641019.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v3.git.git.1668641019.gitgitgadget@gmail.com","subject":"[PATCH v3 3/6] fsmonitor: implement filesystem change listener for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-16T23:23:36Z","receivedAt":"2022-11-16T23:23:58Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nImplement a filesystem change listener for Linux based on the inotify API:\nhttps://man7.org/linux/man-pages/man7/inotify.7.html\n\ninotify requires registering a watch on every directory in the worktree and\nspecial handling of moves/renames.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-listen-linux.c | 676 ++++++++++++++++++++++++++++\n 1 file changed, 676 insertions(+)\n create mode 100644 compat/fsmonitor/fsm-listen-linux.c\n\ndiff --git a/compat/fsmonitor/fsm-listen-linux.c b/compat/fsmonitor/fsm-listen-linux.c\nnew file mode 100644\nindex 00000000000..e8548e4e009\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-listen-linux.c\n@@ -0,0 +1,676 @@\n+#include \"cache.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsm-listen.h\"\n+#include \"fsmonitor--daemon.h\"\n+#include <dirent.h>\n+#include <fcntl.h>\n+#include <sys/inotify.h>\n+#include <sys/stat.h>\n+\n+/*\n+ * Safe value to bitwise OR with rest of mask for\n+ * kernels that do not support IN_MASK_CREATE\n+ */\n+#ifndef IN_MASK_CREATE\n+#define IN_MASK_CREATE 0x00000000\n+#endif\n+\n+enum shutdown_reason {\n+\tSHUTDOWN_CONTINUE = 0,\n+\tSHUTDOWN_STOP,\n+\tSHUTDOWN_ERROR,\n+\tSHUTDOWN_FORCE\n+};\n+\n+struct watch_entry {\n+\tstruct hashmap_entry ent;\n+\tint wd;\n+\tuint32_t cookie;\n+\tconst char *dir;\n+};\n+\n+struct rename_entry {\n+\tstruct hashmap_entry ent;\n+\ttime_t whence;\n+\tuint32_t cookie;\n+\tconst char *dir;\n+};\n+\n+struct fsm_listen_data {\n+\tint fd_inotify;\n+\tenum shutdown_reason shutdown;\n+\tstruct hashmap watches;\n+\tstruct hashmap renames;\n+\tstruct hashmap revwatches;\n+};\n+\n+static int watch_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct watch_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct watch_entry, ent);\n+\te2 = container_of(eptr, const struct watch_entry, ent);\n+\treturn e1->wd != e2->wd;\n+}\n+\n+static int revwatches_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct watch_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct watch_entry, ent);\n+\te2 = container_of(eptr, const struct watch_entry, ent);\n+\treturn strcmp(e1->dir, e2->dir);\n+}\n+\n+static int rename_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct rename_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct rename_entry, ent);\n+\te2 = container_of(eptr, const struct rename_entry, ent);\n+\treturn e1->cookie != e2->cookie;\n+}\n+\n+/*\n+ * Register an inotify watch, add watch descriptor to path mapping\n+ * and the reverse mapping.\n+ */\n+static int add_watch(const char *path, struct fsm_listen_data *data)\n+{\n+\tconst char *interned = strintern(path);\n+\tstruct watch_entry *w1, *w2;\n+\n+\t/* add the inotify watch, don't allow watches to be modified */\n+\tint wd = inotify_add_watch(data->fd_inotify, interned,\n+\t\t\t\t(IN_ALL_EVENTS | IN_ONLYDIR | IN_MASK_CREATE)\n+\t\t\t\t^ IN_ACCESS ^ IN_CLOSE ^ IN_OPEN);\n+\tif (wd < 0)\n+\t\treturn error_errno(\"inotify_add_watch('%s') failed\", interned);\n+\n+\t/* add watch descriptor -> directory mapping */\n+\tCALLOC_ARRAY(w1, 1);\n+\tw1->wd = wd;\n+\tw1->dir = interned;\n+\thashmap_entry_init(&w1->ent, memhash(&w1->wd, sizeof(int)));\n+\thashmap_add(&data->watches, &w1->ent);\n+\n+\t/* add directory -> watch descriptor mapping */\n+\tCALLOC_ARRAY(w2, 1);\n+\tw2->wd = wd;\n+\tw2->dir = interned;\n+\thashmap_entry_init(&w2->ent, memhash(w2->dir, strlen(w2->dir)));\n+\thashmap_add(&data->revwatches, &w2->ent);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove the inotify watch, the watch descriptor to path mapping\n+ * and the reverse mapping.\n+ */\n+static void remove_watch(struct watch_entry *w,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct watch_entry k1, k2, *w1, *w2;\n+\n+\t/* remove watch, ignore error if kernel already did it */\n+\tif (inotify_rm_watch(data->fd_inotify, w->wd) && errno != EINVAL)\n+\t\terror_errno(\"inotify_rm_watch() failed\");\n+\n+\thashmap_entry_init(&k1.ent, memhash(&w->wd, sizeof(int)));\n+\tw1 = hashmap_remove_entry(&data->watches, &k1, ent, NULL);\n+\tif (!w1)\n+\t\tBUG(\"Double remove of watch for '%s'\", w->dir);\n+\n+\tif (w1->cookie)\n+\t\tBUG(\"Removing watch for '%s' which has a pending rename\", w1->dir);\n+\n+\thashmap_entry_init(&k2.ent, memhash(w->dir, strlen(w->dir)));\n+\tw2 = hashmap_remove_entry(&data->revwatches, &k2, ent, NULL);\n+\tif (!w2)\n+\t\tBUG(\"Double remove of reverse watch for '%s'\", w->dir);\n+\n+\t/* w1->dir and w2->dir are interned strings, we don't own them */\n+\tfree(w1);\n+\tfree(w2);\n+}\n+\n+/*\n+ * Check for stale directory renames.\n+ *\n+ * https://man7.org/linux/man-pages/man7/inotify.7.html\n+ *\n+ * Allow for some small timeout to account for the fact that insertion of the\n+ * IN_MOVED_FROM+IN_MOVED_TO event pair is not atomic, and the possibility that\n+ * there may not be any IN_MOVED_TO event.\n+ *\n+ * If the IN_MOVED_TO event is not received within the timeout then events have\n+ * been missed and the monitor is in an inconsistent state with respect to the\n+ * filesystem.\n+ */\n+static int check_stale_dir_renames(struct hashmap *renames, time_t max_age)\n+{\n+\tstruct rename_entry *re;\n+\tstruct hashmap_iter iter;\n+\n+\thashmap_for_each_entry(renames, &iter, re, ent) {\n+\t\tif (re->whence <= max_age)\n+\t\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Track pending renames.\n+ *\n+ * Tracking is done via a event cookie to watch descriptor mapping.\n+ *\n+ * A rename is not complete until matching a IN_MOVED_TO event is received\n+ * for a corresponding IN_MOVED_FROM event.\n+ */\n+static void add_dir_rename(uint32_t cookie, const char *path,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct watch_entry k, *w;\n+\tstruct rename_entry *re;\n+\n+\t/* lookup the watch descriptor for the given path */\n+\thashmap_entry_init(&k.ent, memhash(path, strlen(path)));\n+\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n+\tif (!w) /* should never happen */\n+\t\tBUG(\"No watch for '%s'\", path);\n+\tw->cookie = cookie;\n+\n+\t/* add the pending rename to match against later */\n+\tCALLOC_ARRAY(re, 1);\n+\tre->dir = w->dir;\n+\tre->cookie = w->cookie;\n+\tre->whence = time(NULL);\n+\thashmap_entry_init(&re->ent, memhash(&re->cookie, sizeof(uint32_t)));\n+\thashmap_add(&data->renames, &re->ent);\n+}\n+\n+/*\n+ * Handle directory renames\n+ *\n+ * Once a IN_MOVED_TO event is received, lookup the rename tracking information\n+ * via the event cookie and use this information to update the watch.\n+ */\n+static void rename_dir(uint32_t cookie, const char *path,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct rename_entry rek, *re;\n+\tstruct watch_entry k, *w;\n+\n+\t/* lookup a pending rename to match */\n+\trek.cookie = cookie;\n+\thashmap_entry_init(&rek.ent, memhash(&rek.cookie, sizeof(uint32_t)));\n+\tre = hashmap_get_entry(&data->renames, &rek, ent, NULL);\n+\tif (re) {\n+\t\tk.dir = re->dir;\n+\t\thashmap_entry_init(&k.ent, memhash(k.dir, strlen(k.dir)));\n+\t\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n+\t\tif (w) {\n+\t\t\tw->cookie = 0; /* rename handled */\n+\t\t\tremove_watch(w, data);\n+\t\t\tadd_watch(path, data);\n+\t\t} else {\n+\t\t\tBUG(\"No matching watch\");\n+\t\t}\n+\t} else {\n+\t\tBUG(\"No matching cookie\");\n+\t}\n+}\n+\n+/*\n+ * Recursively add watches to every directory under path\n+ */\n+static int register_inotify(const char *path,\n+\tstruct fsmonitor_daemon_state *state,\n+\tstruct fsmonitor_batch *batch)\n+{\n+\tDIR *dir;\n+\tconst char *rel;\n+\tstruct strbuf current = STRBUF_INIT;\n+\tstruct dirent *de;\n+\tstruct stat fs;\n+\tint ret = -1;\n+\n+\tdir = opendir(path);\n+\tif (!dir)\n+\t\treturn error_errno(\"opendir('%s') failed\", path);\n+\n+\twhile ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {\n+\t\tstrbuf_reset(&current);\n+\t\tstrbuf_addf(&current, \"%s/%s\", path, de->d_name);\n+\t\tif (lstat(current.buf, &fs)) {\n+\t\t\terror_errno(\"lstat('%s') failed\", current.buf);\n+\t\t\tgoto failed;\n+\t\t}\n+\n+\t\t/* recurse into directory */\n+\t\tif (S_ISDIR(fs.st_mode)) {\n+\t\t\tif (add_watch(current.buf, state->listen_data))\n+\t\t\t\tgoto failed;\n+\t\t\tif (register_inotify(current.buf, state, batch))\n+\t\t\t\tgoto failed;\n+\t\t} else if (batch) {\n+\t\t\trel = current.buf + state->path_worktree_watch.len + 1;\n+\t\t\ttrace_printf_key(&trace_fsmonitor, \"explicitly adding '%s'\", rel);\n+\t\t\tfsmonitor_batch__add_path(batch, rel);\n+\t\t}\n+\t}\n+\tret = 0;\n+\n+failed:\n+\tstrbuf_release(&current);\n+\tif (closedir(dir) < 0)\n+\t\treturn error_errno(\"closedir('%s') failed\", path);\n+\treturn ret;\n+}\n+\n+static int em_rename_dir_from(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVED_FROM));\n+}\n+\n+static int em_rename_dir_to(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVED_TO));\n+}\n+\n+static int em_remove_watch(u_int32_t mask)\n+{\n+\treturn (mask & IN_DELETE_SELF);\n+}\n+\n+static int em_dir_renamed(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVE));\n+}\n+\n+static int em_dir_created(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_CREATE));\n+}\n+\n+static int em_dir_deleted(uint32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_DELETE));\n+}\n+\n+static int em_force_shutdown(u_int32_t mask)\n+{\n+\treturn (mask & IN_UNMOUNT) || (mask & IN_Q_OVERFLOW);\n+}\n+\n+static int em_ignore(u_int32_t mask)\n+{\n+\treturn (mask & IN_IGNORED) || (mask & IN_MOVE_SELF);\n+}\n+\n+static void log_mask_set(const char *path, u_int32_t mask)\n+{\n+\tstruct strbuf msg = STRBUF_INIT;\n+\n+\tif (mask & IN_ACCESS)\n+\t\tstrbuf_addstr(&msg, \"IN_ACCESS|\");\n+\tif (mask & IN_MODIFY)\n+\t\tstrbuf_addstr(&msg, \"IN_MODIFY|\");\n+\tif (mask & IN_ATTRIB)\n+\t\tstrbuf_addstr(&msg, \"IN_ATTRIB|\");\n+\tif (mask & IN_CLOSE_WRITE)\n+\t\tstrbuf_addstr(&msg, \"IN_CLOSE_WRITE|\");\n+\tif (mask & IN_CLOSE_NOWRITE)\n+\t\tstrbuf_addstr(&msg, \"IN_CLOSE_NOWRITE|\");\n+\tif (mask & IN_OPEN)\n+\t\tstrbuf_addstr(&msg, \"IN_OPEN|\");\n+\tif (mask & IN_MOVED_FROM)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVED_FROM|\");\n+\tif (mask & IN_MOVED_TO)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVED_TO|\");\n+\tif (mask & IN_CREATE)\n+\t\tstrbuf_addstr(&msg, \"IN_CREATE|\");\n+\tif (mask & IN_DELETE)\n+\t\tstrbuf_addstr(&msg, \"IN_DELETE|\");\n+\tif (mask & IN_DELETE_SELF)\n+\t\tstrbuf_addstr(&msg, \"IN_DELETE_SELF|\");\n+\tif (mask & IN_MOVE_SELF)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVE_SELF|\");\n+\tif (mask & IN_UNMOUNT)\n+\t\tstrbuf_addstr(&msg, \"IN_UNMOUNT|\");\n+\tif (mask & IN_Q_OVERFLOW)\n+\t\tstrbuf_addstr(&msg, \"IN_Q_OVERFLOW|\");\n+\tif (mask & IN_IGNORED)\n+\t\tstrbuf_addstr(&msg, \"IN_IGNORED|\");\n+\tif (mask & IN_ISDIR)\n+\t\tstrbuf_addstr(&msg, \"IN_ISDIR|\");\n+\n+\ttrace_printf_key(&trace_fsmonitor, \"inotify_event: '%s', mask=%#8.8x %s\",\n+\t\t\t\tpath, mask, msg.buf);\n+\n+\tstrbuf_release(&msg);\n+}\n+\n+int fsm_listen__ctor(struct fsmonitor_daemon_state *state)\n+{\n+\tint fd;\n+\tint ret = 0;\n+\tstruct fsm_listen_data *data;\n+\n+\tCALLOC_ARRAY(data, 1);\n+\tstate->listen_data = data;\n+\tstate->listen_error_code = -1;\n+\tdata->shutdown = SHUTDOWN_ERROR;\n+\n+\tfd = inotify_init1(O_NONBLOCK);\n+\tif (fd < 0)\n+\t\treturn error_errno(\"inotify_init1() failed\");\n+\n+\tdata->fd_inotify = fd;\n+\n+\thashmap_init(&data->watches, watch_entry_cmp, NULL, 0);\n+\thashmap_init(&data->renames, rename_entry_cmp, NULL, 0);\n+\thashmap_init(&data->revwatches, revwatches_entry_cmp, NULL, 0);\n+\n+\tif (add_watch(state->path_worktree_watch.buf, data))\n+\t\tret = -1;\n+\telse if (register_inotify(state->path_worktree_watch.buf, state, NULL))\n+\t\tret = -1;\n+\telse if (state->nr_paths_watching > 1) {\n+\t\tif (add_watch(state->path_gitdir_watch.buf, data))\n+\t\t\tret = -1;\n+\t\telse if (register_inotify(state->path_gitdir_watch.buf, state, NULL))\n+\t\t\tret = -1;\n+\t}\n+\n+\tif (!ret) {\n+\t\tstate->listen_error_code = 0;\n+\t\tdata->shutdown = SHUTDOWN_CONTINUE;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+void fsm_listen__dtor(struct fsmonitor_daemon_state *state)\n+{\n+\tstruct fsm_listen_data *data;\n+\tstruct hashmap_iter iter;\n+\tstruct watch_entry *w;\n+\tint fd;\n+\n+\tif (!state || !state->listen_data)\n+\t\treturn;\n+\n+\tdata = state->listen_data;\n+\tfd = data->fd_inotify;\n+\n+\thashmap_for_each_entry(&data->watches, &iter, w, ent) {\n+\t\tw->cookie = 0; /* ignore any pending renames */\n+\t\tremove_watch(w, data);\n+\t}\n+\thashmap_clear(&data->watches);\n+\n+\thashmap_clear(&data->revwatches); /* remove_watch freed the entries */\n+\n+\thashmap_clear_and_free(&data->renames, struct rename_entry, ent);\n+\n+\tFREE_AND_NULL(state->listen_data);\n+\n+\tif (fd && (close(fd) < 0))\n+\t\terror_errno(_(\"closing inotify file descriptor failed\"));\n+}\n+\n+void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)\n+{\n+\tif (!state->listen_data->shutdown)\n+\t\tstate->listen_data->shutdown = SHUTDOWN_STOP;\n+}\n+\n+/*\n+ * Process a single inotify event and queue for publication.\n+ */\n+static int process_event(const char *path,\n+\tconst struct inotify_event *event,\n+\tstruct fsmonitor_batch *batch,\n+\tstruct string_list *cookie_list,\n+\tstruct fsmonitor_daemon_state *state)\n+{\n+\tconst char *rel;\n+\tconst char *last_sep;\n+\n+\tswitch (fsmonitor_classify_path_absolute(state, path)) {\n+\t\tcase IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:\n+\t\tcase IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:\n+\t\t\t/* Use just the filename of the cookie file. */\n+\t\t\tlast_sep = find_last_dir_sep(path);\n+\t\t\tstring_list_append(cookie_list,\n+\t\t\t\t\tlast_sep ? last_sep + 1 : path);\n+\t\t\tbreak;\n+\t\tcase IS_INSIDE_DOT_GIT:\n+\t\tcase IS_INSIDE_GITDIR:\n+\t\t\tbreak;\n+\t\tcase IS_DOT_GIT:\n+\t\tcase IS_GITDIR:\n+\t\t\t/*\n+\t\t\t* If .git directory is deleted or renamed away,\n+\t\t\t* we have to quit.\n+\t\t\t*/\n+\t\t\tif (em_dir_deleted(event->mask)) {\n+\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\"event: gitdir removed\");\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\tif (em_dir_renamed(event->mask)) {\n+\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\"event: gitdir renamed\");\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase IS_WORKDIR_PATH:\n+\t\t\t/* normal events in the working directory */\n+\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n+\t\t\t\tlog_mask_set(path, event->mask);\n+\n+\t\t\trel = path + state->path_worktree_watch.len + 1;\n+\t\t\tfsmonitor_batch__add_path(batch, rel);\n+\n+\t\t\tif (em_dir_deleted(event->mask))\n+\t\t\t\tbreak;\n+\n+\t\t\t/* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */\n+\t\t\tif (em_rename_dir_from(event->mask))\n+\t\t\t\tadd_dir_rename(event->cookie, path, state->listen_data);\n+\n+\t\t\t/* received IN_MOVE_TO, update watch to reflect new path */\n+\t\t\tif (em_rename_dir_to(event->mask)) {\n+\t\t\t\trename_dir(event->cookie, path, state->listen_data);\n+\t\t\t\tif (register_inotify(path, state, batch)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (em_dir_created(event->mask)) {\n+\t\t\t\tif (add_watch(path, state->listen_data)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t\tif (register_inotify(path, state, batch)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase IS_OUTSIDE_CONE:\n+\t\tdefault:\n+\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\"ignoring '%s'\", path);\n+\t\t\tbreak;\n+\t}\n+\treturn 0;\n+done:\n+\treturn -1;\n+}\n+\n+/*\n+ * Read the inotify event stream and pre-process events before further\n+ * processing and eventual publishing.\n+ */\n+static void handle_events(struct fsmonitor_daemon_state *state)\n+{\n+\t /* See https://man7.org/linux/man-pages/man7/inotify.7.html */\n+\tchar buf[4096]\n+\t\t__attribute__ ((aligned(__alignof__(struct inotify_event))));\n+\n+\tstruct hashmap watches = state->listen_data->watches;\n+\tstruct fsmonitor_batch *batch = NULL;\n+\tstruct string_list cookie_list = STRING_LIST_INIT_DUP;\n+\tstruct watch_entry k, *w;\n+\tstruct strbuf path;\n+\tconst struct inotify_event *event;\n+\tint fd = state->listen_data->fd_inotify;\n+\tssize_t len;\n+\tchar *ptr, *p;\n+\n+\tstrbuf_init(&path, PATH_MAX);\n+\n+\tfor(;;) {\n+\t\tlen = read(fd, buf, sizeof(buf));\n+\t\tif (len == -1 && errno != EAGAIN) {\n+\t\t\terror_errno(_(\"reading inotify message stream failed\"));\n+\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\t/* nothing to read */\n+\t\tif (len <= 0)\n+\t\t\tgoto done;\n+\n+\t\t/* Loop over all events in the buffer. */\n+\t\tfor (ptr = buf; ptr < buf + len;\n+\t\t\t ptr += sizeof(struct inotify_event) + event->len) {\n+\n+\t\t\tevent = (const struct inotify_event *) ptr;\n+\n+\t\t\tif (em_ignore(event->mask))\n+\t\t\t\tcontinue;\n+\n+\t\t\t/* File system was unmounted or event queue overflowed */\n+\t\t\tif (em_force_shutdown(event->mask)) {\n+\t\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n+\t\t\t\t\tlog_mask_set(\"Forcing shutdown\", event->mask);\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\thashmap_entry_init(&k.ent, memhash(&event->wd, sizeof(int)));\n+\t\t\tk.wd = event->wd;\n+\n+\t\t\tw = hashmap_get_entry(&watches, &k, ent, NULL);\n+\t\t\tif (!w) /* should never happen */\n+\t\t\t\tBUG(\"No watch for '%s'\", event->name);\n+\n+\t\t\t/* directory watch was removed */\n+\t\t\tif (em_remove_watch(event->mask)) {\n+\t\t\t\tremove_watch(w, state->listen_data);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tstrbuf_reset(&path);\n+\t\t\tstrbuf_add(&path, w->dir, strlen(w->dir));\n+\t\t\tstrbuf_addch(&path, '/');\n+\t\t\tstrbuf_addstr(&path, event->name);\n+\n+\t\t\tp = fsmonitor__resolve_alias(path.buf, &state->alias);\n+\t\t\tif (!p)\n+\t\t\t\tp = strbuf_detach(&path, NULL);\n+\n+\t\t\tif (!batch)\n+\t\t\t\tbatch = fsmonitor_batch__new();\n+\n+\t\t\tif (process_event(p, event, batch, &cookie_list, state)) {\n+\t\t\t\tfree(p);\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tfree(p);\n+\t\t}\n+\t\tstrbuf_reset(&path);\n+\t\tfsmonitor_publish(state, batch, &cookie_list);\n+\t\tstring_list_clear(&cookie_list, 0);\n+\t\tbatch = NULL;\n+\t}\n+done:\n+\tstrbuf_release(&path);\n+\tfsmonitor_batch__free_list(batch);\n+\tstring_list_clear(&cookie_list, 0);\n+}\n+\n+/*\n+ * Non-blocking read of the inotify events stream. The inotify fd is polled\n+ * frequently to help minimize the number of queue overflows.\n+ */\n+void fsm_listen__loop(struct fsmonitor_daemon_state *state)\n+{\n+\tint poll_num;\n+\tconst int interval = 1000;\n+\ttime_t checked = time(NULL);\n+\tstruct pollfd fds[1];\n+\tfds[0].fd = state->listen_data->fd_inotify;\n+\tfds[0].events = POLLIN;\n+\n+\tfor(;;) {\n+\t\tswitch (state->listen_data->shutdown) {\n+\t\t\tcase SHUTDOWN_CONTINUE:\n+\t\t\t\tpoll_num = poll(fds, 1, 1);\n+\t\t\t\tif (poll_num == -1) {\n+\t\t\t\t\tif (errno == EINTR)\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\terror_errno(_(\"polling inotify message stream failed\"));\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif ((time(NULL) - checked) >= interval) {\n+\t\t\t\t\tchecked = time(NULL);\n+\t\t\t\t\tif (check_stale_dir_renames(&state->listen_data->renames,\n+\t\t\t\t\t\tchecked - interval)) {\n+\t\t\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\t\"Missed IN_MOVED_TO events, forcing shutdown\");\n+\t\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\n+\t\t\t\tif (poll_num > 0 && (fds[0].revents & POLLIN))\n+\t\t\t\t\thandle_events(state);\n+\n+\t\t\t\tcontinue;\n+\t\t\tcase SHUTDOWN_ERROR:\n+\t\t\t\tstate->listen_error_code = -1;\n+\t\t\t\tipc_server_stop_async(state->ipc_server_data);\n+\t\t\t\tbreak;\n+\t\t\tcase SHUTDOWN_FORCE:\n+\t\t\t\tstate->listen_error_code = 0;\n+\t\t\t\tipc_server_stop_async(state->ipc_server_data);\n+\t\t\t\tbreak;\n+\t\t\tcase SHUTDOWN_STOP:\n+\t\t\tdefault:\n+\t\t\t\tstate->listen_error_code = 0;\n+\t\t\t\tbreak;\n+\t\t}\n+\t\treturn;\n+\t}\n+}\n-- \ngitgitgadget\n\n"},{"id":"467406","messageId":"05f5b2dd4fb9656689f01ae0c39c89a8fcb0f796.1668641019.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v3.git.git.1668641019.gitgitgadget@gmail.com","subject":"[PATCH v3 4/6] fsmonitor: enable fsmonitor for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-16T23:23:37Z","receivedAt":"2022-11-16T23:24:00Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nUpdate build to enable fsmonitor for Linux.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n config.mak.uname                    |  8 ++++++++\n contrib/buildsystems/CMakeLists.txt | 11 ++++++++++-\n 2 files changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/config.mak.uname b/config.mak.uname\nindex d63629fe807..5890d810463 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -68,6 +68,14 @@ ifeq ($(uname_S),Linux)\n \tifneq ($(findstring .el7.,$(uname_R)),)\n \t\tBASIC_CFLAGS += -std=c99\n \tendif\n+\t# The builtin FSMonitor on Linux builds upon Simple-IPC.  Both require\n+\t# Unix domain sockets and PThreads.\n+\tifndef NO_PTHREADS\n+\tifndef NO_UNIX_SOCKETS\n+\tFSMONITOR_DAEMON_BACKEND = linux\n+\tFSMONITOR_OS_SETTINGS = linux\n+\tendif\n+\tendif\n endif\n ifeq ($(uname_S),GNU/kFreeBSD)\n \tHAVE_ALLOCA_H = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 3957e4cf8cd..f058c3d19b4 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -304,7 +304,16 @@ else()\n endif()\n \n if(SUPPORTS_SIMPLE_IPC)\n-\tif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n+\t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-linux.c)\n+\n+\t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-linux.c)\n+\telseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n-- \ngitgitgadget\n\n"},{"id":"467407","messageId":"754355ca44f56823a4dad8138228b446b39a420c.1668641019.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v3.git.git.1668641019.gitgitgadget@gmail.com","subject":"[PATCH v3 5/6] fsmonitor: test updates","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-16T23:23:38Z","receivedAt":"2022-11-16T23:24:02Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nt7527-builtin-fsmonitor was leaking fsmonitor--daemon processes in some\ncases.\n\nAccomodate slight difference in the number of events generated on Linux.\n\nOn lower-powered systems, spin a little to give the daemon time\nto respond to and log filesystem events.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n t/t7527-builtin-fsmonitor.sh | 94 ++++++++++++++++++++++++++++++------\n 1 file changed, 80 insertions(+), 14 deletions(-)\n\ndiff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh\nindex 4abc74db2bb..951374231b7 100755\n--- a/t/t7527-builtin-fsmonitor.sh\n+++ b/t/t7527-builtin-fsmonitor.sh\n@@ -13,7 +13,7 @@ fi\n stop_daemon_delete_repo () {\n \tr=$1 &&\n \ttest_might_fail git -C $r fsmonitor--daemon stop &&\n-\trm -rf $1\n+\trm -rf $r\n }\n \n start_daemon () {\n@@ -72,6 +72,34 @@ start_daemon () {\n \t)\n }\n \n+IMPLICIT_TIMEOUT=5\n+\n+wait_for_update () {\n+\tfunc=$1 &&\n+\tfile=$2 &&\n+\tsz=$(wc -c < \"$file\") &&\n+\tlast=0 &&\n+\t$func &&\n+\tk=0 &&\n+\twhile test \"$k\" -lt $IMPLICIT_TIMEOUT\n+\tdo\n+\t\tnsz=$(wc -c < \"$file\")\n+\t\tif test \"$nsz\" -gt \"$sz\"\n+\t\tthen\n+\t\t\tif test \"$last\" -eq \"$nsz\"\n+\t\t\tthen\n+\t\t\t\tcat \"$file\" &&\n+\t\t\t\treturn 0\n+\t\t\tfi\n+\t\t\tlast=$nsz\n+\t\tfi\n+\t\tsleep 1\n+\t\tk=$(( $k + 1 ))\n+\tdone &&\n+\tcat \"$file\" &&\n+\treturn 0\n+}\n+\n # Is a Trace2 data event present with the given catetory and key?\n # We do not care what the value is.\n #\n@@ -137,7 +165,6 @@ test_expect_success 'implicit daemon start' '\n # machines (where it might take a moment to wake and reschedule the\n # daemon process) to avoid false alarms during test runs.)\n #\n-IMPLICIT_TIMEOUT=5\n \n verify_implicit_shutdown () {\n \tr=$1 &&\n@@ -373,6 +400,15 @@ create_files () {\n \techo 3 >dir2/new\n }\n \n+rename_directory () {\n+\tmv dirtorename dirrenamed\n+}\n+\n+rename_directory_file () {\n+\tmv dirtorename dirrenamed &&\n+\techo 1 > dirrenamed/new\n+}\n+\n rename_files () {\n \tmv rename renamed &&\n \tmv dir1/rename dir1/renamed &&\n@@ -427,10 +463,12 @@ test_expect_success 'edit some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tedit_files &&\n+\twait_for_update edit_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/modified$\"  .git/trace &&\n \tgrep \"^event: dir2/modified$\"  .git/trace &&\n \tgrep \"^event: modified$\"       .git/trace &&\n@@ -442,10 +480,12 @@ test_expect_success 'create some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tcreate_files &&\n+\twait_for_update create_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/new$\" .git/trace &&\n \tgrep \"^event: dir2/new$\" .git/trace &&\n \tgrep \"^event: new$\"      .git/trace\n@@ -456,10 +496,12 @@ test_expect_success 'delete some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tdelete_files &&\n+\twait_for_update delete_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/delete$\" .git/trace &&\n \tgrep \"^event: dir2/delete$\" .git/trace &&\n \tgrep \"^event: delete$\"      .git/trace\n@@ -470,10 +512,12 @@ test_expect_success 'rename some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\trename_files &&\n+\twait_for_update rename_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/rename$\"  .git/trace &&\n \tgrep \"^event: dir2/rename$\"  .git/trace &&\n \tgrep \"^event: rename$\"       .git/trace &&\n@@ -487,23 +531,42 @@ test_expect_success 'rename directory' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tmv dirtorename dirrenamed &&\n+\twait_for_update rename_directory \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dirtorename/*$\" .git/trace &&\n \tgrep \"^event: dirrenamed/*$\"  .git/trace\n '\n \n+test_expect_success 'rename directory file' '\n+\ttest_when_finished clean_up_repo_and_stop_daemon &&\n+\n+\tstart_daemon --tf \"$PWD/.git/trace\" &&\n+\n+\twait_for_update rename_directory_file \"$PWD/.git/trace\" &&\n+\n+\ttest-tool fsmonitor-client query --token 0 &&\n+\n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n+\tgrep \"^event: dirtorename/*$\" .git/trace &&\n+\tgrep \"^event: dirrenamed/*$\"  .git/trace &&\n+\tgrep \"^event: dirrenamed/new$\"  .git/trace\n+'\n test_expect_success 'file changes to directory' '\n \ttest_when_finished clean_up_repo_and_stop_daemon &&\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tfile_to_directory &&\n+\twait_for_update file_to_directory \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: delete$\"     .git/trace &&\n \tgrep \"^event: delete/new$\" .git/trace\n '\n@@ -513,10 +576,12 @@ test_expect_success 'directory changes to a file' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tdirectory_to_file &&\n+\twait_for_update directory_to_file \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1$\" .git/trace\n '\n \n@@ -561,7 +626,7 @@ test_expect_success 'flush cached data' '\n \ttest-tool -C test_flush fsmonitor-client query --token \"builtin:test_00000002:0\" >actual_2 &&\n \tnul_to_q <actual_2 >actual_q2 &&\n \n-\tgrep \"^builtin:test_00000002:0Q$\" actual_q2 &&\n+\tgrep \"^builtin:test_00000002:[0-1]Q$\" actual_q2 &&\n \n \t>test_flush/file_3 &&\n \n@@ -732,7 +797,8 @@ u_values=\"$u1 $u2\"\n for u in $u_values\n do\n \ttest_expect_success \"unicode in repo root path: $u\" '\n-\t\ttest_when_finished \"stop_daemon_delete_repo $u\" &&\n+\t\ttest_when_finished \\\n+\t\t\"stop_daemon_delete_repo `echo \"$u\" | sed 's:x:\\\\\\\\\\\\\\\\\\\\\\\\\\\\x:g'`\" &&\n \n \t\tgit init \"$u\" &&\n \t\techo 1 >\"$u\"/file1 &&\n@@ -818,8 +884,7 @@ test_expect_success 'submodule setup' '\n '\n \n test_expect_success 'submodule always visited' '\n-\ttest_when_finished \"git -C super fsmonitor--daemon stop; \\\n-\t\t\t    rm -rf super; \\\n+\ttest_when_finished \"rm -rf super; \\\n \t\t\t    rm -rf sub\" &&\n \n \tcreate_super super &&\n@@ -887,7 +952,8 @@ have_t2_error_event () {\n }\n \n test_expect_success \"stray submodule super-prefix warning\" '\n-\ttest_when_finished \"rm -rf super; \\\n+\ttest_when_finished \"git -C super/dir_1/dir_2/sub fsmonitor--daemon stop; \\\n+\t\t\t    rm -rf super; \\\n \t\t\t    rm -rf sub;   \\\n \t\t\t    rm super-sub.trace\" &&\n \n-- \ngitgitgadget\n\n"},{"id":"467408","messageId":"f56175e097a6cf08b0a5c87cd1524062437c4db0.1668641019.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v3.git.git.1668641019.gitgitgadget@gmail.com","subject":"[PATCH v3 6/6] fsmonitor: update doc for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-16T23:23:39Z","receivedAt":"2022-11-16T23:24:04Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nUpdate the documentation for Linux.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Documentation/config/fsmonitor--daemon.txt |  4 ++--\n Documentation/git-fsmonitor--daemon.txt    | 24 ++++++++++++++--------\n 2 files changed, 18 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config/fsmonitor--daemon.txt b/Documentation/config/fsmonitor--daemon.txt\nindex c225c6c9e74..2cafb040d96 100644\n--- a/Documentation/config/fsmonitor--daemon.txt\n+++ b/Documentation/config/fsmonitor--daemon.txt\n@@ -4,8 +4,8 @@ fsmonitor.allowRemote::\n     behavior.  Only respected when `core.fsmonitor` is set to `true`.\n \n fsmonitor.socketDir::\n-    This Mac OS-specific option, if set, specifies the directory in\n+    Mac OS and Linux-specific option. If set, specifies the directory in\n     which to create the Unix domain socket used for communication\n     between the fsmonitor daemon and various Git commands. The directory must\n-    reside on a native Mac OS filesystem.  Only respected when `core.fsmonitor`\n+    reside on a native filesystem.  Only respected when `core.fsmonitor`\n     is set to `true`.\ndiff --git a/Documentation/git-fsmonitor--daemon.txt b/Documentation/git-fsmonitor--daemon.txt\nindex 8238eadb0e1..c2b08229c74 100644\n--- a/Documentation/git-fsmonitor--daemon.txt\n+++ b/Documentation/git-fsmonitor--daemon.txt\n@@ -76,23 +76,31 @@ repositories; this may be overridden by setting `fsmonitor.allowRemote` to\n correctly with all network-mounted repositories and such use is considered\n experimental.\n \n-On Mac OS, the inter-process communication (IPC) between various Git\n+On Linux and Mac OS, the inter-process communication (IPC) between various Git\n commands and the fsmonitor daemon is done via a Unix domain socket (UDS) -- a\n-special type of file -- which is supported by native Mac OS filesystems,\n-but not on network-mounted filesystems, NTFS, or FAT32.  Other filesystems\n-may or may not have the needed support; the fsmonitor daemon is not guaranteed\n-to work with these filesystems and such use is considered experimental.\n+special type of file -- which is supported by many native Linux and Mac OS\n+filesystems, but not on network-mounted filesystems, NTFS, or FAT32.  Other\n+filesystems may or may not have the needed support; the fsmonitor daemon is not\n+guaranteed to work with these filesystems and such use is considered\n+experimental.\n \n By default, the socket is created in the `.git` directory, however, if the\n `.git` directory is on a network-mounted filesystem, it will be instead be\n created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a\n network-mounted filesystem in which case you must set the configuration\n-variable `fsmonitor.socketDir` to the path of a directory on a Mac OS native\n+variable `fsmonitor.socketDir` to the path of a directory on a native\n filesystem in which to create the socket file.\n \n If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`)\n-is on a native Mac OS file filesystem the fsmonitor daemon will report an\n-error that will cause the daemon and the currently running command to exit.\n+is on a native Linux or Mac OS filesystem the fsmonitor daemon will report\n+an error that will cause the daemon to exit and the currently running command\n+to issue a warning.\n+\n+On Linux, the fsmonitor daemon registers a watch for each directory in the\n+repository.  The default per-user limit for the number of watches on most Linux\n+systems is 8192.  This may not be sufficient for large repositories or if\n+multiple instances of the fsmonitor daemon are running.\n+See https://watchexec.github.io/docs/inotify-limits.html[Linux inotify limits] for more information.\n \n CONFIGURATION\n -------------\n-- \ngitgitgadget\n"},{"id":"467409","messageId":"Y3Vx1wWYSCzAeXjB@nand.local","threadId":"58588","inReplyTo":"pull.1352.v3.git.git.1668641019.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/6] fsmonitor: Implement fsmonitor for Linux","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-11-16T23:27:19Z","receivedAt":"2022-11-16T23:27:25Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"[+cc Jeff Hostetler]\n\nOn Wed, Nov 16, 2022 at 11:23:33PM +0000, Eric DeCosta via GitGitGadget wrote:\n> Eric DeCosta (6):\n>   fsmonitor: prepare to share code between Mac OS and Linux\n>   fsmonitor: determine if filesystem is local or remote\n>   fsmonitor: implement filesystem change listener for Linux\n>   fsmonitor: enable fsmonitor for Linux\n>   fsmonitor: test updates\n>   fsmonitor: update doc for Linux\n\nThanks, I picked these up. Let's add Jeff Hostetler (cc'd) to the chain\nand see if he has any thoughts.\n\nThanks,\nTaylor\n"},{"id":"467875","messageId":"99d684c7bdfac9db9cbf74bae1d21f9dc20ac825.1669230044.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v4.git.git.1669230044.gitgitgadget@gmail.com","subject":"[PATCH v4 1/6] fsmonitor: prepare to share code between Mac OS and Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-23T19:00:39Z","receivedAt":"2022-11-23T19:00:53Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nLinux and Mac OS can share some of the code originally developed for Mac OS.\n\nMac OS and Linux can share fsm-ipc-unix.c and fsm-settings-unix.c\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-health-linux.c    | 24 ++++++++++\n compat/fsmonitor/fsm-ipc-darwin.c      | 53 +---------------------\n compat/fsmonitor/fsm-ipc-linux.c       |  1 +\n compat/fsmonitor/fsm-ipc-unix.c        | 52 +++++++++++++++++++++\n compat/fsmonitor/fsm-settings-darwin.c | 63 +-------------------------\n compat/fsmonitor/fsm-settings-linux.c  |  1 +\n compat/fsmonitor/fsm-settings-unix.c   | 61 +++++++++++++++++++++++++\n 7 files changed, 141 insertions(+), 114 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-health-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-unix.c\n create mode 100644 compat/fsmonitor/fsm-settings-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.c\n\ndiff --git a/compat/fsmonitor/fsm-health-linux.c b/compat/fsmonitor/fsm-health-linux.c\nnew file mode 100644\nindex 00000000000..b9f709e8548\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-health-linux.c\n@@ -0,0 +1,24 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsm-health.h\"\n+#include \"fsmonitor--daemon.h\"\n+\n+int fsm_health__ctor(struct fsmonitor_daemon_state *state)\n+{\n+\treturn 0;\n+}\n+\n+void fsm_health__dtor(struct fsmonitor_daemon_state *state)\n+{\n+\treturn;\n+}\n+\n+void fsm_health__loop(struct fsmonitor_daemon_state *state)\n+{\n+\treturn;\n+}\n+\n+void fsm_health__stop_async(struct fsmonitor_daemon_state *state)\n+{\n+}\ndiff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-darwin.c\nindex d67b0ee50d3..4c3c92081ee 100644\n--- a/compat/fsmonitor/fsm-ipc-darwin.c\n+++ b/compat/fsmonitor/fsm-ipc-darwin.c\n@@ -1,52 +1 @@\n-#include \"cache.h\"\n-#include \"config.h\"\n-#include \"strbuf.h\"\n-#include \"fsmonitor.h\"\n-#include \"fsmonitor-ipc.h\"\n-#include \"fsmonitor-path-utils.h\"\n-\n-static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n-\n-const char *fsmonitor_ipc__get_path(struct repository *r)\n-{\n-\tstatic const char *ipc_path = NULL;\n-\tgit_SHA_CTX sha1ctx;\n-\tchar *sock_dir = NULL;\n-\tstruct strbuf ipc_file = STRBUF_INIT;\n-\tunsigned char hash[GIT_MAX_RAWSZ];\n-\n-\tif (!r)\n-\t\tBUG(\"No repository passed into fsmonitor_ipc__get_path\");\n-\n-\tif (ipc_path)\n-\t\treturn ipc_path;\n-\n-\n-\t/* By default the socket file is created in the .git directory */\n-\tif (fsmonitor__is_fs_remote(r->gitdir) < 1) {\n-\t\tipc_path = fsmonitor_ipc__get_default_path();\n-\t\treturn ipc_path;\n-\t}\n-\n-\tgit_SHA1_Init(&sha1ctx);\n-\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n-\tgit_SHA1_Final(hash, &sha1ctx);\n-\n-\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n-\n-\t/* Create the socket file in either socketDir or $HOME */\n-\tif (sock_dir && *sock_dir) {\n-\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n-\t\t\t\t\tsock_dir, hash_to_hex(hash));\n-\t} else {\n-\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n-\t}\n-\tfree(sock_dir);\n-\n-\tipc_path = interpolate_path(ipc_file.buf, 1);\n-\tif (!ipc_path)\n-\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n-\n-\tstrbuf_release(&ipc_file);\n-\treturn ipc_path;\n-}\n+#include \"fsm-ipc-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-ipc-linux.c b/compat/fsmonitor/fsm-ipc-linux.c\nnew file mode 100644\nindex 00000000000..4c3c92081ee\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-ipc-linux.c\n@@ -0,0 +1 @@\n+#include \"fsm-ipc-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-ipc-unix.c b/compat/fsmonitor/fsm-ipc-unix.c\nnew file mode 100644\nindex 00000000000..d67b0ee50d3\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-ipc-unix.c\n@@ -0,0 +1,52 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"strbuf.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n+\n+static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n+\n+const char *fsmonitor_ipc__get_path(struct repository *r)\n+{\n+\tstatic const char *ipc_path = NULL;\n+\tgit_SHA_CTX sha1ctx;\n+\tchar *sock_dir = NULL;\n+\tstruct strbuf ipc_file = STRBUF_INIT;\n+\tunsigned char hash[GIT_MAX_RAWSZ];\n+\n+\tif (!r)\n+\t\tBUG(\"No repository passed into fsmonitor_ipc__get_path\");\n+\n+\tif (ipc_path)\n+\t\treturn ipc_path;\n+\n+\n+\t/* By default the socket file is created in the .git directory */\n+\tif (fsmonitor__is_fs_remote(r->gitdir) < 1) {\n+\t\tipc_path = fsmonitor_ipc__get_default_path();\n+\t\treturn ipc_path;\n+\t}\n+\n+\tgit_SHA1_Init(&sha1ctx);\n+\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n+\tgit_SHA1_Final(hash, &sha1ctx);\n+\n+\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n+\n+\t/* Create the socket file in either socketDir or $HOME */\n+\tif (sock_dir && *sock_dir) {\n+\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n+\t\t\t\t\tsock_dir, hash_to_hex(hash));\n+\t} else {\n+\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n+\t}\n+\tfree(sock_dir);\n+\n+\tipc_path = interpolate_path(ipc_file.buf, 1);\n+\tif (!ipc_path)\n+\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n+\n+\tstrbuf_release(&ipc_file);\n+\treturn ipc_path;\n+}\ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c\nindex 6abbc7af3ab..14baf9f0603 100644\n--- a/compat/fsmonitor/fsm-settings-darwin.c\n+++ b/compat/fsmonitor/fsm-settings-darwin.c\n@@ -1,62 +1 @@\n-#include \"config.h\"\n-#include \"fsmonitor.h\"\n-#include \"fsmonitor-ipc.h\"\n-#include \"fsmonitor-settings.h\"\n-#include \"fsmonitor-path-utils.h\"\n-\n- /*\n- * For the builtin FSMonitor, we create the Unix domain socket for the\n- * IPC in the .git directory.  If the working directory is remote,\n- * then the socket will be created on the remote file system.  This\n- * can fail if the remote file system does not support UDS file types\n- * (e.g. smbfs to a Windows server) or if the remote kernel does not\n- * allow a non-local process to bind() the socket.  (These problems\n- * could be fixed by moving the UDS out of the .git directory and to a\n- * well-known local directory on the client machine, but care should\n- * be taken to ensure that $HOME is actually local and not a managed\n- * file share.)\n- *\n- * FAT32 and NTFS working directories are problematic too.\n- *\n- * The builtin FSMonitor uses a Unix domain socket in the .git\n- * directory for IPC.  These Windows drive formats do not support\n- * Unix domain sockets, so mark them as incompatible for the daemon.\n- *\n- */\n-static enum fsmonitor_reason check_uds_volume(struct repository *r)\n-{\n-\tstruct fs_info fs;\n-\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n-\tstruct strbuf path = STRBUF_INIT;\n-\tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n-\n-\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n-\t\tstrbuf_release(&path);\n-\t\treturn FSMONITOR_REASON_ERROR;\n-\t}\n-\n-\tstrbuf_release(&path);\n-\n-\tif (fs.is_remote ||\n-\t\t!strcmp(fs.typename, \"msdos\") ||\n-\t\t!strcmp(fs.typename, \"ntfs\")) {\n-\t\tfree(fs.typename);\n-\t\treturn FSMONITOR_REASON_NOSOCKETS;\n-\t}\n-\n-\tfree(fs.typename);\n-\treturn FSMONITOR_REASON_OK;\n-}\n-\n-enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n-{\n-\tenum fsmonitor_reason reason;\n-\n-\tif (ipc) {\n-\t\treason = check_uds_volume(r);\n-\t\tif (reason != FSMONITOR_REASON_OK)\n-\t\t\treturn reason;\n-\t}\n-\n-\treturn FSMONITOR_REASON_OK;\n-}\n+#include \"fsm-settings-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-settings-linux.c b/compat/fsmonitor/fsm-settings-linux.c\nnew file mode 100644\nindex 00000000000..14baf9f0603\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-settings-linux.c\n@@ -0,0 +1 @@\n+#include \"fsm-settings-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-settings-unix.c b/compat/fsmonitor/fsm-settings-unix.c\nnew file mode 100644\nindex 00000000000..a6ed32575a9\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-settings-unix.c\n@@ -0,0 +1,61 @@\n+#include \"config.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n+\n+ /*\n+ * For the builtin FSMonitor, we create the Unix domain socket for the\n+ * IPC in the .git directory.  If the working directory is remote,\n+ * then the socket will be created on the remote file system.  This\n+ * can fail if the remote file system does not support UDS file types\n+ * (e.g. smbfs to a Windows server) or if the remote kernel does not\n+ * allow a non-local process to bind() the socket.  (These problems\n+ * could be fixed by moving the UDS out of the .git directory and to a\n+ * well-known local directory on the client machine, but care should\n+ * be taken to ensure that $HOME is actually local and not a managed\n+ * file share.)\n+ *\n+ * FAT32 and NTFS working directories are problematic too.\n+ *\n+ * The builtin FSMonitor uses a Unix domain socket in the .git\n+ * directory for IPC.  These Windows drive formats do not support\n+ * Unix domain sockets, so mark them as incompatible for the daemon.\n+ *\n+ */\n+static enum fsmonitor_reason check_uds_volume(struct repository *r)\n+{\n+\tstruct fs_info fs;\n+\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstrbuf_addstr(&path, ipc_path);\n+\n+\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n+\t\tstrbuf_release(&path);\n+\t\treturn FSMONITOR_REASON_ERROR;\n+\t}\n+\n+\tstrbuf_release(&path);\n+\n+\tif (fs.is_remote ||\n+\t\t!strcmp(fs.typename, \"msdos\") ||\n+\t\t!strcmp(fs.typename, \"ntfs\")) {\n+\t\tfree(fs.typename);\n+\t\treturn FSMONITOR_REASON_NOSOCKETS;\n+\t}\n+\n+\tfree(fs.typename);\n+\treturn FSMONITOR_REASON_OK;\n+}\n+\n+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n+{\n+\tenum fsmonitor_reason reason;\n+\n+\tif (ipc) {\n+\t\treason = check_uds_volume(r);\n+\t\tif (reason != FSMONITOR_REASON_OK)\n+\t\t\treturn reason;\n+\t}\n+\n+\treturn FSMONITOR_REASON_OK;\n+}\n-- \ngitgitgadget\n\n"},{"id":"467876","messageId":"pull.1352.v4.git.git.1669230044.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v3.git.git.1668641019.gitgitgadget@gmail.com","subject":"[PATCH v4 0/6] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-23T19:00:38Z","receivedAt":"2022-11-23T19:00:54Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\nWindows and Mac OS.\n\nThis patch set builds upon previous work for done for Windows and Mac OS to\nimplement a fsmonitor back-end for Linux based on the Linux inotify API.\ninotify differs significantly from the equivalent Windows and Mac OS APIs in\nthat a watch must be registered for every directory of interest (rather than\na singular watch at the root of the directory tree) and special care must be\ntaken to handle directory renames correctly.\n\nMore information about inotify:\nhttps://man7.org/linux/man-pages/man7/inotify.7.html\n\nv3 differs from v2:\n\n * Avoid potential entanglements with GPLv3\n * Classify a reasonable set of filesystems as being remote\n\nv2 differs from v1:\n\n * Prior work for Windows and Mac OS has been merged to master, reducing the\n   patch set from 12 to 6 patches\n * Code review feedback\n * Identified and resolved race condition revealed by CI test system, see\n   \"Limitations and caveats\" regarding monitoring of directory trees from\n   the man page, above\n * Apologies for being away from this for so long, but my attention was\n   needed elsewhere\n\nv1 differs from v0:\n\n * Code review feedback\n * Update how and which code can be shared between Mac OS and Linux\n * Increase polling frequency to every 1ms (matches Mac OS)\n * Updates to t7527 to improve test stability\n\nEric DeCosta (6):\n  fsmonitor: prepare to share code between Mac OS and Linux\n  fsmonitor: determine if filesystem is local or remote\n  fsmonitor: implement filesystem change listener for Linux\n  fsmonitor: enable fsmonitor for Linux\n  fsmonitor: test updates\n  fsmonitor: update doc for Linux\n\n Documentation/config/fsmonitor--daemon.txt |   4 +-\n Documentation/git-fsmonitor--daemon.txt    |  24 +-\n compat/fsmonitor/fsm-health-linux.c        |  24 +\n compat/fsmonitor/fsm-ipc-darwin.c          |  53 +-\n compat/fsmonitor/fsm-ipc-linux.c           |   1 +\n compat/fsmonitor/fsm-ipc-unix.c            |  52 ++\n compat/fsmonitor/fsm-listen-linux.c        | 676 +++++++++++++++++++++\n compat/fsmonitor/fsm-path-utils-linux.c    | 186 ++++++\n compat/fsmonitor/fsm-settings-darwin.c     |  63 +-\n compat/fsmonitor/fsm-settings-linux.c      |   1 +\n compat/fsmonitor/fsm-settings-unix.c       |  61 ++\n config.mak.uname                           |   8 +\n contrib/buildsystems/CMakeLists.txt        |  11 +-\n t/t7527-builtin-fsmonitor.sh               |  94 ++-\n 14 files changed, 1119 insertions(+), 139 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-health-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-unix.c\n create mode 100644 compat/fsmonitor/fsm-listen-linux.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.c\n\n\nbase-commit: 319605f8f00e402f3ea758a02c63534ff800a711\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1352%2Fedecosta-mw%2Ffsmonitor_linux-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1352/edecosta-mw/fsmonitor_linux-v4\nPull-Request: https://github.com/git/git/pull/1352\n\nRange-diff vs v3:\n\n 1:  99d684c7bdf = 1:  99d684c7bdf fsmonitor: prepare to share code between Mac OS and Linux\n 2:  aa405379891 ! 2:  e53fc077540 fsmonitor: determine if filesystem is local or remote\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +#include <errno.h>\n      +#include <mntent.h>\n      +#include <sys/mount.h>\n     ++#include <sys/vfs.h>\n      +#include <sys/statvfs.h>\n      +\n     -+/*\n     -+ * https://github.com/coreutils/gnulib/blob/master/lib/mountlist.c\n     -+ */\n     -+#ifndef ME_REMOTE\n     -+/* A file system is \"remote\" if its Fs_name contains a ':'\n     -+   or if (it is of type (smbfs or cifs) and its Fs_name starts with '//')\n     -+   or if it is of any other of the listed types\n     -+   or Fs_name is equal to \"-hosts\" (used by autofs to mount remote fs).\n     -+   \"VM\" file systems like prl_fs or vboxsf are not considered remote here. */\n     -+# define ME_REMOTE(Fs_name, Fs_type)            \\\n     -+\t(strchr (Fs_name, ':') != NULL              \\\n     -+\t || ((Fs_name)[0] == '/'                    \\\n     -+\t\t && (Fs_name)[1] == '/'                 \\\n     -+\t\t && (strcmp (Fs_type, \"smbfs\") == 0     \\\n     -+\t\t\t || strcmp (Fs_type, \"smb3\") == 0   \\\n     -+\t\t\t || strcmp (Fs_type, \"cifs\") == 0)) \\\n     -+\t || strcmp (Fs_type, \"acfs\") == 0           \\\n     -+\t || strcmp (Fs_type, \"afs\") == 0            \\\n     -+\t || strcmp (Fs_type, \"coda\") == 0           \\\n     -+\t || strcmp (Fs_type, \"auristorfs\") == 0     \\\n     -+\t || strcmp (Fs_type, \"fhgfs\") == 0          \\\n     -+\t || strcmp (Fs_type, \"gpfs\") == 0           \\\n     -+\t || strcmp (Fs_type, \"ibrix\") == 0          \\\n     -+\t || strcmp (Fs_type, \"ocfs2\") == 0          \\\n     -+\t || strcmp (Fs_type, \"vxfs\") == 0           \\\n     -+\t || strcmp (\"-hosts\", Fs_name) == 0)\n     -+#endif\n     ++static int is_remote_fs(const char* path) {\n     ++\tstruct statfs fs;\n     ++\n     ++\tif (statfs(path, &fs)) {\n     ++\t\terror_errno(_(\"statfs('%s') failed\"), path);\n     ++\t\treturn -1;\n     ++\t}\n     ++\n     ++\tswitch (fs.f_type) {\n     ++\t\tcase 0x61636673:  /* ACFS */\n     ++\t\tcase 0x5346414F:  /* AFS */\n     ++\t\tcase 0x00C36400:  /* CEPH */\n     ++\t\tcase 0xFF534D42:  /* CIFS */\n     ++\t\tcase 0x73757245:  /* CODA */\n     ++\t\tcase 0x19830326:  /* FHGFS */\n     ++\t\tcase 0x1161970:   /* GFS */\n     ++\t\tcase 0x47504653:  /* GPFS */\n     ++\t\tcase 0x013111A8:  /* IBRIX */\n     ++\t\tcase 0x6B414653:  /* KAFS */\n     ++\t\tcase 0x0BD00BD0:  /* LUSTRE */\n     ++\t\tcase 0x564C:      /* NCP */\n     ++\t\tcase 0x6969:      /* NFS */\n     ++\t\tcase 0x6E667364:  /* NFSD */\n     ++\t\tcase 0x7461636f:  /* OCFS2 */\n     ++\t\tcase 0xAAD7AAEA:  /* PANFS */\n     ++\t\tcase 0x517B:      /* SMB */\n     ++\t\tcase 0xBEEFDEAD:  /* SNFS */\n     ++\t\tcase 0xFE534D42:  /* SMB2 */\n     ++\t\tcase 0xBACBACBC:  /* VMHGFS */\n     ++\t\tcase 0xA501FCF5:  /* VXFS */\n     ++\t\t\treturn 1;\n     ++\t\tdefault:\n     ++\t\t\tbreak;\n     ++\t}\n     ++\n     ++\treturn 0;\n     ++}\n      +\n      +static int find_mount(const char *path, const struct statvfs *fs,\n      +\tstruct mntent *ent)\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n      +\t\t\t path, fs.f_flag, ment.mnt_type, ment.mnt_fsname);\n      +\n     -+\tfs_info->is_remote = ME_REMOTE(ment.mnt_fsname, ment.mnt_type);\n     ++\tfs_info->is_remote = is_remote_fs(ment.mnt_dir);\n      +\tfs_info->typename = ment.mnt_fsname;\n      +\tfree(ment.mnt_dir);\n      +\tfree(ment.mnt_type);\n      +\n     ++\tif (fs_info->is_remote < 0) {\n     ++\t\tfree(ment.mnt_fsname);\n     ++\t\treturn -1;\n     ++\t}\n     ++\n      +\ttrace_printf_key(&trace_fsmonitor,\n      +\t\t\t\t\"'%s' is_remote: %d\",\n      +\t\t\t\tpath, fs_info->is_remote);\n     ++\n      +\treturn 0;\n      +}\n      +\n 3:  c2e5a7201aa = 3:  80282efef57 fsmonitor: implement filesystem change listener for Linux\n 4:  05f5b2dd4fb = 4:  cb03803e355 fsmonitor: enable fsmonitor for Linux\n 5:  754355ca44f = 5:  8d9d469b356 fsmonitor: test updates\n 6:  f56175e097a = 6:  5afd03fa6ca fsmonitor: update doc for Linux\n\n-- \ngitgitgadget\n"},{"id":"467877","messageId":"e53fc07754094aa5ba8080ec7761869c6429a8af.1669230044.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v4.git.git.1669230044.gitgitgadget@gmail.com","subject":"[PATCH v4 2/6] fsmonitor: determine if filesystem is local or remote","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-23T19:00:40Z","receivedAt":"2022-11-23T19:00:57Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nCompare the given path to the mounted filesystems. Find the mount that is\nthe longest prefix of the path (if any) and determine if that mount is on a\nlocal or remote filesystem.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-path-utils-linux.c | 186 ++++++++++++++++++++++++\n 1 file changed, 186 insertions(+)\n create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n\ndiff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c\nnew file mode 100644\nindex 00000000000..d3281422ebc\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-path-utils-linux.c\n@@ -0,0 +1,186 @@\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-path-utils.h\"\n+#include <errno.h>\n+#include <mntent.h>\n+#include <sys/mount.h>\n+#include <sys/vfs.h>\n+#include <sys/statvfs.h>\n+\n+static int is_remote_fs(const char* path) {\n+\tstruct statfs fs;\n+\n+\tif (statfs(path, &fs)) {\n+\t\terror_errno(_(\"statfs('%s') failed\"), path);\n+\t\treturn -1;\n+\t}\n+\n+\tswitch (fs.f_type) {\n+\t\tcase 0x61636673:  /* ACFS */\n+\t\tcase 0x5346414F:  /* AFS */\n+\t\tcase 0x00C36400:  /* CEPH */\n+\t\tcase 0xFF534D42:  /* CIFS */\n+\t\tcase 0x73757245:  /* CODA */\n+\t\tcase 0x19830326:  /* FHGFS */\n+\t\tcase 0x1161970:   /* GFS */\n+\t\tcase 0x47504653:  /* GPFS */\n+\t\tcase 0x013111A8:  /* IBRIX */\n+\t\tcase 0x6B414653:  /* KAFS */\n+\t\tcase 0x0BD00BD0:  /* LUSTRE */\n+\t\tcase 0x564C:      /* NCP */\n+\t\tcase 0x6969:      /* NFS */\n+\t\tcase 0x6E667364:  /* NFSD */\n+\t\tcase 0x7461636f:  /* OCFS2 */\n+\t\tcase 0xAAD7AAEA:  /* PANFS */\n+\t\tcase 0x517B:      /* SMB */\n+\t\tcase 0xBEEFDEAD:  /* SNFS */\n+\t\tcase 0xFE534D42:  /* SMB2 */\n+\t\tcase 0xBACBACBC:  /* VMHGFS */\n+\t\tcase 0xA501FCF5:  /* VXFS */\n+\t\t\treturn 1;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int find_mount(const char *path, const struct statvfs *fs,\n+\tstruct mntent *ent)\n+{\n+\tconst char *const mounts = \"/proc/mounts\";\n+\tconst char *rp = real_pathdup(path, 1);\n+\tstruct mntent *ment = NULL;\n+\tstruct statvfs mntfs;\n+\tFILE *fp;\n+\tint found = 0;\n+\tint dlen, plen, flen = 0;\n+\n+\tent->mnt_fsname = NULL;\n+\tent->mnt_dir = NULL;\n+\tent->mnt_type = NULL;\n+\n+\tfp = setmntent(mounts, \"r\");\n+\tif (!fp) {\n+\t\terror_errno(_(\"setmntent('%s') failed\"), mounts);\n+\t\treturn -1;\n+\t}\n+\n+\tplen = strlen(rp);\n+\n+\t/* read all the mount information and compare to path */\n+\twhile ((ment = getmntent(fp)) != NULL) {\n+\t\tif (statvfs(ment->mnt_dir, &mntfs)) {\n+\t\t\tswitch (errno) {\n+\t\t\tcase EPERM:\n+\t\t\tcase ESRCH:\n+\t\t\tcase EACCES:\n+\t\t\t\tcontinue;\n+\t\t\tdefault:\n+\t\t\t\terror_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n+\t\t\t\tendmntent(fp);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* is mount on the same filesystem and is a prefix of the path */\n+\t\tif ((fs->f_fsid == mntfs.f_fsid) &&\n+\t\t\t!strncmp(ment->mnt_dir, rp, strlen(ment->mnt_dir))) {\n+\t\t\tdlen = strlen(ment->mnt_dir);\n+\t\t\tif (dlen > plen)\n+\t\t\t\tcontinue;\n+\t\t\t/*\n+\t\t\t * root is always a potential match; otherwise look for\n+\t\t\t * directory prefix\n+\t\t\t */\n+\t\t\tif ((dlen == 1 && ment->mnt_dir[0] == '/') ||\n+\t\t\t\t(dlen > flen && (!rp[dlen] || rp[dlen] == '/'))) {\n+\t\t\t\tflen = dlen;\n+\t\t\t\t/*\n+\t\t\t\t * https://man7.org/linux/man-pages/man3/getmntent.3.html\n+\t\t\t\t *\n+\t\t\t\t * The pointer points to a static area of memory which is\n+\t\t\t\t * overwritten by subsequent calls to getmntent().\n+\t\t\t\t */\n+\t\t\t\tfound = 1;\n+\t\t\t\tfree(ent->mnt_fsname);\n+\t\t\t\tfree(ent->mnt_dir);\n+\t\t\t\tfree(ent->mnt_type);\n+\t\t\t\tent->mnt_fsname = xstrdup(ment->mnt_fsname);\n+\t\t\t\tent->mnt_dir = xstrdup(ment->mnt_dir);\n+\t\t\t\tent->mnt_type = xstrdup(ment->mnt_type);\n+\t\t\t}\n+\t\t}\n+\t}\n+\tendmntent(fp);\n+\n+\tif (!found)\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n+{\n+\tstruct mntent ment;\n+\tstruct statvfs fs;\n+\n+\tif (statvfs(path, &fs))\n+\t\treturn error_errno(_(\"statvfs('%s') failed\"), path);\n+\n+\n+\tif (find_mount(path, &fs, &ment) < 0) {\n+\t\tfree(ment.mnt_fsname);\n+\t\tfree(ment.mnt_dir);\n+\t\tfree(ment.mnt_type);\n+\t\treturn -1;\n+\t}\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n+\t\t\t path, fs.f_flag, ment.mnt_type, ment.mnt_fsname);\n+\n+\tfs_info->is_remote = is_remote_fs(ment.mnt_dir);\n+\tfs_info->typename = ment.mnt_fsname;\n+\tfree(ment.mnt_dir);\n+\tfree(ment.mnt_type);\n+\n+\tif (fs_info->is_remote < 0) {\n+\t\tfree(ment.mnt_fsname);\n+\t\treturn -1;\n+\t}\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"'%s' is_remote: %d\",\n+\t\t\t\tpath, fs_info->is_remote);\n+\n+\treturn 0;\n+}\n+\n+int fsmonitor__is_fs_remote(const char *path)\n+{\n+\tstruct fs_info fs;\n+\n+\tif (fsmonitor__get_fs_info(path, &fs))\n+\t\treturn -1;\n+\n+\tfree(fs.typename);\n+\n+\treturn fs.is_remote;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info)\n+{\n+\treturn 0;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info)\n+{\n+\treturn NULL;\n+}\n-- \ngitgitgadget\n\n"},{"id":"467878","messageId":"80282efef5779728022a317388da6790e5db44e2.1669230044.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v4.git.git.1669230044.gitgitgadget@gmail.com","subject":"[PATCH v4 3/6] fsmonitor: implement filesystem change listener for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-23T19:00:41Z","receivedAt":"2022-11-23T19:01:09Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nImplement a filesystem change listener for Linux based on the inotify API:\nhttps://man7.org/linux/man-pages/man7/inotify.7.html\n\ninotify requires registering a watch on every directory in the worktree and\nspecial handling of moves/renames.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-listen-linux.c | 676 ++++++++++++++++++++++++++++\n 1 file changed, 676 insertions(+)\n create mode 100644 compat/fsmonitor/fsm-listen-linux.c\n\ndiff --git a/compat/fsmonitor/fsm-listen-linux.c b/compat/fsmonitor/fsm-listen-linux.c\nnew file mode 100644\nindex 00000000000..e8548e4e009\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-listen-linux.c\n@@ -0,0 +1,676 @@\n+#include \"cache.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsm-listen.h\"\n+#include \"fsmonitor--daemon.h\"\n+#include <dirent.h>\n+#include <fcntl.h>\n+#include <sys/inotify.h>\n+#include <sys/stat.h>\n+\n+/*\n+ * Safe value to bitwise OR with rest of mask for\n+ * kernels that do not support IN_MASK_CREATE\n+ */\n+#ifndef IN_MASK_CREATE\n+#define IN_MASK_CREATE 0x00000000\n+#endif\n+\n+enum shutdown_reason {\n+\tSHUTDOWN_CONTINUE = 0,\n+\tSHUTDOWN_STOP,\n+\tSHUTDOWN_ERROR,\n+\tSHUTDOWN_FORCE\n+};\n+\n+struct watch_entry {\n+\tstruct hashmap_entry ent;\n+\tint wd;\n+\tuint32_t cookie;\n+\tconst char *dir;\n+};\n+\n+struct rename_entry {\n+\tstruct hashmap_entry ent;\n+\ttime_t whence;\n+\tuint32_t cookie;\n+\tconst char *dir;\n+};\n+\n+struct fsm_listen_data {\n+\tint fd_inotify;\n+\tenum shutdown_reason shutdown;\n+\tstruct hashmap watches;\n+\tstruct hashmap renames;\n+\tstruct hashmap revwatches;\n+};\n+\n+static int watch_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct watch_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct watch_entry, ent);\n+\te2 = container_of(eptr, const struct watch_entry, ent);\n+\treturn e1->wd != e2->wd;\n+}\n+\n+static int revwatches_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct watch_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct watch_entry, ent);\n+\te2 = container_of(eptr, const struct watch_entry, ent);\n+\treturn strcmp(e1->dir, e2->dir);\n+}\n+\n+static int rename_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct rename_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct rename_entry, ent);\n+\te2 = container_of(eptr, const struct rename_entry, ent);\n+\treturn e1->cookie != e2->cookie;\n+}\n+\n+/*\n+ * Register an inotify watch, add watch descriptor to path mapping\n+ * and the reverse mapping.\n+ */\n+static int add_watch(const char *path, struct fsm_listen_data *data)\n+{\n+\tconst char *interned = strintern(path);\n+\tstruct watch_entry *w1, *w2;\n+\n+\t/* add the inotify watch, don't allow watches to be modified */\n+\tint wd = inotify_add_watch(data->fd_inotify, interned,\n+\t\t\t\t(IN_ALL_EVENTS | IN_ONLYDIR | IN_MASK_CREATE)\n+\t\t\t\t^ IN_ACCESS ^ IN_CLOSE ^ IN_OPEN);\n+\tif (wd < 0)\n+\t\treturn error_errno(\"inotify_add_watch('%s') failed\", interned);\n+\n+\t/* add watch descriptor -> directory mapping */\n+\tCALLOC_ARRAY(w1, 1);\n+\tw1->wd = wd;\n+\tw1->dir = interned;\n+\thashmap_entry_init(&w1->ent, memhash(&w1->wd, sizeof(int)));\n+\thashmap_add(&data->watches, &w1->ent);\n+\n+\t/* add directory -> watch descriptor mapping */\n+\tCALLOC_ARRAY(w2, 1);\n+\tw2->wd = wd;\n+\tw2->dir = interned;\n+\thashmap_entry_init(&w2->ent, memhash(w2->dir, strlen(w2->dir)));\n+\thashmap_add(&data->revwatches, &w2->ent);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove the inotify watch, the watch descriptor to path mapping\n+ * and the reverse mapping.\n+ */\n+static void remove_watch(struct watch_entry *w,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct watch_entry k1, k2, *w1, *w2;\n+\n+\t/* remove watch, ignore error if kernel already did it */\n+\tif (inotify_rm_watch(data->fd_inotify, w->wd) && errno != EINVAL)\n+\t\terror_errno(\"inotify_rm_watch() failed\");\n+\n+\thashmap_entry_init(&k1.ent, memhash(&w->wd, sizeof(int)));\n+\tw1 = hashmap_remove_entry(&data->watches, &k1, ent, NULL);\n+\tif (!w1)\n+\t\tBUG(\"Double remove of watch for '%s'\", w->dir);\n+\n+\tif (w1->cookie)\n+\t\tBUG(\"Removing watch for '%s' which has a pending rename\", w1->dir);\n+\n+\thashmap_entry_init(&k2.ent, memhash(w->dir, strlen(w->dir)));\n+\tw2 = hashmap_remove_entry(&data->revwatches, &k2, ent, NULL);\n+\tif (!w2)\n+\t\tBUG(\"Double remove of reverse watch for '%s'\", w->dir);\n+\n+\t/* w1->dir and w2->dir are interned strings, we don't own them */\n+\tfree(w1);\n+\tfree(w2);\n+}\n+\n+/*\n+ * Check for stale directory renames.\n+ *\n+ * https://man7.org/linux/man-pages/man7/inotify.7.html\n+ *\n+ * Allow for some small timeout to account for the fact that insertion of the\n+ * IN_MOVED_FROM+IN_MOVED_TO event pair is not atomic, and the possibility that\n+ * there may not be any IN_MOVED_TO event.\n+ *\n+ * If the IN_MOVED_TO event is not received within the timeout then events have\n+ * been missed and the monitor is in an inconsistent state with respect to the\n+ * filesystem.\n+ */\n+static int check_stale_dir_renames(struct hashmap *renames, time_t max_age)\n+{\n+\tstruct rename_entry *re;\n+\tstruct hashmap_iter iter;\n+\n+\thashmap_for_each_entry(renames, &iter, re, ent) {\n+\t\tif (re->whence <= max_age)\n+\t\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Track pending renames.\n+ *\n+ * Tracking is done via a event cookie to watch descriptor mapping.\n+ *\n+ * A rename is not complete until matching a IN_MOVED_TO event is received\n+ * for a corresponding IN_MOVED_FROM event.\n+ */\n+static void add_dir_rename(uint32_t cookie, const char *path,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct watch_entry k, *w;\n+\tstruct rename_entry *re;\n+\n+\t/* lookup the watch descriptor for the given path */\n+\thashmap_entry_init(&k.ent, memhash(path, strlen(path)));\n+\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n+\tif (!w) /* should never happen */\n+\t\tBUG(\"No watch for '%s'\", path);\n+\tw->cookie = cookie;\n+\n+\t/* add the pending rename to match against later */\n+\tCALLOC_ARRAY(re, 1);\n+\tre->dir = w->dir;\n+\tre->cookie = w->cookie;\n+\tre->whence = time(NULL);\n+\thashmap_entry_init(&re->ent, memhash(&re->cookie, sizeof(uint32_t)));\n+\thashmap_add(&data->renames, &re->ent);\n+}\n+\n+/*\n+ * Handle directory renames\n+ *\n+ * Once a IN_MOVED_TO event is received, lookup the rename tracking information\n+ * via the event cookie and use this information to update the watch.\n+ */\n+static void rename_dir(uint32_t cookie, const char *path,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct rename_entry rek, *re;\n+\tstruct watch_entry k, *w;\n+\n+\t/* lookup a pending rename to match */\n+\trek.cookie = cookie;\n+\thashmap_entry_init(&rek.ent, memhash(&rek.cookie, sizeof(uint32_t)));\n+\tre = hashmap_get_entry(&data->renames, &rek, ent, NULL);\n+\tif (re) {\n+\t\tk.dir = re->dir;\n+\t\thashmap_entry_init(&k.ent, memhash(k.dir, strlen(k.dir)));\n+\t\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n+\t\tif (w) {\n+\t\t\tw->cookie = 0; /* rename handled */\n+\t\t\tremove_watch(w, data);\n+\t\t\tadd_watch(path, data);\n+\t\t} else {\n+\t\t\tBUG(\"No matching watch\");\n+\t\t}\n+\t} else {\n+\t\tBUG(\"No matching cookie\");\n+\t}\n+}\n+\n+/*\n+ * Recursively add watches to every directory under path\n+ */\n+static int register_inotify(const char *path,\n+\tstruct fsmonitor_daemon_state *state,\n+\tstruct fsmonitor_batch *batch)\n+{\n+\tDIR *dir;\n+\tconst char *rel;\n+\tstruct strbuf current = STRBUF_INIT;\n+\tstruct dirent *de;\n+\tstruct stat fs;\n+\tint ret = -1;\n+\n+\tdir = opendir(path);\n+\tif (!dir)\n+\t\treturn error_errno(\"opendir('%s') failed\", path);\n+\n+\twhile ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {\n+\t\tstrbuf_reset(&current);\n+\t\tstrbuf_addf(&current, \"%s/%s\", path, de->d_name);\n+\t\tif (lstat(current.buf, &fs)) {\n+\t\t\terror_errno(\"lstat('%s') failed\", current.buf);\n+\t\t\tgoto failed;\n+\t\t}\n+\n+\t\t/* recurse into directory */\n+\t\tif (S_ISDIR(fs.st_mode)) {\n+\t\t\tif (add_watch(current.buf, state->listen_data))\n+\t\t\t\tgoto failed;\n+\t\t\tif (register_inotify(current.buf, state, batch))\n+\t\t\t\tgoto failed;\n+\t\t} else if (batch) {\n+\t\t\trel = current.buf + state->path_worktree_watch.len + 1;\n+\t\t\ttrace_printf_key(&trace_fsmonitor, \"explicitly adding '%s'\", rel);\n+\t\t\tfsmonitor_batch__add_path(batch, rel);\n+\t\t}\n+\t}\n+\tret = 0;\n+\n+failed:\n+\tstrbuf_release(&current);\n+\tif (closedir(dir) < 0)\n+\t\treturn error_errno(\"closedir('%s') failed\", path);\n+\treturn ret;\n+}\n+\n+static int em_rename_dir_from(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVED_FROM));\n+}\n+\n+static int em_rename_dir_to(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVED_TO));\n+}\n+\n+static int em_remove_watch(u_int32_t mask)\n+{\n+\treturn (mask & IN_DELETE_SELF);\n+}\n+\n+static int em_dir_renamed(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVE));\n+}\n+\n+static int em_dir_created(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_CREATE));\n+}\n+\n+static int em_dir_deleted(uint32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_DELETE));\n+}\n+\n+static int em_force_shutdown(u_int32_t mask)\n+{\n+\treturn (mask & IN_UNMOUNT) || (mask & IN_Q_OVERFLOW);\n+}\n+\n+static int em_ignore(u_int32_t mask)\n+{\n+\treturn (mask & IN_IGNORED) || (mask & IN_MOVE_SELF);\n+}\n+\n+static void log_mask_set(const char *path, u_int32_t mask)\n+{\n+\tstruct strbuf msg = STRBUF_INIT;\n+\n+\tif (mask & IN_ACCESS)\n+\t\tstrbuf_addstr(&msg, \"IN_ACCESS|\");\n+\tif (mask & IN_MODIFY)\n+\t\tstrbuf_addstr(&msg, \"IN_MODIFY|\");\n+\tif (mask & IN_ATTRIB)\n+\t\tstrbuf_addstr(&msg, \"IN_ATTRIB|\");\n+\tif (mask & IN_CLOSE_WRITE)\n+\t\tstrbuf_addstr(&msg, \"IN_CLOSE_WRITE|\");\n+\tif (mask & IN_CLOSE_NOWRITE)\n+\t\tstrbuf_addstr(&msg, \"IN_CLOSE_NOWRITE|\");\n+\tif (mask & IN_OPEN)\n+\t\tstrbuf_addstr(&msg, \"IN_OPEN|\");\n+\tif (mask & IN_MOVED_FROM)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVED_FROM|\");\n+\tif (mask & IN_MOVED_TO)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVED_TO|\");\n+\tif (mask & IN_CREATE)\n+\t\tstrbuf_addstr(&msg, \"IN_CREATE|\");\n+\tif (mask & IN_DELETE)\n+\t\tstrbuf_addstr(&msg, \"IN_DELETE|\");\n+\tif (mask & IN_DELETE_SELF)\n+\t\tstrbuf_addstr(&msg, \"IN_DELETE_SELF|\");\n+\tif (mask & IN_MOVE_SELF)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVE_SELF|\");\n+\tif (mask & IN_UNMOUNT)\n+\t\tstrbuf_addstr(&msg, \"IN_UNMOUNT|\");\n+\tif (mask & IN_Q_OVERFLOW)\n+\t\tstrbuf_addstr(&msg, \"IN_Q_OVERFLOW|\");\n+\tif (mask & IN_IGNORED)\n+\t\tstrbuf_addstr(&msg, \"IN_IGNORED|\");\n+\tif (mask & IN_ISDIR)\n+\t\tstrbuf_addstr(&msg, \"IN_ISDIR|\");\n+\n+\ttrace_printf_key(&trace_fsmonitor, \"inotify_event: '%s', mask=%#8.8x %s\",\n+\t\t\t\tpath, mask, msg.buf);\n+\n+\tstrbuf_release(&msg);\n+}\n+\n+int fsm_listen__ctor(struct fsmonitor_daemon_state *state)\n+{\n+\tint fd;\n+\tint ret = 0;\n+\tstruct fsm_listen_data *data;\n+\n+\tCALLOC_ARRAY(data, 1);\n+\tstate->listen_data = data;\n+\tstate->listen_error_code = -1;\n+\tdata->shutdown = SHUTDOWN_ERROR;\n+\n+\tfd = inotify_init1(O_NONBLOCK);\n+\tif (fd < 0)\n+\t\treturn error_errno(\"inotify_init1() failed\");\n+\n+\tdata->fd_inotify = fd;\n+\n+\thashmap_init(&data->watches, watch_entry_cmp, NULL, 0);\n+\thashmap_init(&data->renames, rename_entry_cmp, NULL, 0);\n+\thashmap_init(&data->revwatches, revwatches_entry_cmp, NULL, 0);\n+\n+\tif (add_watch(state->path_worktree_watch.buf, data))\n+\t\tret = -1;\n+\telse if (register_inotify(state->path_worktree_watch.buf, state, NULL))\n+\t\tret = -1;\n+\telse if (state->nr_paths_watching > 1) {\n+\t\tif (add_watch(state->path_gitdir_watch.buf, data))\n+\t\t\tret = -1;\n+\t\telse if (register_inotify(state->path_gitdir_watch.buf, state, NULL))\n+\t\t\tret = -1;\n+\t}\n+\n+\tif (!ret) {\n+\t\tstate->listen_error_code = 0;\n+\t\tdata->shutdown = SHUTDOWN_CONTINUE;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+void fsm_listen__dtor(struct fsmonitor_daemon_state *state)\n+{\n+\tstruct fsm_listen_data *data;\n+\tstruct hashmap_iter iter;\n+\tstruct watch_entry *w;\n+\tint fd;\n+\n+\tif (!state || !state->listen_data)\n+\t\treturn;\n+\n+\tdata = state->listen_data;\n+\tfd = data->fd_inotify;\n+\n+\thashmap_for_each_entry(&data->watches, &iter, w, ent) {\n+\t\tw->cookie = 0; /* ignore any pending renames */\n+\t\tremove_watch(w, data);\n+\t}\n+\thashmap_clear(&data->watches);\n+\n+\thashmap_clear(&data->revwatches); /* remove_watch freed the entries */\n+\n+\thashmap_clear_and_free(&data->renames, struct rename_entry, ent);\n+\n+\tFREE_AND_NULL(state->listen_data);\n+\n+\tif (fd && (close(fd) < 0))\n+\t\terror_errno(_(\"closing inotify file descriptor failed\"));\n+}\n+\n+void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)\n+{\n+\tif (!state->listen_data->shutdown)\n+\t\tstate->listen_data->shutdown = SHUTDOWN_STOP;\n+}\n+\n+/*\n+ * Process a single inotify event and queue for publication.\n+ */\n+static int process_event(const char *path,\n+\tconst struct inotify_event *event,\n+\tstruct fsmonitor_batch *batch,\n+\tstruct string_list *cookie_list,\n+\tstruct fsmonitor_daemon_state *state)\n+{\n+\tconst char *rel;\n+\tconst char *last_sep;\n+\n+\tswitch (fsmonitor_classify_path_absolute(state, path)) {\n+\t\tcase IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:\n+\t\tcase IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:\n+\t\t\t/* Use just the filename of the cookie file. */\n+\t\t\tlast_sep = find_last_dir_sep(path);\n+\t\t\tstring_list_append(cookie_list,\n+\t\t\t\t\tlast_sep ? last_sep + 1 : path);\n+\t\t\tbreak;\n+\t\tcase IS_INSIDE_DOT_GIT:\n+\t\tcase IS_INSIDE_GITDIR:\n+\t\t\tbreak;\n+\t\tcase IS_DOT_GIT:\n+\t\tcase IS_GITDIR:\n+\t\t\t/*\n+\t\t\t* If .git directory is deleted or renamed away,\n+\t\t\t* we have to quit.\n+\t\t\t*/\n+\t\t\tif (em_dir_deleted(event->mask)) {\n+\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\"event: gitdir removed\");\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\tif (em_dir_renamed(event->mask)) {\n+\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\"event: gitdir renamed\");\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase IS_WORKDIR_PATH:\n+\t\t\t/* normal events in the working directory */\n+\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n+\t\t\t\tlog_mask_set(path, event->mask);\n+\n+\t\t\trel = path + state->path_worktree_watch.len + 1;\n+\t\t\tfsmonitor_batch__add_path(batch, rel);\n+\n+\t\t\tif (em_dir_deleted(event->mask))\n+\t\t\t\tbreak;\n+\n+\t\t\t/* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */\n+\t\t\tif (em_rename_dir_from(event->mask))\n+\t\t\t\tadd_dir_rename(event->cookie, path, state->listen_data);\n+\n+\t\t\t/* received IN_MOVE_TO, update watch to reflect new path */\n+\t\t\tif (em_rename_dir_to(event->mask)) {\n+\t\t\t\trename_dir(event->cookie, path, state->listen_data);\n+\t\t\t\tif (register_inotify(path, state, batch)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (em_dir_created(event->mask)) {\n+\t\t\t\tif (add_watch(path, state->listen_data)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t\tif (register_inotify(path, state, batch)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase IS_OUTSIDE_CONE:\n+\t\tdefault:\n+\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\"ignoring '%s'\", path);\n+\t\t\tbreak;\n+\t}\n+\treturn 0;\n+done:\n+\treturn -1;\n+}\n+\n+/*\n+ * Read the inotify event stream and pre-process events before further\n+ * processing and eventual publishing.\n+ */\n+static void handle_events(struct fsmonitor_daemon_state *state)\n+{\n+\t /* See https://man7.org/linux/man-pages/man7/inotify.7.html */\n+\tchar buf[4096]\n+\t\t__attribute__ ((aligned(__alignof__(struct inotify_event))));\n+\n+\tstruct hashmap watches = state->listen_data->watches;\n+\tstruct fsmonitor_batch *batch = NULL;\n+\tstruct string_list cookie_list = STRING_LIST_INIT_DUP;\n+\tstruct watch_entry k, *w;\n+\tstruct strbuf path;\n+\tconst struct inotify_event *event;\n+\tint fd = state->listen_data->fd_inotify;\n+\tssize_t len;\n+\tchar *ptr, *p;\n+\n+\tstrbuf_init(&path, PATH_MAX);\n+\n+\tfor(;;) {\n+\t\tlen = read(fd, buf, sizeof(buf));\n+\t\tif (len == -1 && errno != EAGAIN) {\n+\t\t\terror_errno(_(\"reading inotify message stream failed\"));\n+\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\t/* nothing to read */\n+\t\tif (len <= 0)\n+\t\t\tgoto done;\n+\n+\t\t/* Loop over all events in the buffer. */\n+\t\tfor (ptr = buf; ptr < buf + len;\n+\t\t\t ptr += sizeof(struct inotify_event) + event->len) {\n+\n+\t\t\tevent = (const struct inotify_event *) ptr;\n+\n+\t\t\tif (em_ignore(event->mask))\n+\t\t\t\tcontinue;\n+\n+\t\t\t/* File system was unmounted or event queue overflowed */\n+\t\t\tif (em_force_shutdown(event->mask)) {\n+\t\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n+\t\t\t\t\tlog_mask_set(\"Forcing shutdown\", event->mask);\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\thashmap_entry_init(&k.ent, memhash(&event->wd, sizeof(int)));\n+\t\t\tk.wd = event->wd;\n+\n+\t\t\tw = hashmap_get_entry(&watches, &k, ent, NULL);\n+\t\t\tif (!w) /* should never happen */\n+\t\t\t\tBUG(\"No watch for '%s'\", event->name);\n+\n+\t\t\t/* directory watch was removed */\n+\t\t\tif (em_remove_watch(event->mask)) {\n+\t\t\t\tremove_watch(w, state->listen_data);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tstrbuf_reset(&path);\n+\t\t\tstrbuf_add(&path, w->dir, strlen(w->dir));\n+\t\t\tstrbuf_addch(&path, '/');\n+\t\t\tstrbuf_addstr(&path, event->name);\n+\n+\t\t\tp = fsmonitor__resolve_alias(path.buf, &state->alias);\n+\t\t\tif (!p)\n+\t\t\t\tp = strbuf_detach(&path, NULL);\n+\n+\t\t\tif (!batch)\n+\t\t\t\tbatch = fsmonitor_batch__new();\n+\n+\t\t\tif (process_event(p, event, batch, &cookie_list, state)) {\n+\t\t\t\tfree(p);\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tfree(p);\n+\t\t}\n+\t\tstrbuf_reset(&path);\n+\t\tfsmonitor_publish(state, batch, &cookie_list);\n+\t\tstring_list_clear(&cookie_list, 0);\n+\t\tbatch = NULL;\n+\t}\n+done:\n+\tstrbuf_release(&path);\n+\tfsmonitor_batch__free_list(batch);\n+\tstring_list_clear(&cookie_list, 0);\n+}\n+\n+/*\n+ * Non-blocking read of the inotify events stream. The inotify fd is polled\n+ * frequently to help minimize the number of queue overflows.\n+ */\n+void fsm_listen__loop(struct fsmonitor_daemon_state *state)\n+{\n+\tint poll_num;\n+\tconst int interval = 1000;\n+\ttime_t checked = time(NULL);\n+\tstruct pollfd fds[1];\n+\tfds[0].fd = state->listen_data->fd_inotify;\n+\tfds[0].events = POLLIN;\n+\n+\tfor(;;) {\n+\t\tswitch (state->listen_data->shutdown) {\n+\t\t\tcase SHUTDOWN_CONTINUE:\n+\t\t\t\tpoll_num = poll(fds, 1, 1);\n+\t\t\t\tif (poll_num == -1) {\n+\t\t\t\t\tif (errno == EINTR)\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\terror_errno(_(\"polling inotify message stream failed\"));\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif ((time(NULL) - checked) >= interval) {\n+\t\t\t\t\tchecked = time(NULL);\n+\t\t\t\t\tif (check_stale_dir_renames(&state->listen_data->renames,\n+\t\t\t\t\t\tchecked - interval)) {\n+\t\t\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\t\"Missed IN_MOVED_TO events, forcing shutdown\");\n+\t\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\n+\t\t\t\tif (poll_num > 0 && (fds[0].revents & POLLIN))\n+\t\t\t\t\thandle_events(state);\n+\n+\t\t\t\tcontinue;\n+\t\t\tcase SHUTDOWN_ERROR:\n+\t\t\t\tstate->listen_error_code = -1;\n+\t\t\t\tipc_server_stop_async(state->ipc_server_data);\n+\t\t\t\tbreak;\n+\t\t\tcase SHUTDOWN_FORCE:\n+\t\t\t\tstate->listen_error_code = 0;\n+\t\t\t\tipc_server_stop_async(state->ipc_server_data);\n+\t\t\t\tbreak;\n+\t\t\tcase SHUTDOWN_STOP:\n+\t\t\tdefault:\n+\t\t\t\tstate->listen_error_code = 0;\n+\t\t\t\tbreak;\n+\t\t}\n+\t\treturn;\n+\t}\n+}\n-- \ngitgitgadget\n\n"},{"id":"467879","messageId":"cb03803e3558ffbc5169b088d427ac5557d7e861.1669230044.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v4.git.git.1669230044.gitgitgadget@gmail.com","subject":"[PATCH v4 4/6] fsmonitor: enable fsmonitor for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-23T19:00:42Z","receivedAt":"2022-11-23T19:01:23Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nUpdate build to enable fsmonitor for Linux.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n config.mak.uname                    |  8 ++++++++\n contrib/buildsystems/CMakeLists.txt | 11 ++++++++++-\n 2 files changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/config.mak.uname b/config.mak.uname\nindex d63629fe807..5890d810463 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -68,6 +68,14 @@ ifeq ($(uname_S),Linux)\n \tifneq ($(findstring .el7.,$(uname_R)),)\n \t\tBASIC_CFLAGS += -std=c99\n \tendif\n+\t# The builtin FSMonitor on Linux builds upon Simple-IPC.  Both require\n+\t# Unix domain sockets and PThreads.\n+\tifndef NO_PTHREADS\n+\tifndef NO_UNIX_SOCKETS\n+\tFSMONITOR_DAEMON_BACKEND = linux\n+\tFSMONITOR_OS_SETTINGS = linux\n+\tendif\n+\tendif\n endif\n ifeq ($(uname_S),GNU/kFreeBSD)\n \tHAVE_ALLOCA_H = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 3957e4cf8cd..f058c3d19b4 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -304,7 +304,16 @@ else()\n endif()\n \n if(SUPPORTS_SIMPLE_IPC)\n-\tif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n+\t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-linux.c)\n+\n+\t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-linux.c)\n+\telseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n-- \ngitgitgadget\n\n"},{"id":"467880","messageId":"8d9d469b3566fe45c43cfd348a3a1e2dbd6dc5dd.1669230044.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v4.git.git.1669230044.gitgitgadget@gmail.com","subject":"[PATCH v4 5/6] fsmonitor: test updates","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-23T19:00:43Z","receivedAt":"2022-11-23T19:01:25Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nt7527-builtin-fsmonitor was leaking fsmonitor--daemon processes in some\ncases.\n\nAccomodate slight difference in the number of events generated on Linux.\n\nOn lower-powered systems, spin a little to give the daemon time\nto respond to and log filesystem events.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n t/t7527-builtin-fsmonitor.sh | 94 ++++++++++++++++++++++++++++++------\n 1 file changed, 80 insertions(+), 14 deletions(-)\n\ndiff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh\nindex 4abc74db2bb..951374231b7 100755\n--- a/t/t7527-builtin-fsmonitor.sh\n+++ b/t/t7527-builtin-fsmonitor.sh\n@@ -13,7 +13,7 @@ fi\n stop_daemon_delete_repo () {\n \tr=$1 &&\n \ttest_might_fail git -C $r fsmonitor--daemon stop &&\n-\trm -rf $1\n+\trm -rf $r\n }\n \n start_daemon () {\n@@ -72,6 +72,34 @@ start_daemon () {\n \t)\n }\n \n+IMPLICIT_TIMEOUT=5\n+\n+wait_for_update () {\n+\tfunc=$1 &&\n+\tfile=$2 &&\n+\tsz=$(wc -c < \"$file\") &&\n+\tlast=0 &&\n+\t$func &&\n+\tk=0 &&\n+\twhile test \"$k\" -lt $IMPLICIT_TIMEOUT\n+\tdo\n+\t\tnsz=$(wc -c < \"$file\")\n+\t\tif test \"$nsz\" -gt \"$sz\"\n+\t\tthen\n+\t\t\tif test \"$last\" -eq \"$nsz\"\n+\t\t\tthen\n+\t\t\t\tcat \"$file\" &&\n+\t\t\t\treturn 0\n+\t\t\tfi\n+\t\t\tlast=$nsz\n+\t\tfi\n+\t\tsleep 1\n+\t\tk=$(( $k + 1 ))\n+\tdone &&\n+\tcat \"$file\" &&\n+\treturn 0\n+}\n+\n # Is a Trace2 data event present with the given catetory and key?\n # We do not care what the value is.\n #\n@@ -137,7 +165,6 @@ test_expect_success 'implicit daemon start' '\n # machines (where it might take a moment to wake and reschedule the\n # daemon process) to avoid false alarms during test runs.)\n #\n-IMPLICIT_TIMEOUT=5\n \n verify_implicit_shutdown () {\n \tr=$1 &&\n@@ -373,6 +400,15 @@ create_files () {\n \techo 3 >dir2/new\n }\n \n+rename_directory () {\n+\tmv dirtorename dirrenamed\n+}\n+\n+rename_directory_file () {\n+\tmv dirtorename dirrenamed &&\n+\techo 1 > dirrenamed/new\n+}\n+\n rename_files () {\n \tmv rename renamed &&\n \tmv dir1/rename dir1/renamed &&\n@@ -427,10 +463,12 @@ test_expect_success 'edit some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tedit_files &&\n+\twait_for_update edit_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/modified$\"  .git/trace &&\n \tgrep \"^event: dir2/modified$\"  .git/trace &&\n \tgrep \"^event: modified$\"       .git/trace &&\n@@ -442,10 +480,12 @@ test_expect_success 'create some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tcreate_files &&\n+\twait_for_update create_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/new$\" .git/trace &&\n \tgrep \"^event: dir2/new$\" .git/trace &&\n \tgrep \"^event: new$\"      .git/trace\n@@ -456,10 +496,12 @@ test_expect_success 'delete some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tdelete_files &&\n+\twait_for_update delete_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/delete$\" .git/trace &&\n \tgrep \"^event: dir2/delete$\" .git/trace &&\n \tgrep \"^event: delete$\"      .git/trace\n@@ -470,10 +512,12 @@ test_expect_success 'rename some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\trename_files &&\n+\twait_for_update rename_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/rename$\"  .git/trace &&\n \tgrep \"^event: dir2/rename$\"  .git/trace &&\n \tgrep \"^event: rename$\"       .git/trace &&\n@@ -487,23 +531,42 @@ test_expect_success 'rename directory' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tmv dirtorename dirrenamed &&\n+\twait_for_update rename_directory \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dirtorename/*$\" .git/trace &&\n \tgrep \"^event: dirrenamed/*$\"  .git/trace\n '\n \n+test_expect_success 'rename directory file' '\n+\ttest_when_finished clean_up_repo_and_stop_daemon &&\n+\n+\tstart_daemon --tf \"$PWD/.git/trace\" &&\n+\n+\twait_for_update rename_directory_file \"$PWD/.git/trace\" &&\n+\n+\ttest-tool fsmonitor-client query --token 0 &&\n+\n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n+\tgrep \"^event: dirtorename/*$\" .git/trace &&\n+\tgrep \"^event: dirrenamed/*$\"  .git/trace &&\n+\tgrep \"^event: dirrenamed/new$\"  .git/trace\n+'\n test_expect_success 'file changes to directory' '\n \ttest_when_finished clean_up_repo_and_stop_daemon &&\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tfile_to_directory &&\n+\twait_for_update file_to_directory \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: delete$\"     .git/trace &&\n \tgrep \"^event: delete/new$\" .git/trace\n '\n@@ -513,10 +576,12 @@ test_expect_success 'directory changes to a file' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tdirectory_to_file &&\n+\twait_for_update directory_to_file \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1$\" .git/trace\n '\n \n@@ -561,7 +626,7 @@ test_expect_success 'flush cached data' '\n \ttest-tool -C test_flush fsmonitor-client query --token \"builtin:test_00000002:0\" >actual_2 &&\n \tnul_to_q <actual_2 >actual_q2 &&\n \n-\tgrep \"^builtin:test_00000002:0Q$\" actual_q2 &&\n+\tgrep \"^builtin:test_00000002:[0-1]Q$\" actual_q2 &&\n \n \t>test_flush/file_3 &&\n \n@@ -732,7 +797,8 @@ u_values=\"$u1 $u2\"\n for u in $u_values\n do\n \ttest_expect_success \"unicode in repo root path: $u\" '\n-\t\ttest_when_finished \"stop_daemon_delete_repo $u\" &&\n+\t\ttest_when_finished \\\n+\t\t\"stop_daemon_delete_repo `echo \"$u\" | sed 's:x:\\\\\\\\\\\\\\\\\\\\\\\\\\\\x:g'`\" &&\n \n \t\tgit init \"$u\" &&\n \t\techo 1 >\"$u\"/file1 &&\n@@ -818,8 +884,7 @@ test_expect_success 'submodule setup' '\n '\n \n test_expect_success 'submodule always visited' '\n-\ttest_when_finished \"git -C super fsmonitor--daemon stop; \\\n-\t\t\t    rm -rf super; \\\n+\ttest_when_finished \"rm -rf super; \\\n \t\t\t    rm -rf sub\" &&\n \n \tcreate_super super &&\n@@ -887,7 +952,8 @@ have_t2_error_event () {\n }\n \n test_expect_success \"stray submodule super-prefix warning\" '\n-\ttest_when_finished \"rm -rf super; \\\n+\ttest_when_finished \"git -C super/dir_1/dir_2/sub fsmonitor--daemon stop; \\\n+\t\t\t    rm -rf super; \\\n \t\t\t    rm -rf sub;   \\\n \t\t\t    rm super-sub.trace\" &&\n \n-- \ngitgitgadget\n\n"},{"id":"467881","messageId":"5afd03fa6ca3a790eb06213573d9be3c40dee492.1669230044.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v4.git.git.1669230044.gitgitgadget@gmail.com","subject":"[PATCH v4 6/6] fsmonitor: update doc for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-11-23T19:00:44Z","receivedAt":"2022-11-23T19:01:27Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nUpdate the documentation for Linux.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Documentation/config/fsmonitor--daemon.txt |  4 ++--\n Documentation/git-fsmonitor--daemon.txt    | 24 ++++++++++++++--------\n 2 files changed, 18 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config/fsmonitor--daemon.txt b/Documentation/config/fsmonitor--daemon.txt\nindex c225c6c9e74..2cafb040d96 100644\n--- a/Documentation/config/fsmonitor--daemon.txt\n+++ b/Documentation/config/fsmonitor--daemon.txt\n@@ -4,8 +4,8 @@ fsmonitor.allowRemote::\n     behavior.  Only respected when `core.fsmonitor` is set to `true`.\n \n fsmonitor.socketDir::\n-    This Mac OS-specific option, if set, specifies the directory in\n+    Mac OS and Linux-specific option. If set, specifies the directory in\n     which to create the Unix domain socket used for communication\n     between the fsmonitor daemon and various Git commands. The directory must\n-    reside on a native Mac OS filesystem.  Only respected when `core.fsmonitor`\n+    reside on a native filesystem.  Only respected when `core.fsmonitor`\n     is set to `true`.\ndiff --git a/Documentation/git-fsmonitor--daemon.txt b/Documentation/git-fsmonitor--daemon.txt\nindex 8238eadb0e1..c2b08229c74 100644\n--- a/Documentation/git-fsmonitor--daemon.txt\n+++ b/Documentation/git-fsmonitor--daemon.txt\n@@ -76,23 +76,31 @@ repositories; this may be overridden by setting `fsmonitor.allowRemote` to\n correctly with all network-mounted repositories and such use is considered\n experimental.\n \n-On Mac OS, the inter-process communication (IPC) between various Git\n+On Linux and Mac OS, the inter-process communication (IPC) between various Git\n commands and the fsmonitor daemon is done via a Unix domain socket (UDS) -- a\n-special type of file -- which is supported by native Mac OS filesystems,\n-but not on network-mounted filesystems, NTFS, or FAT32.  Other filesystems\n-may or may not have the needed support; the fsmonitor daemon is not guaranteed\n-to work with these filesystems and such use is considered experimental.\n+special type of file -- which is supported by many native Linux and Mac OS\n+filesystems, but not on network-mounted filesystems, NTFS, or FAT32.  Other\n+filesystems may or may not have the needed support; the fsmonitor daemon is not\n+guaranteed to work with these filesystems and such use is considered\n+experimental.\n \n By default, the socket is created in the `.git` directory, however, if the\n `.git` directory is on a network-mounted filesystem, it will be instead be\n created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a\n network-mounted filesystem in which case you must set the configuration\n-variable `fsmonitor.socketDir` to the path of a directory on a Mac OS native\n+variable `fsmonitor.socketDir` to the path of a directory on a native\n filesystem in which to create the socket file.\n \n If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`)\n-is on a native Mac OS file filesystem the fsmonitor daemon will report an\n-error that will cause the daemon and the currently running command to exit.\n+is on a native Linux or Mac OS filesystem the fsmonitor daemon will report\n+an error that will cause the daemon to exit and the currently running command\n+to issue a warning.\n+\n+On Linux, the fsmonitor daemon registers a watch for each directory in the\n+repository.  The default per-user limit for the number of watches on most Linux\n+systems is 8192.  This may not be sufficient for large repositories or if\n+multiple instances of the fsmonitor daemon are running.\n+See https://watchexec.github.io/docs/inotify-limits.html[Linux inotify limits] for more information.\n \n CONFIGURATION\n -------------\n-- \ngitgitgadget\n"},{"id":"467979","messageId":"xmqqedtrcwxy.fsf@gitster.g","threadId":"58588","inReplyTo":"e53fc07754094aa5ba8080ec7761869c6429a8af.1669230044.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 2/6] fsmonitor: determine if filesystem is local or remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-11-25T07:31:05Z","receivedAt":"2022-11-25T07:31:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +static int is_remote_fs(const char* path) {\n\nStyle (asterisk sticks to the variable not to the type).\n\n> +\tswitch (fs.f_type) {\n> +\t\tcase 0x61636673:  /* ACFS */\n> ...\n> +\t\tcase 0xA501FCF5:  /* VXFS */\n> +\t\t\treturn 1;\n> +\t\tdefault:\n> +\t\t\tbreak;\n> +\t}\n\nAlign case/default to switch by de-denting one level, just like you\ndid the switch() statement in find_mount().\n\n> +static int find_mount(const char *path, const struct statvfs *fs,\n> +\tstruct mntent *ent)\n> +{\n> +\tconst char *const mounts = \"/proc/mounts\";\n> +\tconst char *rp = real_pathdup(path, 1);\n\nNobody seems to free() this once we are done with this function.\n\n> +\tstruct mntent *ment = NULL;\n> +\tstruct statvfs mntfs;\n> +\tFILE *fp;\n> +\tint found = 0;\n> +\tint dlen, plen, flen = 0;\n> +\n> +\tent->mnt_fsname = NULL;\n> +\tent->mnt_dir = NULL;\n> +\tent->mnt_type = NULL;\n\nMore on this later.\n\n> +\tfp = setmntent(mounts, \"r\");\n> +\tif (!fp) {\n> +\t\terror_errno(_(\"setmntent('%s') failed\"), mounts);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tplen = strlen(rp);\n> +\n> +\t/* read all the mount information and compare to path */\n> +\twhile ((ment = getmntent(fp)) != NULL) {\n> +\t\tif (statvfs(ment->mnt_dir, &mntfs)) {\n> +\t\t\tswitch (errno) {\n> +\t\t\tcase EPERM:\n> +\t\t\tcase ESRCH:\n> +\t\t\tcase EACCES:\n> +\t\t\t\tcontinue;\n> +\t\t\tdefault:\n> +\t\t\t\terror_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n> +\t\t\t\tendmntent(fp);\n> +\t\t\t\treturn -1;\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\t/* is mount on the same filesystem and is a prefix of the path */\n> +\t\tif ((fs->f_fsid == mntfs.f_fsid) &&\n> +\t\t\t!strncmp(ment->mnt_dir, rp, strlen(ment->mnt_dir))) {\n> +\t\t\tdlen = strlen(ment->mnt_dir);\n> +\t\t\tif (dlen > plen)\n> +\t\t\t\tcontinue;\n> +\t\t\t/*\n> +\t\t\t * root is always a potential match; otherwise look for\n> +\t\t\t * directory prefix\n> +\t\t\t */\n> +\t\t\tif ((dlen == 1 && ment->mnt_dir[0] == '/') ||\n> +\t\t\t\t(dlen > flen && (!rp[dlen] || rp[dlen] == '/'))) {\n> +\t\t\t\tflen = dlen;\n> +\t\t\t\t/*\n> +\t\t\t\t * https://man7.org/linux/man-pages/man3/getmntent.3.html\n> +\t\t\t\t *\n> +\t\t\t\t * The pointer points to a static area of memory which is\n> +\t\t\t\t * overwritten by subsequent calls to getmntent().\n> +\t\t\t\t */\n> +\t\t\t\tfound = 1;\n> +\t\t\t\tfree(ent->mnt_fsname);\n> +\t\t\t\tfree(ent->mnt_dir);\n> +\t\t\t\tfree(ent->mnt_type);\n> +\t\t\t\tent->mnt_fsname = xstrdup(ment->mnt_fsname);\n> +\t\t\t\tent->mnt_dir = xstrdup(ment->mnt_dir);\n> +\t\t\t\tent->mnt_type = xstrdup(ment->mnt_type);\n> +\t\t\t}\n\nSo more than one mount entries could match the given path.  This\nloop implements \"the last one wins\", but is that a sensible thing to\ndo?  Shouldn't it be more like \"the longest one, being the most\nspecific, wins\"?  If /usr mounts on / and /usr/local mounts on /usr,\nasking for /usr/local/me would want to discover that it is on\nthe filesystem mounted at /usr/local regardless of the order in\nwhich getmntent() returns the entries, no?\n\n> +\t\t}\n> +\t}\n> +\tendmntent(fp);\n> +\n> +\tif (!found)\n> +\t\treturn -1;\n> +\n> +\treturn 0;\n> +}\n\n\n> +\n> +int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n> +{\n> +\tstruct mntent ment;\n> +\tstruct statvfs fs;\n> +\n> +\tif (statvfs(path, &fs))\n> +\t\treturn error_errno(_(\"statvfs('%s') failed\"), path);\n> +\n> +\n> +\tif (find_mount(path, &fs, &ment) < 0) {\n> +\t\tfree(ment.mnt_fsname);\n> +\t\tfree(ment.mnt_dir);\n> +\t\tfree(ment.mnt_type);\n\nIt is a good idea to free, but I _think_ the code is easier to\nfollow if you _clear_ ment before calling find_mount(), not in\nfind_mount().\n\n> +\t\treturn -1;\n> +\t}\n> +\n> +\ttrace_printf_key(&trace_fsmonitor,\n> +\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n> +\t\t\t path, fs.f_flag, ment.mnt_type, ment.mnt_fsname);\n> +\n> +\tfs_info->is_remote = is_remote_fs(ment.mnt_dir);\n> +\tfs_info->typename = ment.mnt_fsname;\n> +\tfree(ment.mnt_dir);\n> +\tfree(ment.mnt_type);\n> +\n> +\tif (fs_info->is_remote < 0) {\n> +\t\tfree(ment.mnt_fsname);\n> +\t\treturn -1;\n\nfs_info->typename just started to point into an already freed memory\nat this point, which is a very safe thing to do to the caller of\nthis function.\n\nRather, perhaps delay the setting of typename after this statement,\nwhich would be more friendly to the caller than telling them that\nthey are not allowed to touch the member when the function returns\nnegative.\n\n> +\t}\n> +\n> +\ttrace_printf_key(&trace_fsmonitor,\n> +\t\t\t\t\"'%s' is_remote: %d\",\n> +\t\t\t\tpath, fs_info->is_remote);\n> +\n> +\treturn 0;\n> +}\n> +\n> +int fsmonitor__is_fs_remote(const char *path)\n> +{\n> +\tstruct fs_info fs;\n> +\n> +\tif (fsmonitor__get_fs_info(path, &fs))\n> +\t\treturn -1;\n> +\n> +\tfree(fs.typename);\n> +\n> +\treturn fs.is_remote;\n> +}\n> +\n> +/*\n> + * No-op for now.\n> + */\n> +int fsmonitor__get_alias(const char *path, struct alias_info *info)\n> +{\n> +\treturn 0;\n> +}\n> +\n> +/*\n> + * No-op for now.\n> + */\n> +char *fsmonitor__resolve_alias(const char *path,\n> +\tconst struct alias_info *info)\n> +{\n> +\treturn NULL;\n> +}\n"},{"id":"468881","messageId":"221212.86lenc6h2l.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"e53fc07754094aa5ba8080ec7761869c6429a8af.1669230044.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 2/6] fsmonitor: determine if filesystem is local or remote","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-12T10:24:27Z","receivedAt":"2022-12-12T10:46:26Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Nov 23 2022, Eric DeCosta via GitGitGadget wrote:\n\n> From: Eric DeCosta <edecosta@mathworks.com>\n>\n> Compare the given path to the mounted filesystems. Find the mount that is\n> the longest prefix of the path (if any) and determine if that mount is on a\n> local or remote filesystem.\n>\n> Signed-off-by: Eric DeCosta <edecosta@mathworks.com>\n> ---\n>  compat/fsmonitor/fsm-path-utils-linux.c | 186 ++++++++++++++++++++++++\n>  1 file changed, 186 insertions(+)\n>  create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n>\n> diff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c\n> new file mode 100644\n> index 00000000000..d3281422ebc\n> --- /dev/null\n> +++ b/compat/fsmonitor/fsm-path-utils-linux.c\n> @@ -0,0 +1,186 @@\n> +#include \"fsmonitor.h\"\n> +#include \"fsmonitor-path-utils.h\"\n> +#include <errno.h>\n> +#include <mntent.h>\n> +#include <sys/mount.h>\n> +#include <sys/vfs.h>\n> +#include <sys/statvfs.h>\n> +\n> +static int is_remote_fs(const char* path) {\n> +\tstruct statfs fs;\n> +\n> +\tif (statfs(path, &fs)) {\n> +\t\terror_errno(_(\"statfs('%s') failed\"), path);\n> +\t\treturn -1;\n> +\t}\n\nNit: Drop the braces and do:\n\n\tif (statfs(...) == -1)\n\t\treturn error_errno(...)\n\n> +\tswitch (fs.f_type) {\n> +\t\tcase 0x61636673:  /* ACFS */\n> +\t\tcase 0x5346414F:  /* AFS */\n> +\t\tcase 0x00C36400:  /* CEPH */\n> +\t\tcase 0xFF534D42:  /* CIFS */\n> +\t\tcase 0x73757245:  /* CODA */\n> +\t\tcase 0x19830326:  /* FHGFS */\n> +\t\tcase 0x1161970:   /* GFS */\n> +\t\tcase 0x47504653:  /* GPFS */\n> +\t\tcase 0x013111A8:  /* IBRIX */\n> +\t\tcase 0x6B414653:  /* KAFS */\n> +\t\tcase 0x0BD00BD0:  /* LUSTRE */\n> +\t\tcase 0x564C:      /* NCP */\n> +\t\tcase 0x6969:      /* NFS */\n> +\t\tcase 0x6E667364:  /* NFSD */\n> +\t\tcase 0x7461636f:  /* OCFS2 */\n> +\t\tcase 0xAAD7AAEA:  /* PANFS */\n> +\t\tcase 0x517B:      /* SMB */\n> +\t\tcase 0xBEEFDEAD:  /* SNFS */\n> +\t\tcase 0xFE534D42:  /* SMB2 */\n> +\t\tcase 0xBACBACBC:  /* VMHGFS */\n> +\t\tcase 0xA501FCF5:  /* VXFS */\n\nSo, before we'd compare against the name, but to avoid the GPLv3\ncopy/pasting we're now comparing against the fs.f_type.\n\nIf we are hardcoding them, our usual convention is to lower-case\nhexdigits, so 0xbacbacbc not 0xBACBACBC.\n\nBut at least my statfs() manpage documents the named defines in\nlinux/magic.h for most of these. Why not use those?\n\n> +\t\t\treturn 1;\n> +\t\tdefault:\n> +\t\t\tbreak;\n\nYou could just \"return 0\" here, and...\n\n> +\t}\n> +\n> +\treturn 0;\n\n...drop this \"return 0\".\n\n> +}\n> +\n> +static int find_mount(const char *path, const struct statvfs *fs,\n> +\tstruct mntent *ent)\n\nMisindentation.\n\n> +{\n> +\tconst char *const mounts = \"/proc/mounts\";\n> +\tconst char *rp = real_pathdup(path, 1);\n> +\tstruct mntent *ment = NULL;\n> +\tstruct statvfs mntfs;\n> +\tFILE *fp;\n> +\tint found = 0;\n> +\tint dlen, plen, flen = 0;\n> +\n> +\tent->mnt_fsname = NULL;\n> +\tent->mnt_dir = NULL;\n> +\tent->mnt_type = NULL;\n> +\n> +\tfp = setmntent(mounts, \"r\");\n> +\tif (!fp) {\n> +\t\terror_errno(_(\"setmntent('%s') failed\"), mounts);\n> +\t\treturn -1;\n\nDitto \"return error_errno()\"\n\n\n> +\t}\n> +\n> +\tplen = strlen(rp);\n\nLet's make \"plen\", \"dlen\" and \"flen\" a \"size_t\", not \"int\"\n> +\n> +\t/* read all the mount information and compare to path */\n> +\twhile ((ment = getmntent(fp)) != NULL) {\n\nDrop the \"!= NULL\"\n\n> +\t\tif (statvfs(ment->mnt_dir, &mntfs)) {\n> +\t\t\tswitch (errno) {\n> +\t\t\tcase EPERM:\n> +\t\t\tcase ESRCH:\n> +\t\t\tcase EACCES:\n> +\t\t\t\tcontinue;\n> +\t\t\tdefault:\n> +\t\t\t\terror_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n> +\t\t\t\tendmntent(fp);\n\nShouldn't we check the endmntent() error too? Now, from the manpage the\ninterface is funny, and always returns 1.\n\nBut since this is linux-specific code it seems safe enough to go with it\n& glibc assumptions and:\n\n\terrno = 0;\n        endmntent(fp);\n        if (errno)\n        \treturn error_errno(....);\n\nI.e. it'll just call fclose(), which might set errno() on failure.\n\nMaybe it's not worth it...\n\n> +\tif (statvfs(path, &fs))\n> +\t\treturn error_errno(_(\"statvfs('%s') failed\"), path);\n\nHere you do use that \"return error_errno(...)\" pattern...\", yay!\n\n\n> +\n> +\n> +\tif (find_mount(path, &fs, &ment) < 0) {\n> +\t\tfree(ment.mnt_fsname);\n> +\t\tfree(ment.mnt_dir);\n> +\t\tfree(ment.mnt_type);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\ttrace_printf_key(&trace_fsmonitor,\n> +\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n> +\t\t\t path, fs.f_flag, ment.mnt_type, ment.mnt_fsname);\n> +\n> +\tfs_info->is_remote = is_remote_fs(ment.mnt_dir);\n> +\tfs_info->typename = ment.mnt_fsname;\n> +\tfree(ment.mnt_dir);\n> +\tfree(ment.mnt_type);\n\nIf you're going to \\n\\n-seperate this and the trace_printf_key() above I\nthink moving the second free() here to that \"block\" would make sense,\nsinec here is the last time we use mnt_dir, but the last time we used\nmnt_type was in the trace_printf_key().\n\nBut...\n\n> +\n> +\tif (fs_info->is_remote < 0) {\n> +\t\tfree(ment.mnt_fsname);\n\n...aren't you NULL init-ing these, why not just for all of these:\n\n\tgoto error;\n\nThen....\n> +\t\treturn -1;\n> +\t}\n> +\n> +\ttrace_printf_key(&trace_fsmonitor,\n> +\t\t\t\t\"'%s' is_remote: %d\",\n> +\t\t\t\tpath, fs_info->is_remote);\n> +\n> +\treturn 0;\n\nHave this be:\n\n\tint ret = -1; /* earlier */\n\n\tret = 0;\ncleanup:\n\tfree(...);\n\tfree(...);\n\treturn ret;\n\n> +}\n> +\n> +int fsmonitor__is_fs_remote(const char *path)\n> +{\n> +\tstruct fs_info fs;\n> +\n> +\tif (fsmonitor__get_fs_info(path, &fs))\n> +\t\treturn -1;\n> +\n> +\tfree(fs.typename);\n\nThis will segfault if you take the part through fsmonitor__get_fs_info()\nwhere we don't have the fs.typename yet, i.e. if statfs() fails.\n\nThere's the trivial NULL-init way to work around it, but I think this\nsuggests a leaky abstraction. If we fail to get the fs info, then the\nfunction itself should have free'd that, shouldn't it?\n\n> +/*\n> + * No-op for now.\n> + */\n> +char *fsmonitor__resolve_alias(const char *path,\n> +\tconst struct alias_info *info)\n\nDitto misindentatione\n"},{"id":"468882","messageId":"221212.86h6y06gca.gmgdl@evledraar.gmail.com","threadId":"58588","inReplyTo":"80282efef5779728022a317388da6790e5db44e2.1669230044.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 3/6] fsmonitor: implement filesystem change listener for Linux","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-12T10:42:26Z","receivedAt":"2022-12-12T11:08:12Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Nov 23 2022, Eric DeCosta via GitGitGadget wrote:\n\n> From: Eric DeCosta <edecosta@mathworks.com>\n> [...]\n> +}\n> +\n> +/*\n> + * Remove the inotify watch, the watch descriptor to path mapping\n> + * and the reverse mapping.\n> + */\n> +static void remove_watch(struct watch_entry *w,\n> +\tstruct fsm_listen_data *data)\n> +{\n> +\tstruct watch_entry k1, k2, *w1, *w2;\n> +\n> +\t/* remove watch, ignore error if kernel already did it */\n> +\tif (inotify_rm_watch(data->fd_inotify, w->wd) && errno != EINVAL)\n> +\t\terror_errno(\"inotify_rm_watch() failed\");\n\nIf we error_errno(), shouldn't this function have a return value?\n\n> +\n> +\thashmap_entry_init(&k1.ent, memhash(&w->wd, sizeof(int)));\n> +\tw1 = hashmap_remove_entry(&data->watches, &k1, ent, NULL);\n> +\tif (!w1)\n> +\t\tBUG(\"Double remove of watch for '%s'\", w->dir);\n> +\n> +\tif (w1->cookie)\n> +\t\tBUG(\"Removing watch for '%s' which has a pending rename\", w1->dir);\n> +\n> +\thashmap_entry_init(&k2.ent, memhash(w->dir, strlen(w->dir)));\n> +\tw2 = hashmap_remove_entry(&data->revwatches, &k2, ent, NULL);\n> +\tif (!w2)\n> +\t\tBUG(\"Double remove of reverse watch for '%s'\", w->dir);\n\nFor the BUG() additions: Start with lower-case in messages, see\nCodingGuidelines. I.e. \"double remove of ...\" etc. Ditto below..\n\n> [...]\n> +/*\n> + * Handle directory renames\n> + *\n> + * Once a IN_MOVED_TO event is received, lookup the rename tracking information\n> + * via the event cookie and use this information to update the watch.\n> + */\n> +static void rename_dir(uint32_t cookie, const char *path,\n> +\tstruct fsm_listen_data *data)\n> +{\n> +\tstruct rename_entry rek, *re;\n> +\tstruct watch_entry k, *w;\n> +\n> +\t/* lookup a pending rename to match */\n> +\trek.cookie = cookie;\n> +\thashmap_entry_init(&rek.ent, memhash(&rek.cookie, sizeof(uint32_t)));\n> +\tre = hashmap_get_entry(&data->renames, &rek, ent, NULL);\n> +\tif (re) {\n> +\t\tk.dir = re->dir;\n> +\t\thashmap_entry_init(&k.ent, memhash(k.dir, strlen(k.dir)));\n> +\t\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n> +\t\tif (w) {\n> +\t\t\tw->cookie = 0; /* rename handled */\n> +\t\t\tremove_watch(w, data);\n> +\t\t\tadd_watch(path, data);\n\nElsewhere you check the add_watch() return value, but not here?\n\n> +\t\t} else {\n> +\t\t\tBUG(\"No matching watch\");\n> +\t\t}\n> +\t} else {\n> +\t\tBUG(\"No matching cookie\");\n> +\t}\n> +}\n> +\n> +/*\n> + * Recursively add watches to every directory under path\n> + */\n> +static int register_inotify(const char *path,\n> +\tstruct fsmonitor_daemon_state *state,\n> +\tstruct fsmonitor_batch *batch)\n> +{\n> +\tDIR *dir;\n> +\tconst char *rel;\n> +\tstruct strbuf current = STRBUF_INIT;\n> +\tstruct dirent *de;\n> +\tstruct stat fs;\n> +\tint ret = -1;\n> +\n> +\tdir = opendir(path);\n> +\tif (!dir)\n> +\t\treturn error_errno(\"opendir('%s') failed\", path);\n> +\n> +\twhile ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {\n\nditto drop the \"!= NULL\"\n\n> +\tstruct strbuf msg = STRBUF_INIT;\n> +\n> +\tif (mask & IN_ACCESS)\n> +\t\tstrbuf_addstr(&msg, \"IN_ACCESS|\");\n> +\tif (mask & IN_MODIFY)\n> +\t\tstrbuf_addstr(&msg, \"IN_MODIFY|\");\n> +\tif (mask & IN_ATTRIB)\n> +\t\tstrbuf_addstr(&msg, \"IN_ATTRIB|\");\n> +\tif (mask & IN_CLOSE_WRITE)\n> +\t\tstrbuf_addstr(&msg, \"IN_CLOSE_WRITE|\");\n> +\tif (mask & IN_CLOSE_NOWRITE)\n> +\t\tstrbuf_addstr(&msg, \"IN_CLOSE_NOWRITE|\");\n> +\tif (mask & IN_OPEN)\n> +\t\tstrbuf_addstr(&msg, \"IN_OPEN|\");\n> +\tif (mask & IN_MOVED_FROM)\n> +\t\tstrbuf_addstr(&msg, \"IN_MOVED_FROM|\");\n> +\tif (mask & IN_MOVED_TO)\n> +\t\tstrbuf_addstr(&msg, \"IN_MOVED_TO|\");\n> +\tif (mask & IN_CREATE)\n> +\t\tstrbuf_addstr(&msg, \"IN_CREATE|\");\n> +\tif (mask & IN_DELETE)\n> +\t\tstrbuf_addstr(&msg, \"IN_DELETE|\");\n> +\tif (mask & IN_DELETE_SELF)\n> +\t\tstrbuf_addstr(&msg, \"IN_DELETE_SELF|\");\n> +\tif (mask & IN_MOVE_SELF)\n> +\t\tstrbuf_addstr(&msg, \"IN_MOVE_SELF|\");\n> +\tif (mask & IN_UNMOUNT)\n> +\t\tstrbuf_addstr(&msg, \"IN_UNMOUNT|\");\n> +\tif (mask & IN_Q_OVERFLOW)\n> +\t\tstrbuf_addstr(&msg, \"IN_Q_OVERFLOW|\");\n> +\tif (mask & IN_IGNORED)\n> +\t\tstrbuf_addstr(&msg, \"IN_IGNORED|\");\n> +\tif (mask & IN_ISDIR)\n> +\t\tstrbuf_addstr(&msg, \"IN_ISDIR|\");\n\nShouldn't the very last addition to mask omit the \"|\"?\n\nI think it would be worth just making this a NULL-delimited \"int, const char *\" array like:\n\n\t{\n\t\t{ IN_ACCESS, \"IN_ACCESS\" },\n\t\t...\n\t\tNULL,\n\t};\n\nThen looping over it, or maybe not...\n\n> +\ttrace_printf_key(&trace_fsmonitor, \"inotify_event: '%s', mask=%#8.8x %s\",\n> +\t\t\t\tpath, mask, msg.buf);\n> +\n> +\tstrbuf_release(&msg);\n> +}\n> +\n> +int fsm_listen__ctor(struct fsmonitor_daemon_state *state)\n> +{\n> +\tint fd;\n> +\tint ret = 0;\n> +\tstruct fsm_listen_data *data;\n> +\n> +\tCALLOC_ARRAY(data, 1);\n> +\tstate->listen_data = data;\n> +\tstate->listen_error_code = -1;\n> +\tdata->shutdown = SHUTDOWN_ERROR;\n> +\n> +\tfd = inotify_init1(O_NONBLOCK);\n> +\tif (fd < 0)\n> +\t\treturn error_errno(\"inotify_init1() failed\");\n\nHere you leak the \"data\" you just allocated on error.\n\n> +\n> +\tdata->fd_inotify = fd;\n> +\n> +\thashmap_init(&data->watches, watch_entry_cmp, NULL, 0);\n> +\thashmap_init(&data->renames, rename_entry_cmp, NULL, 0);\n> +\thashmap_init(&data->revwatches, revwatches_entry_cmp, NULL, 0);\n> +\n> +\tif (add_watch(state->path_worktree_watch.buf, data))\n\nI.e. we should only avoid free()-ing it if we can successfully hand it\nover to add_watch(), shouldn't we?n\n\n> +\t\tret = -1;\n> +\telse if (register_inotify(state->path_worktree_watch.buf, state, NULL))\n> +\t\tret = -1;\n\nWe add {}'s to all if/else if branches if one needs it, see CodingGuidelines.\n> +\telse if (state->nr_paths_watching > 1) {\n> +\t\tif (add_watch(state->path_gitdir_watch.buf, data))\n> +\t\t\tret = -1;\n> +\t\telse if (register_inotify(state->path_gitdir_watch.buf, state, NULL))\n> +\t\t\tret = -1;\n\nCan't this be:\n\n\telse if (state->nr_paths_watching > 1 &&\n\t\t (add_watch(...) || register_inotify(...)))\n\t\tret = -1;\n\n> +\tswitch (fsmonitor_classify_path_absolute(state, path)) {\n> +\t\tcase IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:\n\nDon't indent the \"case\" label here more than the \"switch\", see\nCodingGuidelines.\n\n\n> +static void handle_events(struct fsmonitor_daemon_state *state)\n> +{\n> +\t /* See https://man7.org/linux/man-pages/man7/inotify.7.html */\n\nExtra \" \" here after the \"\\t\".\n\n> +\tchar buf[4096]\n> +\t\t__attribute__ ((aligned(__alignof__(struct inotify_event))));\n> +\n> +\tstruct hashmap watches = state->listen_data->watches;\n> +\tstruct fsmonitor_batch *batch = NULL;\n> +\tstruct string_list cookie_list = STRING_LIST_INIT_DUP;\n> +\tstruct watch_entry k, *w;\n> +\tstruct strbuf path;\n> +\tconst struct inotify_event *event;\n> +\tint fd = state->listen_data->fd_inotify;\n> +\tssize_t len;\n> +\tchar *ptr, *p;\n> +\n> +\tstrbuf_init(&path, PATH_MAX);\n\nJust use \"struct strbuf path = STRBUF_INIT\" instead? I.e...\n> +\n> +\tfor(;;) {\n> +\t\tlen = read(fd, buf, sizeof(buf));\n> +\t\tif (len == -1 && errno != EAGAIN) {\n> +\t\t\terror_errno(_(\"reading inotify message stream failed\"));\n> +\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n> +\t\t\tgoto done;\n> +\t\t}\n> +\n> +\t\t/* nothing to read */\n> +\t\tif (len <= 0)\n> +\t\t\tgoto done;\n> +\n> +\t\t/* Loop over all events in the buffer. */\n> +\t\tfor (ptr = buf; ptr < buf + len;\n> +\t\t\t ptr += sizeof(struct inotify_event) + event->len) {\n> +\n> +\t\t\tevent = (const struct inotify_event *) ptr;\n> +\n> +\t\t\tif (em_ignore(event->mask))\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\t/* File system was unmounted or event queue overflowed */\n> +\t\t\tif (em_force_shutdown(event->mask)) {\n> +\t\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n> +\t\t\t\t\tlog_mask_set(\"Forcing shutdown\", event->mask);\n> +\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n> +\t\t\t\tgoto done;\n> +\t\t\t}\n> +\n> +\t\t\thashmap_entry_init(&k.ent, memhash(&event->wd, sizeof(int)));\n> +\t\t\tk.wd = event->wd;\n> +\n> +\t\t\tw = hashmap_get_entry(&watches, &k, ent, NULL);\n> +\t\t\tif (!w) /* should never happen */\n> +\t\t\t\tBUG(\"No watch for '%s'\", event->name);\n> +\n> +\t\t\t/* directory watch was removed */\n> +\t\t\tif (em_remove_watch(event->mask)) {\n> +\t\t\t\tremove_watch(w, state->listen_data);\n> +\t\t\t\tcontinue;\n> +\t\t\t}\n> +\n> +\t\t\tstrbuf_reset(&path);\n> +\t\t\tstrbuf_add(&path, w->dir, strlen(w->dir));\n> +\t\t\tstrbuf_addch(&path, '/');\n> +\t\t\tstrbuf_addstr(&path, event->name);\n\n... we may not even get to this, so we may pointlessly pre-grow it, if\nwe're considering the micro-optimization.\n\nBut if we do need it it'll quickly get up to size in the loop, which is\nprobably smaller than PATH_MAX, and paths can exceed PATH_MAX....\n\n> +\tfor(;;) {\n> +\t\tswitch (state->listen_data->shutdown) {\n> +\t\t\tcase SHUTDOWN_CONTINUE:\n> +\t\t\t\tpoll_num = poll(fds, 1, 1);\n> +\t\t\t\tif (poll_num == -1) {\n> +\t\t\t\t\tif (errno == EINTR)\n> +\t\t\t\t\t\tcontinue;\n> +\t\t\t\t\terror_errno(_(\"polling inotify message stream failed\"));\n> +\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n> +\t\t\t\t\tcontinue;\n> +\t\t\t\t}\n> +\n> +\t\t\t\tif ((time(NULL) - checked) >= interval) {\n> +\t\t\t\t\tchecked = time(NULL);\n\nAs this is linux-specific code, shouldn't we use the linux-specific API\nto get the guaranteed atomically growing time here, arther than time()?\n\nMaybe not, but I wonder if this has funny side-effects with ntpd\nadjusting the time concurrently...\n"},{"id":"468943","messageId":"pull.1352.v5.git.git.1670882286.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v4.git.git.1669230044.gitgitgadget@gmail.com","subject":"[PATCH v5 0/6] fsmonitor: Implement fsmonitor for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-12-12T21:57:59Z","receivedAt":"2022-12-12T21:58:14Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\nWindows and Mac OS.\n\nThis patch set builds upon previous work for done for Windows and Mac OS to\nimplement a fsmonitor back-end for Linux based on the Linux inotify API.\ninotify differs significantly from the equivalent Windows and Mac OS APIs in\nthat a watch must be registered for every directory of interest (rather than\na singular watch at the root of the directory tree) and special care must be\ntaken to handle directory renames correctly.\n\nMore information about inotify:\nhttps://man7.org/linux/man-pages/man7/inotify.7.html\n\nv4 differs from v3:\n\n * Code review feedback\n\nv3 differs from v2:\n\n * Avoid potential entanglements with GPLv3\n * Classify a reasonable set of filesystems as being remote\n\nv2 differs from v1:\n\n * Prior work for Windows and Mac OS has been merged to master, reducing the\n   patch set from 12 to 6 patches\n * Code review feedback\n * Identified and resolved race condition revealed by CI test system, see\n   \"Limitations and caveats\" regarding monitoring of directory trees from\n   the man page, above\n * Apologies for being away from this for so long, but my attention was\n   needed elsewhere\n\nv1 differs from v0:\n\n * Code review feedback\n * Update how and which code can be shared between Mac OS and Linux\n * Increase polling frequency to every 1ms (matches Mac OS)\n * Updates to t7527 to improve test stability\n\nEric DeCosta (6):\n  fsmonitor: prepare to share code between Mac OS and Linux\n  fsmonitor: determine if filesystem is local or remote\n  fsmonitor: implement filesystem change listener for Linux\n  fsmonitor: enable fsmonitor for Linux\n  fsmonitor: test updates\n  fsmonitor: update doc for Linux\n\n Documentation/config/fsmonitor--daemon.txt |   4 +-\n Documentation/git-fsmonitor--daemon.txt    |  24 +-\n compat/fsmonitor/fsm-health-linux.c        |  24 +\n compat/fsmonitor/fsm-ipc-darwin.c          |  53 +-\n compat/fsmonitor/fsm-ipc-linux.c           |   1 +\n compat/fsmonitor/fsm-ipc-unix.c            |  52 ++\n compat/fsmonitor/fsm-listen-linux.c        | 676 +++++++++++++++++++++\n compat/fsmonitor/fsm-path-utils-linux.c    | 193 ++++++\n compat/fsmonitor/fsm-settings-darwin.c     |  63 +-\n compat/fsmonitor/fsm-settings-linux.c      |   1 +\n compat/fsmonitor/fsm-settings-unix.c       |  62 ++\n config.mak.uname                           |   8 +\n contrib/buildsystems/CMakeLists.txt        |  11 +-\n t/t7527-builtin-fsmonitor.sh               |  94 ++-\n 14 files changed, 1127 insertions(+), 139 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-health-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-unix.c\n create mode 100644 compat/fsmonitor/fsm-listen-linux.c\n create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.c\n\n\nbase-commit: c48035d29b4e524aed3a32f0403676f0d9128863\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1352%2Fedecosta-mw%2Ffsmonitor_linux-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1352/edecosta-mw/fsmonitor_linux-v5\nPull-Request: https://github.com/git/git/pull/1352\n\nRange-diff vs v4:\n\n 1:  99d684c7bdf ! 1:  7f659603c9a fsmonitor: prepare to share code between Mac OS and Linux\n     @@ compat/fsmonitor/fsm-settings-unix.c (new)\n      +\tstrbuf_addstr(&path, ipc_path);\n      +\n      +\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n     ++\t\tfree(fs.typename);\n      +\t\tstrbuf_release(&path);\n      +\t\treturn FSMONITOR_REASON_ERROR;\n      +\t}\n 2:  e53fc077540 ! 2:  eb3ff9d1c05 fsmonitor: determine if filesystem is local or remote\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +#include <sys/vfs.h>\n      +#include <sys/statvfs.h>\n      +\n     -+static int is_remote_fs(const char* path) {\n     ++static int is_remote_fs(const char *path) {\n      +\tstruct statfs fs;\n      +\n      +\tif (statfs(path, &fs)) {\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +\t}\n      +\n      +\tswitch (fs.f_type) {\n     -+\t\tcase 0x61636673:  /* ACFS */\n     -+\t\tcase 0x5346414F:  /* AFS */\n     -+\t\tcase 0x00C36400:  /* CEPH */\n     -+\t\tcase 0xFF534D42:  /* CIFS */\n     -+\t\tcase 0x73757245:  /* CODA */\n     -+\t\tcase 0x19830326:  /* FHGFS */\n     -+\t\tcase 0x1161970:   /* GFS */\n     -+\t\tcase 0x47504653:  /* GPFS */\n     -+\t\tcase 0x013111A8:  /* IBRIX */\n     -+\t\tcase 0x6B414653:  /* KAFS */\n     -+\t\tcase 0x0BD00BD0:  /* LUSTRE */\n     -+\t\tcase 0x564C:      /* NCP */\n     -+\t\tcase 0x6969:      /* NFS */\n     -+\t\tcase 0x6E667364:  /* NFSD */\n     -+\t\tcase 0x7461636f:  /* OCFS2 */\n     -+\t\tcase 0xAAD7AAEA:  /* PANFS */\n     -+\t\tcase 0x517B:      /* SMB */\n     -+\t\tcase 0xBEEFDEAD:  /* SNFS */\n     -+\t\tcase 0xFE534D42:  /* SMB2 */\n     -+\t\tcase 0xBACBACBC:  /* VMHGFS */\n     -+\t\tcase 0xA501FCF5:  /* VXFS */\n     -+\t\t\treturn 1;\n     -+\t\tdefault:\n     -+\t\t\tbreak;\n     ++\tcase 0x61636673:  /* ACFS */\n     ++\tcase 0x5346414F:  /* AFS */\n     ++\tcase 0x00C36400:  /* CEPH */\n     ++\tcase 0xFF534D42:  /* CIFS */\n     ++\tcase 0x73757245:  /* CODA */\n     ++\tcase 0x19830326:  /* FHGFS */\n     ++\tcase 0x1161970:   /* GFS */\n     ++\tcase 0x47504653:  /* GPFS */\n     ++\tcase 0x013111A8:  /* IBRIX */\n     ++\tcase 0x6B414653:  /* KAFS */\n     ++\tcase 0x0BD00BD0:  /* LUSTRE */\n     ++\tcase 0x564C:      /* NCP */\n     ++\tcase 0x6969:      /* NFS */\n     ++\tcase 0x6E667364:  /* NFSD */\n     ++\tcase 0x7461636f:  /* OCFS2 */\n     ++\tcase 0xAAD7AAEA:  /* PANFS */\n     ++\tcase 0x517B:      /* SMB */\n     ++\tcase 0xBEEFDEAD:  /* SNFS */\n     ++\tcase 0xFE534D42:  /* SMB2 */\n     ++\tcase 0xBACBACBC:  /* VMHGFS */\n     ++\tcase 0xA501FCF5:  /* VXFS */\n     ++\t\treturn 1;\n     ++\tdefault:\n     ++\t\tbreak;\n      +\t}\n      +\n      +\treturn 0;\n      +}\n      +\n      +static int find_mount(const char *path, const struct statvfs *fs,\n     -+\tstruct mntent *ent)\n     ++\tstruct mntent *entry)\n      +{\n      +\tconst char *const mounts = \"/proc/mounts\";\n     -+\tconst char *rp = real_pathdup(path, 1);\n     ++\tchar *rp = real_pathdup(path, 1);\n      +\tstruct mntent *ment = NULL;\n      +\tstruct statvfs mntfs;\n      +\tFILE *fp;\n      +\tint found = 0;\n      +\tint dlen, plen, flen = 0;\n      +\n     -+\tent->mnt_fsname = NULL;\n     -+\tent->mnt_dir = NULL;\n     -+\tent->mnt_type = NULL;\n     ++\tentry->mnt_fsname = NULL;\n     ++\tentry->mnt_dir = NULL;\n     ++\tentry->mnt_type = NULL;\n      +\n      +\tfp = setmntent(mounts, \"r\");\n      +\tif (!fp) {\n     ++\t\tfree(rp);\n      +\t\terror_errno(_(\"setmntent('%s') failed\"), mounts);\n      +\t\treturn -1;\n      +\t}\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +\t\t\tdefault:\n      +\t\t\t\terror_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n      +\t\t\t\tendmntent(fp);\n     ++\t\t\t\tfree(rp);\n      +\t\t\t\treturn -1;\n      +\t\t\t}\n      +\t\t}\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +\t\t\tif (dlen > plen)\n      +\t\t\t\tcontinue;\n      +\t\t\t/*\n     -+\t\t\t * root is always a potential match; otherwise look for\n     -+\t\t\t * directory prefix\n     ++\t\t\t * look for the longest prefix (including root)\n      +\t\t\t */\n     -+\t\t\tif ((dlen == 1 && ment->mnt_dir[0] == '/') ||\n     -+\t\t\t\t(dlen > flen && (!rp[dlen] || rp[dlen] == '/'))) {\n     ++\t\t\tif (dlen > flen &&\n     ++\t\t\t\t((dlen == 1 && ment->mnt_dir[0] == '/') ||\n     ++\t\t\t\t (!rp[dlen] || rp[dlen] == '/'))) {\n      +\t\t\t\tflen = dlen;\n     ++\t\t\t\tfound = 1;\n     ++\n      +\t\t\t\t/*\n      +\t\t\t\t * https://man7.org/linux/man-pages/man3/getmntent.3.html\n      +\t\t\t\t *\n      +\t\t\t\t * The pointer points to a static area of memory which is\n      +\t\t\t\t * overwritten by subsequent calls to getmntent().\n      +\t\t\t\t */\n     -+\t\t\t\tfound = 1;\n     -+\t\t\t\tfree(ent->mnt_fsname);\n     -+\t\t\t\tfree(ent->mnt_dir);\n     -+\t\t\t\tfree(ent->mnt_type);\n     -+\t\t\t\tent->mnt_fsname = xstrdup(ment->mnt_fsname);\n     -+\t\t\t\tent->mnt_dir = xstrdup(ment->mnt_dir);\n     -+\t\t\t\tent->mnt_type = xstrdup(ment->mnt_type);\n     ++\t\t\t\tfree(entry->mnt_fsname);\n     ++\t\t\t\tfree(entry->mnt_dir);\n     ++\t\t\t\tfree(entry->mnt_type);\n     ++\t\t\t\tentry->mnt_fsname = xstrdup(ment->mnt_fsname);\n     ++\t\t\t\tentry->mnt_dir = xstrdup(ment->mnt_dir);\n     ++\t\t\t\tentry->mnt_type = xstrdup(ment->mnt_type);\n      +\t\t\t}\n      +\t\t}\n      +\t}\n      +\tendmntent(fp);\n     ++\tfree(rp);\n      +\n      +\tif (!found)\n      +\t\treturn -1;\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +\n      +int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n      +{\n     -+\tstruct mntent ment;\n     ++\tstruct mntent entry;\n      +\tstruct statvfs fs;\n      +\n     ++\tfs_info->is_remote = -1;\n     ++\tfs_info->typename = NULL;\n     ++\n      +\tif (statvfs(path, &fs))\n      +\t\treturn error_errno(_(\"statvfs('%s') failed\"), path);\n      +\n     -+\n     -+\tif (find_mount(path, &fs, &ment) < 0) {\n     -+\t\tfree(ment.mnt_fsname);\n     -+\t\tfree(ment.mnt_dir);\n     -+\t\tfree(ment.mnt_type);\n     ++\tif (find_mount(path, &fs, &entry) < 0) {\n     ++\t\tfree(entry.mnt_fsname);\n     ++\t\tfree(entry.mnt_dir);\n     ++\t\tfree(entry.mnt_type);\n      +\t\treturn -1;\n      +\t}\n      +\n      +\ttrace_printf_key(&trace_fsmonitor,\n      +\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n     -+\t\t\t path, fs.f_flag, ment.mnt_type, ment.mnt_fsname);\n     ++\t\t\t path, fs.f_flag, entry.mnt_type, entry.mnt_fsname);\n      +\n     -+\tfs_info->is_remote = is_remote_fs(ment.mnt_dir);\n     -+\tfs_info->typename = ment.mnt_fsname;\n     -+\tfree(ment.mnt_dir);\n     -+\tfree(ment.mnt_type);\n     ++\tfs_info->is_remote = is_remote_fs(entry.mnt_dir);\n     ++\tfs_info->typename = xstrdup(entry.mnt_fsname);\n     ++\tfree(entry.mnt_fsname);\n     ++\tfree(entry.mnt_dir);\n     ++\tfree(entry.mnt_type);\n      +\n     -+\tif (fs_info->is_remote < 0) {\n     -+\t\tfree(ment.mnt_fsname);\n     ++\tif (fs_info->is_remote < 0)\n      +\t\treturn -1;\n     -+\t}\n      +\n      +\ttrace_printf_key(&trace_fsmonitor,\n      +\t\t\t\t\"'%s' is_remote: %d\",\n     @@ compat/fsmonitor/fsm-path-utils-linux.c (new)\n      +{\n      +\tstruct fs_info fs;\n      +\n     -+\tif (fsmonitor__get_fs_info(path, &fs))\n     ++\tif (fsmonitor__get_fs_info(path, &fs)) {\n     ++\t\tfree(fs.typename);\n      +\t\treturn -1;\n     ++\t}\n      +\n      +\tfree(fs.typename);\n      +\n 3:  80282efef57 = 3:  e093a2703b1 fsmonitor: implement filesystem change listener for Linux\n 4:  cb03803e355 = 4:  c03070fb0a2 fsmonitor: enable fsmonitor for Linux\n 5:  8d9d469b356 = 5:  6a7b554642c fsmonitor: test updates\n 6:  5afd03fa6ca = 6:  827410c22ee fsmonitor: update doc for Linux\n\n-- \ngitgitgadget\n"},{"id":"468944","messageId":"7f659603c9ac1457ef581fb82d7fe411bdc7230f.1670882286.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v5.git.git.1670882286.gitgitgadget@gmail.com","subject":"[PATCH v5 1/6] fsmonitor: prepare to share code between Mac OS and Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-12-12T21:58:00Z","receivedAt":"2022-12-12T21:58:19Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nLinux and Mac OS can share some of the code originally developed for Mac OS.\n\nMac OS and Linux can share fsm-ipc-unix.c and fsm-settings-unix.c\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-health-linux.c    | 24 ++++++++++\n compat/fsmonitor/fsm-ipc-darwin.c      | 53 +---------------------\n compat/fsmonitor/fsm-ipc-linux.c       |  1 +\n compat/fsmonitor/fsm-ipc-unix.c        | 52 +++++++++++++++++++++\n compat/fsmonitor/fsm-settings-darwin.c | 63 +-------------------------\n compat/fsmonitor/fsm-settings-linux.c  |  1 +\n compat/fsmonitor/fsm-settings-unix.c   | 62 +++++++++++++++++++++++++\n 7 files changed, 142 insertions(+), 114 deletions(-)\n create mode 100644 compat/fsmonitor/fsm-health-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-linux.c\n create mode 100644 compat/fsmonitor/fsm-ipc-unix.c\n create mode 100644 compat/fsmonitor/fsm-settings-linux.c\n create mode 100644 compat/fsmonitor/fsm-settings-unix.c\n\ndiff --git a/compat/fsmonitor/fsm-health-linux.c b/compat/fsmonitor/fsm-health-linux.c\nnew file mode 100644\nindex 00000000000..b9f709e8548\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-health-linux.c\n@@ -0,0 +1,24 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsm-health.h\"\n+#include \"fsmonitor--daemon.h\"\n+\n+int fsm_health__ctor(struct fsmonitor_daemon_state *state)\n+{\n+\treturn 0;\n+}\n+\n+void fsm_health__dtor(struct fsmonitor_daemon_state *state)\n+{\n+\treturn;\n+}\n+\n+void fsm_health__loop(struct fsmonitor_daemon_state *state)\n+{\n+\treturn;\n+}\n+\n+void fsm_health__stop_async(struct fsmonitor_daemon_state *state)\n+{\n+}\ndiff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-darwin.c\nindex d67b0ee50d3..4c3c92081ee 100644\n--- a/compat/fsmonitor/fsm-ipc-darwin.c\n+++ b/compat/fsmonitor/fsm-ipc-darwin.c\n@@ -1,52 +1 @@\n-#include \"cache.h\"\n-#include \"config.h\"\n-#include \"strbuf.h\"\n-#include \"fsmonitor.h\"\n-#include \"fsmonitor-ipc.h\"\n-#include \"fsmonitor-path-utils.h\"\n-\n-static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n-\n-const char *fsmonitor_ipc__get_path(struct repository *r)\n-{\n-\tstatic const char *ipc_path = NULL;\n-\tgit_SHA_CTX sha1ctx;\n-\tchar *sock_dir = NULL;\n-\tstruct strbuf ipc_file = STRBUF_INIT;\n-\tunsigned char hash[GIT_MAX_RAWSZ];\n-\n-\tif (!r)\n-\t\tBUG(\"No repository passed into fsmonitor_ipc__get_path\");\n-\n-\tif (ipc_path)\n-\t\treturn ipc_path;\n-\n-\n-\t/* By default the socket file is created in the .git directory */\n-\tif (fsmonitor__is_fs_remote(r->gitdir) < 1) {\n-\t\tipc_path = fsmonitor_ipc__get_default_path();\n-\t\treturn ipc_path;\n-\t}\n-\n-\tgit_SHA1_Init(&sha1ctx);\n-\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n-\tgit_SHA1_Final(hash, &sha1ctx);\n-\n-\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n-\n-\t/* Create the socket file in either socketDir or $HOME */\n-\tif (sock_dir && *sock_dir) {\n-\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n-\t\t\t\t\tsock_dir, hash_to_hex(hash));\n-\t} else {\n-\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n-\t}\n-\tfree(sock_dir);\n-\n-\tipc_path = interpolate_path(ipc_file.buf, 1);\n-\tif (!ipc_path)\n-\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n-\n-\tstrbuf_release(&ipc_file);\n-\treturn ipc_path;\n-}\n+#include \"fsm-ipc-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-ipc-linux.c b/compat/fsmonitor/fsm-ipc-linux.c\nnew file mode 100644\nindex 00000000000..4c3c92081ee\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-ipc-linux.c\n@@ -0,0 +1 @@\n+#include \"fsm-ipc-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-ipc-unix.c b/compat/fsmonitor/fsm-ipc-unix.c\nnew file mode 100644\nindex 00000000000..d67b0ee50d3\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-ipc-unix.c\n@@ -0,0 +1,52 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"strbuf.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n+\n+static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, \"fsmonitor--daemon.ipc\")\n+\n+const char *fsmonitor_ipc__get_path(struct repository *r)\n+{\n+\tstatic const char *ipc_path = NULL;\n+\tgit_SHA_CTX sha1ctx;\n+\tchar *sock_dir = NULL;\n+\tstruct strbuf ipc_file = STRBUF_INIT;\n+\tunsigned char hash[GIT_MAX_RAWSZ];\n+\n+\tif (!r)\n+\t\tBUG(\"No repository passed into fsmonitor_ipc__get_path\");\n+\n+\tif (ipc_path)\n+\t\treturn ipc_path;\n+\n+\n+\t/* By default the socket file is created in the .git directory */\n+\tif (fsmonitor__is_fs_remote(r->gitdir) < 1) {\n+\t\tipc_path = fsmonitor_ipc__get_default_path();\n+\t\treturn ipc_path;\n+\t}\n+\n+\tgit_SHA1_Init(&sha1ctx);\n+\tgit_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));\n+\tgit_SHA1_Final(hash, &sha1ctx);\n+\n+\trepo_config_get_string(r, \"fsmonitor.socketdir\", &sock_dir);\n+\n+\t/* Create the socket file in either socketDir or $HOME */\n+\tif (sock_dir && *sock_dir) {\n+\t\tstrbuf_addf(&ipc_file, \"%s/.git-fsmonitor-%s\",\n+\t\t\t\t\tsock_dir, hash_to_hex(hash));\n+\t} else {\n+\t\tstrbuf_addf(&ipc_file, \"~/.git-fsmonitor-%s\", hash_to_hex(hash));\n+\t}\n+\tfree(sock_dir);\n+\n+\tipc_path = interpolate_path(ipc_file.buf, 1);\n+\tif (!ipc_path)\n+\t\tdie(_(\"Invalid path: %s\"), ipc_file.buf);\n+\n+\tstrbuf_release(&ipc_file);\n+\treturn ipc_path;\n+}\ndiff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c\nindex 6abbc7af3ab..14baf9f0603 100644\n--- a/compat/fsmonitor/fsm-settings-darwin.c\n+++ b/compat/fsmonitor/fsm-settings-darwin.c\n@@ -1,62 +1 @@\n-#include \"config.h\"\n-#include \"fsmonitor.h\"\n-#include \"fsmonitor-ipc.h\"\n-#include \"fsmonitor-settings.h\"\n-#include \"fsmonitor-path-utils.h\"\n-\n- /*\n- * For the builtin FSMonitor, we create the Unix domain socket for the\n- * IPC in the .git directory.  If the working directory is remote,\n- * then the socket will be created on the remote file system.  This\n- * can fail if the remote file system does not support UDS file types\n- * (e.g. smbfs to a Windows server) or if the remote kernel does not\n- * allow a non-local process to bind() the socket.  (These problems\n- * could be fixed by moving the UDS out of the .git directory and to a\n- * well-known local directory on the client machine, but care should\n- * be taken to ensure that $HOME is actually local and not a managed\n- * file share.)\n- *\n- * FAT32 and NTFS working directories are problematic too.\n- *\n- * The builtin FSMonitor uses a Unix domain socket in the .git\n- * directory for IPC.  These Windows drive formats do not support\n- * Unix domain sockets, so mark them as incompatible for the daemon.\n- *\n- */\n-static enum fsmonitor_reason check_uds_volume(struct repository *r)\n-{\n-\tstruct fs_info fs;\n-\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n-\tstruct strbuf path = STRBUF_INIT;\n-\tstrbuf_add(&path, ipc_path, strlen(ipc_path));\n-\n-\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n-\t\tstrbuf_release(&path);\n-\t\treturn FSMONITOR_REASON_ERROR;\n-\t}\n-\n-\tstrbuf_release(&path);\n-\n-\tif (fs.is_remote ||\n-\t\t!strcmp(fs.typename, \"msdos\") ||\n-\t\t!strcmp(fs.typename, \"ntfs\")) {\n-\t\tfree(fs.typename);\n-\t\treturn FSMONITOR_REASON_NOSOCKETS;\n-\t}\n-\n-\tfree(fs.typename);\n-\treturn FSMONITOR_REASON_OK;\n-}\n-\n-enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n-{\n-\tenum fsmonitor_reason reason;\n-\n-\tif (ipc) {\n-\t\treason = check_uds_volume(r);\n-\t\tif (reason != FSMONITOR_REASON_OK)\n-\t\t\treturn reason;\n-\t}\n-\n-\treturn FSMONITOR_REASON_OK;\n-}\n+#include \"fsm-settings-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-settings-linux.c b/compat/fsmonitor/fsm-settings-linux.c\nnew file mode 100644\nindex 00000000000..14baf9f0603\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-settings-linux.c\n@@ -0,0 +1 @@\n+#include \"fsm-settings-unix.c\"\ndiff --git a/compat/fsmonitor/fsm-settings-unix.c b/compat/fsmonitor/fsm-settings-unix.c\nnew file mode 100644\nindex 00000000000..da23254be40\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-settings-unix.c\n@@ -0,0 +1,62 @@\n+#include \"config.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-ipc.h\"\n+#include \"fsmonitor-path-utils.h\"\n+\n+ /*\n+ * For the builtin FSMonitor, we create the Unix domain socket for the\n+ * IPC in the .git directory.  If the working directory is remote,\n+ * then the socket will be created on the remote file system.  This\n+ * can fail if the remote file system does not support UDS file types\n+ * (e.g. smbfs to a Windows server) or if the remote kernel does not\n+ * allow a non-local process to bind() the socket.  (These problems\n+ * could be fixed by moving the UDS out of the .git directory and to a\n+ * well-known local directory on the client machine, but care should\n+ * be taken to ensure that $HOME is actually local and not a managed\n+ * file share.)\n+ *\n+ * FAT32 and NTFS working directories are problematic too.\n+ *\n+ * The builtin FSMonitor uses a Unix domain socket in the .git\n+ * directory for IPC.  These Windows drive formats do not support\n+ * Unix domain sockets, so mark them as incompatible for the daemon.\n+ *\n+ */\n+static enum fsmonitor_reason check_uds_volume(struct repository *r)\n+{\n+\tstruct fs_info fs;\n+\tconst char *ipc_path = fsmonitor_ipc__get_path(r);\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstrbuf_addstr(&path, ipc_path);\n+\n+\tif (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {\n+\t\tfree(fs.typename);\n+\t\tstrbuf_release(&path);\n+\t\treturn FSMONITOR_REASON_ERROR;\n+\t}\n+\n+\tstrbuf_release(&path);\n+\n+\tif (fs.is_remote ||\n+\t\t!strcmp(fs.typename, \"msdos\") ||\n+\t\t!strcmp(fs.typename, \"ntfs\")) {\n+\t\tfree(fs.typename);\n+\t\treturn FSMONITOR_REASON_NOSOCKETS;\n+\t}\n+\n+\tfree(fs.typename);\n+\treturn FSMONITOR_REASON_OK;\n+}\n+\n+enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)\n+{\n+\tenum fsmonitor_reason reason;\n+\n+\tif (ipc) {\n+\t\treason = check_uds_volume(r);\n+\t\tif (reason != FSMONITOR_REASON_OK)\n+\t\t\treturn reason;\n+\t}\n+\n+\treturn FSMONITOR_REASON_OK;\n+}\n-- \ngitgitgadget\n\n"},{"id":"468945","messageId":"eb3ff9d1c056ed1e9b7cf62ca5afab0203d1bb46.1670882286.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v5.git.git.1670882286.gitgitgadget@gmail.com","subject":"[PATCH v5 2/6] fsmonitor: determine if filesystem is local or remote","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-12-12T21:58:01Z","receivedAt":"2022-12-12T21:58:22Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nCompare the given path to the mounted filesystems. Find the mount that is\nthe longest prefix of the path (if any) and determine if that mount is on a\nlocal or remote filesystem.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-path-utils-linux.c | 193 ++++++++++++++++++++++++\n 1 file changed, 193 insertions(+)\n create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c\n\ndiff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c\nnew file mode 100644\nindex 00000000000..629731f75f6\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-path-utils-linux.c\n@@ -0,0 +1,193 @@\n+#include \"fsmonitor.h\"\n+#include \"fsmonitor-path-utils.h\"\n+#include <errno.h>\n+#include <mntent.h>\n+#include <sys/mount.h>\n+#include <sys/vfs.h>\n+#include <sys/statvfs.h>\n+\n+static int is_remote_fs(const char *path) {\n+\tstruct statfs fs;\n+\n+\tif (statfs(path, &fs)) {\n+\t\terror_errno(_(\"statfs('%s') failed\"), path);\n+\t\treturn -1;\n+\t}\n+\n+\tswitch (fs.f_type) {\n+\tcase 0x61636673:  /* ACFS */\n+\tcase 0x5346414F:  /* AFS */\n+\tcase 0x00C36400:  /* CEPH */\n+\tcase 0xFF534D42:  /* CIFS */\n+\tcase 0x73757245:  /* CODA */\n+\tcase 0x19830326:  /* FHGFS */\n+\tcase 0x1161970:   /* GFS */\n+\tcase 0x47504653:  /* GPFS */\n+\tcase 0x013111A8:  /* IBRIX */\n+\tcase 0x6B414653:  /* KAFS */\n+\tcase 0x0BD00BD0:  /* LUSTRE */\n+\tcase 0x564C:      /* NCP */\n+\tcase 0x6969:      /* NFS */\n+\tcase 0x6E667364:  /* NFSD */\n+\tcase 0x7461636f:  /* OCFS2 */\n+\tcase 0xAAD7AAEA:  /* PANFS */\n+\tcase 0x517B:      /* SMB */\n+\tcase 0xBEEFDEAD:  /* SNFS */\n+\tcase 0xFE534D42:  /* SMB2 */\n+\tcase 0xBACBACBC:  /* VMHGFS */\n+\tcase 0xA501FCF5:  /* VXFS */\n+\t\treturn 1;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int find_mount(const char *path, const struct statvfs *fs,\n+\tstruct mntent *entry)\n+{\n+\tconst char *const mounts = \"/proc/mounts\";\n+\tchar *rp = real_pathdup(path, 1);\n+\tstruct mntent *ment = NULL;\n+\tstruct statvfs mntfs;\n+\tFILE *fp;\n+\tint found = 0;\n+\tint dlen, plen, flen = 0;\n+\n+\tentry->mnt_fsname = NULL;\n+\tentry->mnt_dir = NULL;\n+\tentry->mnt_type = NULL;\n+\n+\tfp = setmntent(mounts, \"r\");\n+\tif (!fp) {\n+\t\tfree(rp);\n+\t\terror_errno(_(\"setmntent('%s') failed\"), mounts);\n+\t\treturn -1;\n+\t}\n+\n+\tplen = strlen(rp);\n+\n+\t/* read all the mount information and compare to path */\n+\twhile ((ment = getmntent(fp)) != NULL) {\n+\t\tif (statvfs(ment->mnt_dir, &mntfs)) {\n+\t\t\tswitch (errno) {\n+\t\t\tcase EPERM:\n+\t\t\tcase ESRCH:\n+\t\t\tcase EACCES:\n+\t\t\t\tcontinue;\n+\t\t\tdefault:\n+\t\t\t\terror_errno(_(\"statvfs('%s') failed\"), ment->mnt_dir);\n+\t\t\t\tendmntent(fp);\n+\t\t\t\tfree(rp);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* is mount on the same filesystem and is a prefix of the path */\n+\t\tif ((fs->f_fsid == mntfs.f_fsid) &&\n+\t\t\t!strncmp(ment->mnt_dir, rp, strlen(ment->mnt_dir))) {\n+\t\t\tdlen = strlen(ment->mnt_dir);\n+\t\t\tif (dlen > plen)\n+\t\t\t\tcontinue;\n+\t\t\t/*\n+\t\t\t * look for the longest prefix (including root)\n+\t\t\t */\n+\t\t\tif (dlen > flen &&\n+\t\t\t\t((dlen == 1 && ment->mnt_dir[0] == '/') ||\n+\t\t\t\t (!rp[dlen] || rp[dlen] == '/'))) {\n+\t\t\t\tflen = dlen;\n+\t\t\t\tfound = 1;\n+\n+\t\t\t\t/*\n+\t\t\t\t * https://man7.org/linux/man-pages/man3/getmntent.3.html\n+\t\t\t\t *\n+\t\t\t\t * The pointer points to a static area of memory which is\n+\t\t\t\t * overwritten by subsequent calls to getmntent().\n+\t\t\t\t */\n+\t\t\t\tfree(entry->mnt_fsname);\n+\t\t\t\tfree(entry->mnt_dir);\n+\t\t\t\tfree(entry->mnt_type);\n+\t\t\t\tentry->mnt_fsname = xstrdup(ment->mnt_fsname);\n+\t\t\t\tentry->mnt_dir = xstrdup(ment->mnt_dir);\n+\t\t\t\tentry->mnt_type = xstrdup(ment->mnt_type);\n+\t\t\t}\n+\t\t}\n+\t}\n+\tendmntent(fp);\n+\tfree(rp);\n+\n+\tif (!found)\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)\n+{\n+\tstruct mntent entry;\n+\tstruct statvfs fs;\n+\n+\tfs_info->is_remote = -1;\n+\tfs_info->typename = NULL;\n+\n+\tif (statvfs(path, &fs))\n+\t\treturn error_errno(_(\"statvfs('%s') failed\"), path);\n+\n+\tif (find_mount(path, &fs, &entry) < 0) {\n+\t\tfree(entry.mnt_fsname);\n+\t\tfree(entry.mnt_dir);\n+\t\tfree(entry.mnt_type);\n+\t\treturn -1;\n+\t}\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t \"statvfs('%s') [flags 0x%08lx] '%s' '%s'\",\n+\t\t\t path, fs.f_flag, entry.mnt_type, entry.mnt_fsname);\n+\n+\tfs_info->is_remote = is_remote_fs(entry.mnt_dir);\n+\tfs_info->typename = xstrdup(entry.mnt_fsname);\n+\tfree(entry.mnt_fsname);\n+\tfree(entry.mnt_dir);\n+\tfree(entry.mnt_type);\n+\n+\tif (fs_info->is_remote < 0)\n+\t\treturn -1;\n+\n+\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\"'%s' is_remote: %d\",\n+\t\t\t\tpath, fs_info->is_remote);\n+\n+\treturn 0;\n+}\n+\n+int fsmonitor__is_fs_remote(const char *path)\n+{\n+\tstruct fs_info fs;\n+\n+\tif (fsmonitor__get_fs_info(path, &fs)) {\n+\t\tfree(fs.typename);\n+\t\treturn -1;\n+\t}\n+\n+\tfree(fs.typename);\n+\n+\treturn fs.is_remote;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+int fsmonitor__get_alias(const char *path, struct alias_info *info)\n+{\n+\treturn 0;\n+}\n+\n+/*\n+ * No-op for now.\n+ */\n+char *fsmonitor__resolve_alias(const char *path,\n+\tconst struct alias_info *info)\n+{\n+\treturn NULL;\n+}\n-- \ngitgitgadget\n\n"},{"id":"468946","messageId":"c03070fb0a2a3e8b5588ae8c4ec868aeb9e7f4b6.1670882286.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v5.git.git.1670882286.gitgitgadget@gmail.com","subject":"[PATCH v5 4/6] fsmonitor: enable fsmonitor for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-12-12T21:58:03Z","receivedAt":"2022-12-12T21:58:30Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nUpdate build to enable fsmonitor for Linux.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n config.mak.uname                    |  8 ++++++++\n contrib/buildsystems/CMakeLists.txt | 11 ++++++++++-\n 2 files changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/config.mak.uname b/config.mak.uname\nindex d63629fe807..5890d810463 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -68,6 +68,14 @@ ifeq ($(uname_S),Linux)\n \tifneq ($(findstring .el7.,$(uname_R)),)\n \t\tBASIC_CFLAGS += -std=c99\n \tendif\n+\t# The builtin FSMonitor on Linux builds upon Simple-IPC.  Both require\n+\t# Unix domain sockets and PThreads.\n+\tifndef NO_PTHREADS\n+\tifndef NO_UNIX_SOCKETS\n+\tFSMONITOR_DAEMON_BACKEND = linux\n+\tFSMONITOR_OS_SETTINGS = linux\n+\tendif\n+\tendif\n endif\n ifeq ($(uname_S),GNU/kFreeBSD)\n \tHAVE_ALLOCA_H = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 2f6e0197ffa..a0319527279 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -304,7 +304,16 @@ else()\n endif()\n \n if(SUPPORTS_SIMPLE_IPC)\n-\tif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n+\tif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n+\t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-linux.c)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-linux.c)\n+\n+\t\tadd_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)\n+\t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-linux.c)\n+\telseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n \t\tadd_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c)\n \t\tlist(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c)\n-- \ngitgitgadget\n\n"},{"id":"468947","messageId":"e093a2703b1caa8ca1474d38519b569cbb81d5d4.1670882286.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v5.git.git.1670882286.gitgitgadget@gmail.com","subject":"[PATCH v5 3/6] fsmonitor: implement filesystem change listener for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-12-12T21:58:02Z","receivedAt":"2022-12-12T21:58:32Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nImplement a filesystem change listener for Linux based on the inotify API:\nhttps://man7.org/linux/man-pages/man7/inotify.7.html\n\ninotify requires registering a watch on every directory in the worktree and\nspecial handling of moves/renames.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n compat/fsmonitor/fsm-listen-linux.c | 676 ++++++++++++++++++++++++++++\n 1 file changed, 676 insertions(+)\n create mode 100644 compat/fsmonitor/fsm-listen-linux.c\n\ndiff --git a/compat/fsmonitor/fsm-listen-linux.c b/compat/fsmonitor/fsm-listen-linux.c\nnew file mode 100644\nindex 00000000000..e8548e4e009\n--- /dev/null\n+++ b/compat/fsmonitor/fsm-listen-linux.c\n@@ -0,0 +1,676 @@\n+#include \"cache.h\"\n+#include \"fsmonitor.h\"\n+#include \"fsm-listen.h\"\n+#include \"fsmonitor--daemon.h\"\n+#include <dirent.h>\n+#include <fcntl.h>\n+#include <sys/inotify.h>\n+#include <sys/stat.h>\n+\n+/*\n+ * Safe value to bitwise OR with rest of mask for\n+ * kernels that do not support IN_MASK_CREATE\n+ */\n+#ifndef IN_MASK_CREATE\n+#define IN_MASK_CREATE 0x00000000\n+#endif\n+\n+enum shutdown_reason {\n+\tSHUTDOWN_CONTINUE = 0,\n+\tSHUTDOWN_STOP,\n+\tSHUTDOWN_ERROR,\n+\tSHUTDOWN_FORCE\n+};\n+\n+struct watch_entry {\n+\tstruct hashmap_entry ent;\n+\tint wd;\n+\tuint32_t cookie;\n+\tconst char *dir;\n+};\n+\n+struct rename_entry {\n+\tstruct hashmap_entry ent;\n+\ttime_t whence;\n+\tuint32_t cookie;\n+\tconst char *dir;\n+};\n+\n+struct fsm_listen_data {\n+\tint fd_inotify;\n+\tenum shutdown_reason shutdown;\n+\tstruct hashmap watches;\n+\tstruct hashmap renames;\n+\tstruct hashmap revwatches;\n+};\n+\n+static int watch_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct watch_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct watch_entry, ent);\n+\te2 = container_of(eptr, const struct watch_entry, ent);\n+\treturn e1->wd != e2->wd;\n+}\n+\n+static int revwatches_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct watch_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct watch_entry, ent);\n+\te2 = container_of(eptr, const struct watch_entry, ent);\n+\treturn strcmp(e1->dir, e2->dir);\n+}\n+\n+static int rename_entry_cmp(const void *cmp_data,\n+\t\t\t  const struct hashmap_entry *eptr,\n+\t\t\t  const struct hashmap_entry *entry_or_key,\n+\t\t\t  const void *keydata)\n+{\n+\tconst struct rename_entry *e1, *e2;\n+\n+\te1 = container_of(eptr, const struct rename_entry, ent);\n+\te2 = container_of(eptr, const struct rename_entry, ent);\n+\treturn e1->cookie != e2->cookie;\n+}\n+\n+/*\n+ * Register an inotify watch, add watch descriptor to path mapping\n+ * and the reverse mapping.\n+ */\n+static int add_watch(const char *path, struct fsm_listen_data *data)\n+{\n+\tconst char *interned = strintern(path);\n+\tstruct watch_entry *w1, *w2;\n+\n+\t/* add the inotify watch, don't allow watches to be modified */\n+\tint wd = inotify_add_watch(data->fd_inotify, interned,\n+\t\t\t\t(IN_ALL_EVENTS | IN_ONLYDIR | IN_MASK_CREATE)\n+\t\t\t\t^ IN_ACCESS ^ IN_CLOSE ^ IN_OPEN);\n+\tif (wd < 0)\n+\t\treturn error_errno(\"inotify_add_watch('%s') failed\", interned);\n+\n+\t/* add watch descriptor -> directory mapping */\n+\tCALLOC_ARRAY(w1, 1);\n+\tw1->wd = wd;\n+\tw1->dir = interned;\n+\thashmap_entry_init(&w1->ent, memhash(&w1->wd, sizeof(int)));\n+\thashmap_add(&data->watches, &w1->ent);\n+\n+\t/* add directory -> watch descriptor mapping */\n+\tCALLOC_ARRAY(w2, 1);\n+\tw2->wd = wd;\n+\tw2->dir = interned;\n+\thashmap_entry_init(&w2->ent, memhash(w2->dir, strlen(w2->dir)));\n+\thashmap_add(&data->revwatches, &w2->ent);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Remove the inotify watch, the watch descriptor to path mapping\n+ * and the reverse mapping.\n+ */\n+static void remove_watch(struct watch_entry *w,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct watch_entry k1, k2, *w1, *w2;\n+\n+\t/* remove watch, ignore error if kernel already did it */\n+\tif (inotify_rm_watch(data->fd_inotify, w->wd) && errno != EINVAL)\n+\t\terror_errno(\"inotify_rm_watch() failed\");\n+\n+\thashmap_entry_init(&k1.ent, memhash(&w->wd, sizeof(int)));\n+\tw1 = hashmap_remove_entry(&data->watches, &k1, ent, NULL);\n+\tif (!w1)\n+\t\tBUG(\"Double remove of watch for '%s'\", w->dir);\n+\n+\tif (w1->cookie)\n+\t\tBUG(\"Removing watch for '%s' which has a pending rename\", w1->dir);\n+\n+\thashmap_entry_init(&k2.ent, memhash(w->dir, strlen(w->dir)));\n+\tw2 = hashmap_remove_entry(&data->revwatches, &k2, ent, NULL);\n+\tif (!w2)\n+\t\tBUG(\"Double remove of reverse watch for '%s'\", w->dir);\n+\n+\t/* w1->dir and w2->dir are interned strings, we don't own them */\n+\tfree(w1);\n+\tfree(w2);\n+}\n+\n+/*\n+ * Check for stale directory renames.\n+ *\n+ * https://man7.org/linux/man-pages/man7/inotify.7.html\n+ *\n+ * Allow for some small timeout to account for the fact that insertion of the\n+ * IN_MOVED_FROM+IN_MOVED_TO event pair is not atomic, and the possibility that\n+ * there may not be any IN_MOVED_TO event.\n+ *\n+ * If the IN_MOVED_TO event is not received within the timeout then events have\n+ * been missed and the monitor is in an inconsistent state with respect to the\n+ * filesystem.\n+ */\n+static int check_stale_dir_renames(struct hashmap *renames, time_t max_age)\n+{\n+\tstruct rename_entry *re;\n+\tstruct hashmap_iter iter;\n+\n+\thashmap_for_each_entry(renames, &iter, re, ent) {\n+\t\tif (re->whence <= max_age)\n+\t\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Track pending renames.\n+ *\n+ * Tracking is done via a event cookie to watch descriptor mapping.\n+ *\n+ * A rename is not complete until matching a IN_MOVED_TO event is received\n+ * for a corresponding IN_MOVED_FROM event.\n+ */\n+static void add_dir_rename(uint32_t cookie, const char *path,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct watch_entry k, *w;\n+\tstruct rename_entry *re;\n+\n+\t/* lookup the watch descriptor for the given path */\n+\thashmap_entry_init(&k.ent, memhash(path, strlen(path)));\n+\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n+\tif (!w) /* should never happen */\n+\t\tBUG(\"No watch for '%s'\", path);\n+\tw->cookie = cookie;\n+\n+\t/* add the pending rename to match against later */\n+\tCALLOC_ARRAY(re, 1);\n+\tre->dir = w->dir;\n+\tre->cookie = w->cookie;\n+\tre->whence = time(NULL);\n+\thashmap_entry_init(&re->ent, memhash(&re->cookie, sizeof(uint32_t)));\n+\thashmap_add(&data->renames, &re->ent);\n+}\n+\n+/*\n+ * Handle directory renames\n+ *\n+ * Once a IN_MOVED_TO event is received, lookup the rename tracking information\n+ * via the event cookie and use this information to update the watch.\n+ */\n+static void rename_dir(uint32_t cookie, const char *path,\n+\tstruct fsm_listen_data *data)\n+{\n+\tstruct rename_entry rek, *re;\n+\tstruct watch_entry k, *w;\n+\n+\t/* lookup a pending rename to match */\n+\trek.cookie = cookie;\n+\thashmap_entry_init(&rek.ent, memhash(&rek.cookie, sizeof(uint32_t)));\n+\tre = hashmap_get_entry(&data->renames, &rek, ent, NULL);\n+\tif (re) {\n+\t\tk.dir = re->dir;\n+\t\thashmap_entry_init(&k.ent, memhash(k.dir, strlen(k.dir)));\n+\t\tw = hashmap_get_entry(&data->revwatches, &k, ent, NULL);\n+\t\tif (w) {\n+\t\t\tw->cookie = 0; /* rename handled */\n+\t\t\tremove_watch(w, data);\n+\t\t\tadd_watch(path, data);\n+\t\t} else {\n+\t\t\tBUG(\"No matching watch\");\n+\t\t}\n+\t} else {\n+\t\tBUG(\"No matching cookie\");\n+\t}\n+}\n+\n+/*\n+ * Recursively add watches to every directory under path\n+ */\n+static int register_inotify(const char *path,\n+\tstruct fsmonitor_daemon_state *state,\n+\tstruct fsmonitor_batch *batch)\n+{\n+\tDIR *dir;\n+\tconst char *rel;\n+\tstruct strbuf current = STRBUF_INIT;\n+\tstruct dirent *de;\n+\tstruct stat fs;\n+\tint ret = -1;\n+\n+\tdir = opendir(path);\n+\tif (!dir)\n+\t\treturn error_errno(\"opendir('%s') failed\", path);\n+\n+\twhile ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {\n+\t\tstrbuf_reset(&current);\n+\t\tstrbuf_addf(&current, \"%s/%s\", path, de->d_name);\n+\t\tif (lstat(current.buf, &fs)) {\n+\t\t\terror_errno(\"lstat('%s') failed\", current.buf);\n+\t\t\tgoto failed;\n+\t\t}\n+\n+\t\t/* recurse into directory */\n+\t\tif (S_ISDIR(fs.st_mode)) {\n+\t\t\tif (add_watch(current.buf, state->listen_data))\n+\t\t\t\tgoto failed;\n+\t\t\tif (register_inotify(current.buf, state, batch))\n+\t\t\t\tgoto failed;\n+\t\t} else if (batch) {\n+\t\t\trel = current.buf + state->path_worktree_watch.len + 1;\n+\t\t\ttrace_printf_key(&trace_fsmonitor, \"explicitly adding '%s'\", rel);\n+\t\t\tfsmonitor_batch__add_path(batch, rel);\n+\t\t}\n+\t}\n+\tret = 0;\n+\n+failed:\n+\tstrbuf_release(&current);\n+\tif (closedir(dir) < 0)\n+\t\treturn error_errno(\"closedir('%s') failed\", path);\n+\treturn ret;\n+}\n+\n+static int em_rename_dir_from(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVED_FROM));\n+}\n+\n+static int em_rename_dir_to(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVED_TO));\n+}\n+\n+static int em_remove_watch(u_int32_t mask)\n+{\n+\treturn (mask & IN_DELETE_SELF);\n+}\n+\n+static int em_dir_renamed(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_MOVE));\n+}\n+\n+static int em_dir_created(u_int32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_CREATE));\n+}\n+\n+static int em_dir_deleted(uint32_t mask)\n+{\n+\treturn ((mask & IN_ISDIR) && (mask & IN_DELETE));\n+}\n+\n+static int em_force_shutdown(u_int32_t mask)\n+{\n+\treturn (mask & IN_UNMOUNT) || (mask & IN_Q_OVERFLOW);\n+}\n+\n+static int em_ignore(u_int32_t mask)\n+{\n+\treturn (mask & IN_IGNORED) || (mask & IN_MOVE_SELF);\n+}\n+\n+static void log_mask_set(const char *path, u_int32_t mask)\n+{\n+\tstruct strbuf msg = STRBUF_INIT;\n+\n+\tif (mask & IN_ACCESS)\n+\t\tstrbuf_addstr(&msg, \"IN_ACCESS|\");\n+\tif (mask & IN_MODIFY)\n+\t\tstrbuf_addstr(&msg, \"IN_MODIFY|\");\n+\tif (mask & IN_ATTRIB)\n+\t\tstrbuf_addstr(&msg, \"IN_ATTRIB|\");\n+\tif (mask & IN_CLOSE_WRITE)\n+\t\tstrbuf_addstr(&msg, \"IN_CLOSE_WRITE|\");\n+\tif (mask & IN_CLOSE_NOWRITE)\n+\t\tstrbuf_addstr(&msg, \"IN_CLOSE_NOWRITE|\");\n+\tif (mask & IN_OPEN)\n+\t\tstrbuf_addstr(&msg, \"IN_OPEN|\");\n+\tif (mask & IN_MOVED_FROM)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVED_FROM|\");\n+\tif (mask & IN_MOVED_TO)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVED_TO|\");\n+\tif (mask & IN_CREATE)\n+\t\tstrbuf_addstr(&msg, \"IN_CREATE|\");\n+\tif (mask & IN_DELETE)\n+\t\tstrbuf_addstr(&msg, \"IN_DELETE|\");\n+\tif (mask & IN_DELETE_SELF)\n+\t\tstrbuf_addstr(&msg, \"IN_DELETE_SELF|\");\n+\tif (mask & IN_MOVE_SELF)\n+\t\tstrbuf_addstr(&msg, \"IN_MOVE_SELF|\");\n+\tif (mask & IN_UNMOUNT)\n+\t\tstrbuf_addstr(&msg, \"IN_UNMOUNT|\");\n+\tif (mask & IN_Q_OVERFLOW)\n+\t\tstrbuf_addstr(&msg, \"IN_Q_OVERFLOW|\");\n+\tif (mask & IN_IGNORED)\n+\t\tstrbuf_addstr(&msg, \"IN_IGNORED|\");\n+\tif (mask & IN_ISDIR)\n+\t\tstrbuf_addstr(&msg, \"IN_ISDIR|\");\n+\n+\ttrace_printf_key(&trace_fsmonitor, \"inotify_event: '%s', mask=%#8.8x %s\",\n+\t\t\t\tpath, mask, msg.buf);\n+\n+\tstrbuf_release(&msg);\n+}\n+\n+int fsm_listen__ctor(struct fsmonitor_daemon_state *state)\n+{\n+\tint fd;\n+\tint ret = 0;\n+\tstruct fsm_listen_data *data;\n+\n+\tCALLOC_ARRAY(data, 1);\n+\tstate->listen_data = data;\n+\tstate->listen_error_code = -1;\n+\tdata->shutdown = SHUTDOWN_ERROR;\n+\n+\tfd = inotify_init1(O_NONBLOCK);\n+\tif (fd < 0)\n+\t\treturn error_errno(\"inotify_init1() failed\");\n+\n+\tdata->fd_inotify = fd;\n+\n+\thashmap_init(&data->watches, watch_entry_cmp, NULL, 0);\n+\thashmap_init(&data->renames, rename_entry_cmp, NULL, 0);\n+\thashmap_init(&data->revwatches, revwatches_entry_cmp, NULL, 0);\n+\n+\tif (add_watch(state->path_worktree_watch.buf, data))\n+\t\tret = -1;\n+\telse if (register_inotify(state->path_worktree_watch.buf, state, NULL))\n+\t\tret = -1;\n+\telse if (state->nr_paths_watching > 1) {\n+\t\tif (add_watch(state->path_gitdir_watch.buf, data))\n+\t\t\tret = -1;\n+\t\telse if (register_inotify(state->path_gitdir_watch.buf, state, NULL))\n+\t\t\tret = -1;\n+\t}\n+\n+\tif (!ret) {\n+\t\tstate->listen_error_code = 0;\n+\t\tdata->shutdown = SHUTDOWN_CONTINUE;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+void fsm_listen__dtor(struct fsmonitor_daemon_state *state)\n+{\n+\tstruct fsm_listen_data *data;\n+\tstruct hashmap_iter iter;\n+\tstruct watch_entry *w;\n+\tint fd;\n+\n+\tif (!state || !state->listen_data)\n+\t\treturn;\n+\n+\tdata = state->listen_data;\n+\tfd = data->fd_inotify;\n+\n+\thashmap_for_each_entry(&data->watches, &iter, w, ent) {\n+\t\tw->cookie = 0; /* ignore any pending renames */\n+\t\tremove_watch(w, data);\n+\t}\n+\thashmap_clear(&data->watches);\n+\n+\thashmap_clear(&data->revwatches); /* remove_watch freed the entries */\n+\n+\thashmap_clear_and_free(&data->renames, struct rename_entry, ent);\n+\n+\tFREE_AND_NULL(state->listen_data);\n+\n+\tif (fd && (close(fd) < 0))\n+\t\terror_errno(_(\"closing inotify file descriptor failed\"));\n+}\n+\n+void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)\n+{\n+\tif (!state->listen_data->shutdown)\n+\t\tstate->listen_data->shutdown = SHUTDOWN_STOP;\n+}\n+\n+/*\n+ * Process a single inotify event and queue for publication.\n+ */\n+static int process_event(const char *path,\n+\tconst struct inotify_event *event,\n+\tstruct fsmonitor_batch *batch,\n+\tstruct string_list *cookie_list,\n+\tstruct fsmonitor_daemon_state *state)\n+{\n+\tconst char *rel;\n+\tconst char *last_sep;\n+\n+\tswitch (fsmonitor_classify_path_absolute(state, path)) {\n+\t\tcase IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:\n+\t\tcase IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:\n+\t\t\t/* Use just the filename of the cookie file. */\n+\t\t\tlast_sep = find_last_dir_sep(path);\n+\t\t\tstring_list_append(cookie_list,\n+\t\t\t\t\tlast_sep ? last_sep + 1 : path);\n+\t\t\tbreak;\n+\t\tcase IS_INSIDE_DOT_GIT:\n+\t\tcase IS_INSIDE_GITDIR:\n+\t\t\tbreak;\n+\t\tcase IS_DOT_GIT:\n+\t\tcase IS_GITDIR:\n+\t\t\t/*\n+\t\t\t* If .git directory is deleted or renamed away,\n+\t\t\t* we have to quit.\n+\t\t\t*/\n+\t\t\tif (em_dir_deleted(event->mask)) {\n+\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\"event: gitdir removed\");\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\tif (em_dir_renamed(event->mask)) {\n+\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\"event: gitdir renamed\");\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase IS_WORKDIR_PATH:\n+\t\t\t/* normal events in the working directory */\n+\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n+\t\t\t\tlog_mask_set(path, event->mask);\n+\n+\t\t\trel = path + state->path_worktree_watch.len + 1;\n+\t\t\tfsmonitor_batch__add_path(batch, rel);\n+\n+\t\t\tif (em_dir_deleted(event->mask))\n+\t\t\t\tbreak;\n+\n+\t\t\t/* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */\n+\t\t\tif (em_rename_dir_from(event->mask))\n+\t\t\t\tadd_dir_rename(event->cookie, path, state->listen_data);\n+\n+\t\t\t/* received IN_MOVE_TO, update watch to reflect new path */\n+\t\t\tif (em_rename_dir_to(event->mask)) {\n+\t\t\t\trename_dir(event->cookie, path, state->listen_data);\n+\t\t\t\tif (register_inotify(path, state, batch)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (em_dir_created(event->mask)) {\n+\t\t\t\tif (add_watch(path, state->listen_data)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t\tif (register_inotify(path, state, batch)) {\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tgoto done;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase IS_OUTSIDE_CONE:\n+\t\tdefault:\n+\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\"ignoring '%s'\", path);\n+\t\t\tbreak;\n+\t}\n+\treturn 0;\n+done:\n+\treturn -1;\n+}\n+\n+/*\n+ * Read the inotify event stream and pre-process events before further\n+ * processing and eventual publishing.\n+ */\n+static void handle_events(struct fsmonitor_daemon_state *state)\n+{\n+\t /* See https://man7.org/linux/man-pages/man7/inotify.7.html */\n+\tchar buf[4096]\n+\t\t__attribute__ ((aligned(__alignof__(struct inotify_event))));\n+\n+\tstruct hashmap watches = state->listen_data->watches;\n+\tstruct fsmonitor_batch *batch = NULL;\n+\tstruct string_list cookie_list = STRING_LIST_INIT_DUP;\n+\tstruct watch_entry k, *w;\n+\tstruct strbuf path;\n+\tconst struct inotify_event *event;\n+\tint fd = state->listen_data->fd_inotify;\n+\tssize_t len;\n+\tchar *ptr, *p;\n+\n+\tstrbuf_init(&path, PATH_MAX);\n+\n+\tfor(;;) {\n+\t\tlen = read(fd, buf, sizeof(buf));\n+\t\tif (len == -1 && errno != EAGAIN) {\n+\t\t\terror_errno(_(\"reading inotify message stream failed\"));\n+\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\t/* nothing to read */\n+\t\tif (len <= 0)\n+\t\t\tgoto done;\n+\n+\t\t/* Loop over all events in the buffer. */\n+\t\tfor (ptr = buf; ptr < buf + len;\n+\t\t\t ptr += sizeof(struct inotify_event) + event->len) {\n+\n+\t\t\tevent = (const struct inotify_event *) ptr;\n+\n+\t\t\tif (em_ignore(event->mask))\n+\t\t\t\tcontinue;\n+\n+\t\t\t/* File system was unmounted or event queue overflowed */\n+\t\t\tif (em_force_shutdown(event->mask)) {\n+\t\t\t\tif (trace_pass_fl(&trace_fsmonitor))\n+\t\t\t\t\tlog_mask_set(\"Forcing shutdown\", event->mask);\n+\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\thashmap_entry_init(&k.ent, memhash(&event->wd, sizeof(int)));\n+\t\t\tk.wd = event->wd;\n+\n+\t\t\tw = hashmap_get_entry(&watches, &k, ent, NULL);\n+\t\t\tif (!w) /* should never happen */\n+\t\t\t\tBUG(\"No watch for '%s'\", event->name);\n+\n+\t\t\t/* directory watch was removed */\n+\t\t\tif (em_remove_watch(event->mask)) {\n+\t\t\t\tremove_watch(w, state->listen_data);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tstrbuf_reset(&path);\n+\t\t\tstrbuf_add(&path, w->dir, strlen(w->dir));\n+\t\t\tstrbuf_addch(&path, '/');\n+\t\t\tstrbuf_addstr(&path, event->name);\n+\n+\t\t\tp = fsmonitor__resolve_alias(path.buf, &state->alias);\n+\t\t\tif (!p)\n+\t\t\t\tp = strbuf_detach(&path, NULL);\n+\n+\t\t\tif (!batch)\n+\t\t\t\tbatch = fsmonitor_batch__new();\n+\n+\t\t\tif (process_event(p, event, batch, &cookie_list, state)) {\n+\t\t\t\tfree(p);\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tfree(p);\n+\t\t}\n+\t\tstrbuf_reset(&path);\n+\t\tfsmonitor_publish(state, batch, &cookie_list);\n+\t\tstring_list_clear(&cookie_list, 0);\n+\t\tbatch = NULL;\n+\t}\n+done:\n+\tstrbuf_release(&path);\n+\tfsmonitor_batch__free_list(batch);\n+\tstring_list_clear(&cookie_list, 0);\n+}\n+\n+/*\n+ * Non-blocking read of the inotify events stream. The inotify fd is polled\n+ * frequently to help minimize the number of queue overflows.\n+ */\n+void fsm_listen__loop(struct fsmonitor_daemon_state *state)\n+{\n+\tint poll_num;\n+\tconst int interval = 1000;\n+\ttime_t checked = time(NULL);\n+\tstruct pollfd fds[1];\n+\tfds[0].fd = state->listen_data->fd_inotify;\n+\tfds[0].events = POLLIN;\n+\n+\tfor(;;) {\n+\t\tswitch (state->listen_data->shutdown) {\n+\t\t\tcase SHUTDOWN_CONTINUE:\n+\t\t\t\tpoll_num = poll(fds, 1, 1);\n+\t\t\t\tif (poll_num == -1) {\n+\t\t\t\t\tif (errno == EINTR)\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\terror_errno(_(\"polling inotify message stream failed\"));\n+\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_ERROR;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif ((time(NULL) - checked) >= interval) {\n+\t\t\t\t\tchecked = time(NULL);\n+\t\t\t\t\tif (check_stale_dir_renames(&state->listen_data->renames,\n+\t\t\t\t\t\tchecked - interval)) {\n+\t\t\t\t\t\ttrace_printf_key(&trace_fsmonitor,\n+\t\t\t\t\t\t\t\"Missed IN_MOVED_TO events, forcing shutdown\");\n+\t\t\t\t\t\tstate->listen_data->shutdown = SHUTDOWN_FORCE;\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\n+\t\t\t\tif (poll_num > 0 && (fds[0].revents & POLLIN))\n+\t\t\t\t\thandle_events(state);\n+\n+\t\t\t\tcontinue;\n+\t\t\tcase SHUTDOWN_ERROR:\n+\t\t\t\tstate->listen_error_code = -1;\n+\t\t\t\tipc_server_stop_async(state->ipc_server_data);\n+\t\t\t\tbreak;\n+\t\t\tcase SHUTDOWN_FORCE:\n+\t\t\t\tstate->listen_error_code = 0;\n+\t\t\t\tipc_server_stop_async(state->ipc_server_data);\n+\t\t\t\tbreak;\n+\t\t\tcase SHUTDOWN_STOP:\n+\t\t\tdefault:\n+\t\t\t\tstate->listen_error_code = 0;\n+\t\t\t\tbreak;\n+\t\t}\n+\t\treturn;\n+\t}\n+}\n-- \ngitgitgadget\n\n"},{"id":"468948","messageId":"6a7b554642c39096e80c0e0146d149eeee55241d.1670882286.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v5.git.git.1670882286.gitgitgadget@gmail.com","subject":"[PATCH v5 5/6] fsmonitor: test updates","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-12-12T21:58:04Z","receivedAt":"2022-12-12T21:58:33Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nt7527-builtin-fsmonitor was leaking fsmonitor--daemon processes in some\ncases.\n\nAccomodate slight difference in the number of events generated on Linux.\n\nOn lower-powered systems, spin a little to give the daemon time\nto respond to and log filesystem events.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n t/t7527-builtin-fsmonitor.sh | 94 ++++++++++++++++++++++++++++++------\n 1 file changed, 80 insertions(+), 14 deletions(-)\n\ndiff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh\nindex 4abc74db2bb..951374231b7 100755\n--- a/t/t7527-builtin-fsmonitor.sh\n+++ b/t/t7527-builtin-fsmonitor.sh\n@@ -13,7 +13,7 @@ fi\n stop_daemon_delete_repo () {\n \tr=$1 &&\n \ttest_might_fail git -C $r fsmonitor--daemon stop &&\n-\trm -rf $1\n+\trm -rf $r\n }\n \n start_daemon () {\n@@ -72,6 +72,34 @@ start_daemon () {\n \t)\n }\n \n+IMPLICIT_TIMEOUT=5\n+\n+wait_for_update () {\n+\tfunc=$1 &&\n+\tfile=$2 &&\n+\tsz=$(wc -c < \"$file\") &&\n+\tlast=0 &&\n+\t$func &&\n+\tk=0 &&\n+\twhile test \"$k\" -lt $IMPLICIT_TIMEOUT\n+\tdo\n+\t\tnsz=$(wc -c < \"$file\")\n+\t\tif test \"$nsz\" -gt \"$sz\"\n+\t\tthen\n+\t\t\tif test \"$last\" -eq \"$nsz\"\n+\t\t\tthen\n+\t\t\t\tcat \"$file\" &&\n+\t\t\t\treturn 0\n+\t\t\tfi\n+\t\t\tlast=$nsz\n+\t\tfi\n+\t\tsleep 1\n+\t\tk=$(( $k + 1 ))\n+\tdone &&\n+\tcat \"$file\" &&\n+\treturn 0\n+}\n+\n # Is a Trace2 data event present with the given catetory and key?\n # We do not care what the value is.\n #\n@@ -137,7 +165,6 @@ test_expect_success 'implicit daemon start' '\n # machines (where it might take a moment to wake and reschedule the\n # daemon process) to avoid false alarms during test runs.)\n #\n-IMPLICIT_TIMEOUT=5\n \n verify_implicit_shutdown () {\n \tr=$1 &&\n@@ -373,6 +400,15 @@ create_files () {\n \techo 3 >dir2/new\n }\n \n+rename_directory () {\n+\tmv dirtorename dirrenamed\n+}\n+\n+rename_directory_file () {\n+\tmv dirtorename dirrenamed &&\n+\techo 1 > dirrenamed/new\n+}\n+\n rename_files () {\n \tmv rename renamed &&\n \tmv dir1/rename dir1/renamed &&\n@@ -427,10 +463,12 @@ test_expect_success 'edit some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tedit_files &&\n+\twait_for_update edit_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/modified$\"  .git/trace &&\n \tgrep \"^event: dir2/modified$\"  .git/trace &&\n \tgrep \"^event: modified$\"       .git/trace &&\n@@ -442,10 +480,12 @@ test_expect_success 'create some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tcreate_files &&\n+\twait_for_update create_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/new$\" .git/trace &&\n \tgrep \"^event: dir2/new$\" .git/trace &&\n \tgrep \"^event: new$\"      .git/trace\n@@ -456,10 +496,12 @@ test_expect_success 'delete some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tdelete_files &&\n+\twait_for_update delete_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/delete$\" .git/trace &&\n \tgrep \"^event: dir2/delete$\" .git/trace &&\n \tgrep \"^event: delete$\"      .git/trace\n@@ -470,10 +512,12 @@ test_expect_success 'rename some files' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\trename_files &&\n+\twait_for_update rename_files \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1/rename$\"  .git/trace &&\n \tgrep \"^event: dir2/rename$\"  .git/trace &&\n \tgrep \"^event: rename$\"       .git/trace &&\n@@ -487,23 +531,42 @@ test_expect_success 'rename directory' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tmv dirtorename dirrenamed &&\n+\twait_for_update rename_directory \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dirtorename/*$\" .git/trace &&\n \tgrep \"^event: dirrenamed/*$\"  .git/trace\n '\n \n+test_expect_success 'rename directory file' '\n+\ttest_when_finished clean_up_repo_and_stop_daemon &&\n+\n+\tstart_daemon --tf \"$PWD/.git/trace\" &&\n+\n+\twait_for_update rename_directory_file \"$PWD/.git/trace\" &&\n+\n+\ttest-tool fsmonitor-client query --token 0 &&\n+\n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n+\tgrep \"^event: dirtorename/*$\" .git/trace &&\n+\tgrep \"^event: dirrenamed/*$\"  .git/trace &&\n+\tgrep \"^event: dirrenamed/new$\"  .git/trace\n+'\n test_expect_success 'file changes to directory' '\n \ttest_when_finished clean_up_repo_and_stop_daemon &&\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tfile_to_directory &&\n+\twait_for_update file_to_directory \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: delete$\"     .git/trace &&\n \tgrep \"^event: delete/new$\" .git/trace\n '\n@@ -513,10 +576,12 @@ test_expect_success 'directory changes to a file' '\n \n \tstart_daemon --tf \"$PWD/.git/trace\" &&\n \n-\tdirectory_to_file &&\n+\twait_for_update directory_to_file \"$PWD/.git/trace\" &&\n \n \ttest-tool fsmonitor-client query --token 0 &&\n \n+\ttest_might_fail git fsmonitor--daemon stop &&\n+\n \tgrep \"^event: dir1$\" .git/trace\n '\n \n@@ -561,7 +626,7 @@ test_expect_success 'flush cached data' '\n \ttest-tool -C test_flush fsmonitor-client query --token \"builtin:test_00000002:0\" >actual_2 &&\n \tnul_to_q <actual_2 >actual_q2 &&\n \n-\tgrep \"^builtin:test_00000002:0Q$\" actual_q2 &&\n+\tgrep \"^builtin:test_00000002:[0-1]Q$\" actual_q2 &&\n \n \t>test_flush/file_3 &&\n \n@@ -732,7 +797,8 @@ u_values=\"$u1 $u2\"\n for u in $u_values\n do\n \ttest_expect_success \"unicode in repo root path: $u\" '\n-\t\ttest_when_finished \"stop_daemon_delete_repo $u\" &&\n+\t\ttest_when_finished \\\n+\t\t\"stop_daemon_delete_repo `echo \"$u\" | sed 's:x:\\\\\\\\\\\\\\\\\\\\\\\\\\\\x:g'`\" &&\n \n \t\tgit init \"$u\" &&\n \t\techo 1 >\"$u\"/file1 &&\n@@ -818,8 +884,7 @@ test_expect_success 'submodule setup' '\n '\n \n test_expect_success 'submodule always visited' '\n-\ttest_when_finished \"git -C super fsmonitor--daemon stop; \\\n-\t\t\t    rm -rf super; \\\n+\ttest_when_finished \"rm -rf super; \\\n \t\t\t    rm -rf sub\" &&\n \n \tcreate_super super &&\n@@ -887,7 +952,8 @@ have_t2_error_event () {\n }\n \n test_expect_success \"stray submodule super-prefix warning\" '\n-\ttest_when_finished \"rm -rf super; \\\n+\ttest_when_finished \"git -C super/dir_1/dir_2/sub fsmonitor--daemon stop; \\\n+\t\t\t    rm -rf super; \\\n \t\t\t    rm -rf sub;   \\\n \t\t\t    rm super-sub.trace\" &&\n \n-- \ngitgitgadget\n\n"},{"id":"468949","messageId":"827410c22ee4846067c79fb83d9e3d9a961be121.1670882286.git.gitgitgadget@gmail.com","threadId":"58588","inReplyTo":"pull.1352.v5.git.git.1670882286.gitgitgadget@gmail.com","subject":"[PATCH v5 6/6] fsmonitor: update doc for Linux","fromName":"Eric DeCosta via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-12-12T21:58:05Z","receivedAt":"2022-12-12T21:58:35Z","isPatch":true,"sender":{"key":"edecosta@mathworks.com","avatar":"https://avatars.githubusercontent.com/u/67609563?v=4"},"body":"From: Eric DeCosta <edecosta@mathworks.com>\n\nUpdate the documentation for Linux.\n\nSigned-off-by: Eric DeCosta <edecosta@mathworks.com>\n---\n Documentation/config/fsmonitor--daemon.txt |  4 ++--\n Documentation/git-fsmonitor--daemon.txt    | 24 ++++++++++++++--------\n 2 files changed, 18 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config/fsmonitor--daemon.txt b/Documentation/config/fsmonitor--daemon.txt\nindex c225c6c9e74..2cafb040d96 100644\n--- a/Documentation/config/fsmonitor--daemon.txt\n+++ b/Documentation/config/fsmonitor--daemon.txt\n@@ -4,8 +4,8 @@ fsmonitor.allowRemote::\n     behavior.  Only respected when `core.fsmonitor` is set to `true`.\n \n fsmonitor.socketDir::\n-    This Mac OS-specific option, if set, specifies the directory in\n+    Mac OS and Linux-specific option. If set, specifies the directory in\n     which to create the Unix domain socket used for communication\n     between the fsmonitor daemon and various Git commands. The directory must\n-    reside on a native Mac OS filesystem.  Only respected when `core.fsmonitor`\n+    reside on a native filesystem.  Only respected when `core.fsmonitor`\n     is set to `true`.\ndiff --git a/Documentation/git-fsmonitor--daemon.txt b/Documentation/git-fsmonitor--daemon.txt\nindex 8238eadb0e1..c2b08229c74 100644\n--- a/Documentation/git-fsmonitor--daemon.txt\n+++ b/Documentation/git-fsmonitor--daemon.txt\n@@ -76,23 +76,31 @@ repositories; this may be overridden by setting `fsmonitor.allowRemote` to\n correctly with all network-mounted repositories and such use is considered\n experimental.\n \n-On Mac OS, the inter-process communication (IPC) between various Git\n+On Linux and Mac OS, the inter-process communication (IPC) between various Git\n commands and the fsmonitor daemon is done via a Unix domain socket (UDS) -- a\n-special type of file -- which is supported by native Mac OS filesystems,\n-but not on network-mounted filesystems, NTFS, or FAT32.  Other filesystems\n-may or may not have the needed support; the fsmonitor daemon is not guaranteed\n-to work with these filesystems and such use is considered experimental.\n+special type of file -- which is supported by many native Linux and Mac OS\n+filesystems, but not on network-mounted filesystems, NTFS, or FAT32.  Other\n+filesystems may or may not have the needed support; the fsmonitor daemon is not\n+guaranteed to work with these filesystems and such use is considered\n+experimental.\n \n By default, the socket is created in the `.git` directory, however, if the\n `.git` directory is on a network-mounted filesystem, it will be instead be\n created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a\n network-mounted filesystem in which case you must set the configuration\n-variable `fsmonitor.socketDir` to the path of a directory on a Mac OS native\n+variable `fsmonitor.socketDir` to the path of a directory on a native\n filesystem in which to create the socket file.\n \n If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`)\n-is on a native Mac OS file filesystem the fsmonitor daemon will report an\n-error that will cause the daemon and the currently running command to exit.\n+is on a native Linux or Mac OS filesystem the fsmonitor daemon will report\n+an error that will cause the daemon to exit and the currently running command\n+to issue a warning.\n+\n+On Linux, the fsmonitor daemon registers a watch for each directory in the\n+repository.  The default per-user limit for the number of watches on most Linux\n+systems is 8192.  This may not be sufficient for large repositories or if\n+multiple instances of the fsmonitor daemon are running.\n+See https://watchexec.github.io/docs/inotify-limits.html[Linux inotify limits] for more information.\n \n CONFIGURATION\n -------------\n-- \ngitgitgadget\n"},{"id":"475184","messageId":"xmqqzg7d8xn2.fsf@gitster.g","threadId":"58588","inReplyTo":"pull.1352.v5.git.git.1670882286.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 0/6] fsmonitor: Implement fsmonitor for Linux","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-12T05:42:09Z","receivedAt":"2023-04-12T05:43:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric DeCosta via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Goal is to deliver fsmonitor for Linux that is on par with fsmonitor for\n> Windows and Mac OS.\n>\n> This patch set builds upon previous work for done for Windows and Mac OS to\n> implement a fsmonitor back-end for Linux based on the Linux inotify API.\n> inotify differs significantly from the equivalent Windows and Mac OS APIs in\n> that a watch must be registered for every directory of interest (rather than\n> a singular watch at the root of the directory tree) and special care must be\n> taken to handle directory renames correctly.\n>\n> More information about inotify:\n> https://man7.org/linux/man-pages/man7/inotify.7.html\n>\n> v4 differs from v3:\n>\n>  * Code review feedback\n\nThis has been dormant for full four months, and it seems to break\nlinux-asan CI job when merged to 'seen',\n\n  https://github.com/git/git/actions/runs/4672116751/jobs/8273938089\n\nwhile the same 'seen' excluding this topic passes everything.\n\n  https://github.com/git/git/actions/runs/4674694371\n\nFor now, I'll drop this topic from 'seen'.\n\nThanks.\n\n"}]}