@@ -3,6 +3,7 @@
#include "parse-options.h"
#include "fsmonitor.h"
#include "fsmonitor-ipc.h"
+#include "fsmonitor-path-utils.h"
#include "compat/fsmonitor/fsm-health.h"
#include "compat/fsmonitor/fsm-listen.h"
#include "fsmonitor--daemon.h"
@@ -1282,6 +1283,13 @@ static int fsmonitor_run_daemon(void)
strbuf_addstr(&state.path_worktree_watch, absolute_path(get_git_work_tree()));
state.nr_paths_watching = 1;
+ state.alias.alias = NULL;
+ state.alias.points_to = NULL;
+ if (fsmonitor__get_alias(state.path_worktree_watch.buf, &state.alias)) {
+ err = error(_("could not get worktree alias"));
+ goto done;
+ }
+
/*
* We create and delete cookie files somewhere inside the .git
* directory to help us keep sync with the file system. If
@@ -26,6 +26,7 @@
#include "fsmonitor.h"
#include "fsm-listen.h"
#include "fsmonitor--daemon.h"
+#include "fsmonitor-path-utils.h"
struct fsm_listen_data
{
@@ -198,8 +199,9 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
struct string_list cookie_list = STRING_LIST_INIT_DUP;
const char *path_k;
const char *slash;
- int k;
+ char *resolved = NULL;
struct strbuf tmp = STRBUF_INIT;
+ int k;
/*
* Build a list of all filesystem changes into a private/local
@@ -209,7 +211,12 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
/*
* On Mac, we receive an array of absolute paths.
*/
- path_k = paths[k];
+ free(resolved);
+ resolved = fsmonitor__resolve_alias(paths[k], &state->alias);
+ if (resolved)
+ path_k = resolved;
+ else
+ path_k = paths[k];
/*
* If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.
@@ -238,6 +245,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
fsmonitor_force_resync(state);
fsmonitor_batch__free_list(batch);
string_list_clear(&cookie_list, 0);
+ batch = NULL;
/*
* We assume that any events that we received
@@ -360,12 +368,14 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,
}
}
+ free(resolved);
fsmonitor_publish(state, batch, &cookie_list);
string_list_clear(&cookie_list, 0);
strbuf_release(&tmp);
return;
force_shutdown:
+ free(resolved);
fsmonitor_batch__free_list(batch);
string_list_clear(&cookie_list, 0);
@@ -1,5 +1,8 @@
#include "fsmonitor.h"
#include "fsmonitor-path-utils.h"
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
#include <sys/param.h>
#include <sys/mount.h>
@@ -38,3 +41,92 @@ int fsmonitor__is_fs_remote(const char *path)
return -1;
return fs.is_remote;
}
+
+/*
+ * Scan the root directory for synthetic firmlinks that when resolved
+ * are a prefix of the path, stopping at the first one found.
+ *
+ * Some information about firmlinks and synthetic firmlinks:
+ * https://eclecticlight.co/2020/01/23/catalina-boot-volumes/
+ *
+ * macOS no longer allows symlinks in the root directory; any link found
+ * there is therefore a synthetic firmlink.
+ *
+ * If this function gets called often, will want to cache all the firmlink
+ * information, but for now there is only one caller of this function.
+ *
+ * If there is more than one alias for the path, that is another
+ * matter altogether.
+ */
+int fsmonitor__get_alias(const char *path, struct alias_info *info)
+{
+ DIR * dir;
+ int read;
+ int retval;
+ struct dirent *de;
+ struct strbuf alias;
+ struct strbuf points_to;
+
+ retval = 0;
+ dir = opendir("/");
+ if (!dir)
+ return -1;
+
+ strbuf_init(&alias, 256);
+ strbuf_init(&points_to, MAXPATHLEN);
+
+ while ((de = readdir(dir)) != NULL) {
+ strbuf_reset(&alias);
+ strbuf_addch(&alias, '/');
+ strbuf_add(&alias, de->d_name, strlen(de->d_name));
+
+ read = readlink(alias.buf, points_to.buf, MAXPATHLEN);
+ if (read > 0) {
+ strbuf_setlen(&points_to, read);
+ if ((strncmp(points_to.buf, path, points_to.len) == 0)
+ && path[points_to.len] == '/') {
+ info->alias = strbuf_detach(&alias, NULL);
+ info->points_to = strbuf_detach(&points_to, NULL);
+ trace_printf_key(&trace_fsmonitor,
+ "Found alias for '%s' : '%s' -> '%s'",
+ path, info->alias, info->points_to);
+ retval = 0;
+ goto done;
+ }
+ } else if (errno != EINVAL) { /* Something other than not a link */
+ trace_printf_key(&trace_fsmonitor, "Error %s", strerror(errno));
+ retval = -1;
+ goto done;
+ }
+ }
+
+ done:
+ closedir(dir);
+ strbuf_release(&alias);
+ strbuf_release(&points_to);
+ return retval;
+}
+
+char *fsmonitor__resolve_alias(const char *path,
+ const struct alias_info *info)
+{
+ int len = info->alias ? strlen(info->alias) : 0;
+
+ if (!len)
+ return NULL;
+
+ if ((strncmp(info->alias, path, len) == 0)
+ && path[len] == '/') {
+ struct strbuf tmp;
+ const char *remainder = path + len;
+ int ptr_len = strlen(info->points_to);
+ int rem_len = strlen(remainder);
+
+ strbuf_init(&tmp, ptr_len + rem_len);
+ strbuf_add(&tmp, info->points_to, ptr_len);
+ strbuf_add(&tmp, remainder, rem_len);
+ return strbuf_detach(&tmp, NULL);
+ }
+
+ return NULL;
+}
@@ -126,3 +126,20 @@ int fsmonitor__is_fs_remote(const char *path)
return -1;
return fs.is_remote;
}
+
+/*
+ * No-op for now.
+ */
+int fsmonitor__get_alias(const char *path, struct alias_info *info)
+{
+ return 0;
+}
+
+/*
+ * No-op for now.
+ */
+char *fsmonitor__resolve_alias(const char *path,
+ const struct alias_info *info)
+{
+ return NULL;
+}
@@ -8,6 +8,7 @@
#include "run-command.h"
#include "simple-ipc.h"
#include "thread-utils.h"
+#include "fsmonitor-path-utils.h"
struct fsmonitor_batch;
struct fsmonitor_token_data;
@@ -43,6 +44,7 @@ struct fsmonitor_daemon_state {
struct strbuf path_worktree_watch;
struct strbuf path_gitdir_watch;
+ struct alias_info alias;
int nr_paths_watching;
struct fsmonitor_token_data *current_token_data;
@@ -59,6 +61,7 @@ struct fsmonitor_daemon_state {
struct ipc_server_data *ipc_server_data;
struct strbuf path_ipc;
+
};
/*
@@ -1,6 +1,14 @@
#ifndef FSM_PATH_UTILS_H
#define FSM_PATH_UTILS_H
+#include "strbuf.h"
+
+struct alias_info
+{
+ char *alias;
+ char *points_to;
+};
+
struct fs_info {
int is_remote;
char *typename;
@@ -20,4 +28,32 @@ int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info);
*/
int fsmonitor__is_fs_remote(const char *path);
+/*
+ * Get the alias in given path, if any.
+ *
+ * Sets alias to the first alias that matches any part of the path.
+ *
+ * If an alias is found, info.alias and info.points_to are set to the
+ * found mapping.
+ *
+ * Returns -1 on error, 0 otherwise.
+ *
+ * The caller owns the storage that is occupied by set info.alias and
+ * info.points_to and is responsible for releasing it with `free(3)`
+ * when done.
+ */
+int fsmonitor__get_alias(const char *path, struct alias_info *info);
+
+/*
+ * Resolve the path against the given alias.
+ *
+ * Returns the resolved path if there is one, NULL otherwise.
+ *
+ * The caller owns the storage that the returned string occupies and
+ * is responsible for releasing it with `free(3)` when done.
+ */
+char *fsmonitor__resolve_alias(const char *path,
+ const struct alias_info *info);
+
+
#endif