29
HANDLE hHandles[1]; /* the array does not own these handles */
30
#define HEALTH_SHUTDOWN 0
31
int nr_handles; /* number of active event handles */
32
+
33
+ struct wt_moved
34
+ {
35
+ wchar_t wpath[MAX_PATH + 1];
36
+ BY_HANDLE_FILE_INFORMATION bhfi;
37
+ } wt_moved;
38
};
39
40
+/*
41
+ * Lookup the system unique ID for the path. This is as close as
42
+ * we get to an inode number, but this also contains volume info,
43
+ * so it is a little stronger.
44
+ */
45
+static int lookup_bhfi(wchar_t *wpath,
46
+ BY_HANDLE_FILE_INFORMATION *bhfi)
47
+{
48
+ DWORD desired_access = FILE_LIST_DIRECTORY;
49
+ DWORD share_mode =
50
+ FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
51
+ HANDLE hDir;
52
+
53
+ hDir = CreateFileW(wpath, desired_access, share_mode, NULL,
54
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
55
+ if (hDir == INVALID_HANDLE_VALUE) {
56
+ error(_("[GLE %ld] health thread could not open '%ls'"),
57
+ GetLastError(), wpath);
58
+ return -1;
59
+ }
60
+
61
+ if (!GetFileInformationByHandle(hDir, bhfi)) {
62
+ error(_("[GLE %ld] health thread getting BHFI for '%ls'"),
63
+ GetLastError(), wpath);
64
+ CloseHandle(hDir);
65
+ return -1;
66
+ }
67
+
68
+ CloseHandle(hDir);
69
+ return 0;
70
+}
71
+
72
+/*
73
+ * Compare the relevant fields from two system unique IDs.
74
+ * We use this to see if two different handles to the same
75
+ * path actually refer to the same *instance* of the file
76
+ * or directory.
77
+ */
78
+static int bhfi_eq(const BY_HANDLE_FILE_INFORMATION *bhfi_1,
79
+ const BY_HANDLE_FILE_INFORMATION *bhfi_2)
80
+{
81
+ return (bhfi_1->dwVolumeSerialNumber == bhfi_2->dwVolumeSerialNumber &&
82
+ bhfi_1->nFileIndexHigh == bhfi_2->nFileIndexHigh &&
83
+ bhfi_1->nFileIndexLow == bhfi_2->nFileIndexLow);
84
+}
85
+
86
+/*
87
+ * Shutdown if the original worktree root directory been deleted,
88
+ * moved, or renamed?
89
+ *
90
+ * Since the main thread did a "chdir(getenv($HOME))" and our CWD
91
+ * is not in the worktree root directory and because the listener
92
+ * thread added FILE_SHARE_DELETE to the watch handle, it is possible
93
+ * for the root directory to be moved or deleted while we are still
94
+ * watching it. We want to detect that here and force a shutdown.
95
+ *
96
+ * Granted, a delete MAY cause some operations to fail, such as
97
+ * GetOverlappedResult(), but it is not guaranteed. And because
98
+ * ReadDirectoryChangesW() only reports on changes *WITHIN* the
99
+ * directory, not changes *ON* the directory, our watch will not
100
+ * receive a delete event for it.
101
+ *
102
+ * A move/rename of the worktree root will also not generate an event.
103
+ * And since the listener thread already has an open handle, it may
104
+ * continue to receive events for events within the directory.
105
+ * However, the pathname of the named-pipe was constructed using the
106
+ * original location of the worktree root. (Remember named-pipes are
107
+ * stored in the NPFS and not in the actual file system.) Clients
108
+ * trying to talk to the worktree after the move/rename will not
109
+ * reach our daemon process, since we're still listening on the
110
+ * pipe with original path.
111
+ *
112
+ * Furthermore, if the user does something like:
113
+ *
114
+ * $ mv repo repo.old
115
+ * $ git init repo
116
+ *
117
+ * A new daemon cannot be started in the new instance of "repo"
118
+ * because the named-pipe is still being used by the daemon on
119
+ * the original instance.
120
+ *
121
+ * So, detect move/rename/delete and shutdown. This should also
122
+ * handle unsafe drive removal.
123
+ *
124
+ * We use the file system unique ID to distinguish the original
125
+ * directory instance from a new instance and force a shutdown
126
+ * if the unique ID changes.
127
+ *
128
+ * Since a worktree move/rename/delete/unmount doesn't happen
129
+ * that often (and we can't get an immediate event anyway), we
130
+ * use a timeout and periodically poll it.
131
+ */
132
+static int has_worktree_moved(struct fsmonitor_daemon_state *state,
133
+ enum interval_fn_ctx ctx)
134
+{
135
+ struct fsm_health_data *data = state->health_data;
136
+ BY_HANDLE_FILE_INFORMATION bhfi;
137
+ int r;
138
+
139
+ switch (ctx) {
140
+ case CTX_TERM:
141
+ return 0;
142
+
143
+ case CTX_INIT:
144
+ if (xutftowcs_path(data->wt_moved.wpath,
145
+ state->path_worktree_watch.buf) < 0) {
146
+ error(_("could not convert to wide characters: '%s'"),
147
+ state->path_worktree_watch.buf);
148
+ return -1;
149
+ }
150
+
151
+ /*
152
+ * On the first call we lookup the unique sequence ID for
153
+ * the worktree root directory.
154
+ */
155
+ return lookup_bhfi(data->wt_moved.wpath, &data->wt_moved.bhfi);
156
+
157
+ case CTX_TIMER:
158
+ r = lookup_bhfi(data->wt_moved.wpath, &bhfi);
159
+ if (r)
160
+ return r;
161
+ if (!bhfi_eq(&data->wt_moved.bhfi, &bhfi)) {
162
+ error(_("BHFI changed '%ls'"), data->wt_moved.wpath);
163
+ return -1;
164
+ }
165
+ return 0;
166
+
167
+ default:
168
+ die(_("unhandled case in 'has_worktree_moved': %d"),
169
+ (int)ctx);
170
+ }
171
+
172
+ return 0;
173
+}
174
+
175
+
176
int fsm_health__ctor(struct fsmonitor_daemon_state *state)
177
{
178
struct fsm_health_data *data;
206
* A table of the polling functions.
207
*/
208
static interval_fn *table[] = {
209
+ has_worktree_moved,
210
NULL, /* must be last */
211
};
212