]> git.ipfire.org Git - thirdparty/git.git/blob - compat/fsmonitor/fsm-health-win32.c
Merge branch 'jb/reflog-expire-delete-dry-run-options'
[thirdparty/git.git] / compat / fsmonitor / fsm-health-win32.c
1 #include "git-compat-util.h"
2 #include "config.h"
3 #include "fsmonitor-ll.h"
4 #include "fsm-health.h"
5 #include "fsmonitor--daemon.h"
6 #include "gettext.h"
7
8 /*
9 * Every minute wake up and test our health.
10 */
11 #define WAIT_FREQ_MS (60 * 1000)
12
13 /*
14 * State machine states for each of the interval functions
15 * used for polling our health.
16 */
17 enum interval_fn_ctx {
18 CTX_INIT = 0,
19 CTX_TERM,
20 CTX_TIMER
21 };
22
23 typedef int (interval_fn)(struct fsmonitor_daemon_state *state,
24 enum interval_fn_ctx ctx);
25
26 struct fsm_health_data
27 {
28 HANDLE hEventShutdown;
29
30 HANDLE hHandles[1]; /* the array does not own these handles */
31 #define HEALTH_SHUTDOWN 0
32 int nr_handles; /* number of active event handles */
33
34 struct wt_moved
35 {
36 wchar_t wpath[MAX_PATH + 1];
37 BY_HANDLE_FILE_INFORMATION bhfi;
38 } wt_moved;
39 };
40
41 /*
42 * Lookup the system unique ID for the path. This is as close as
43 * we get to an inode number, but this also contains volume info,
44 * so it is a little stronger.
45 */
46 static int lookup_bhfi(wchar_t *wpath,
47 BY_HANDLE_FILE_INFORMATION *bhfi)
48 {
49 DWORD desired_access = FILE_LIST_DIRECTORY;
50 DWORD share_mode =
51 FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
52 HANDLE hDir;
53
54 hDir = CreateFileW(wpath, desired_access, share_mode, NULL,
55 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
56 if (hDir == INVALID_HANDLE_VALUE) {
57 error(_("[GLE %ld] health thread could not open '%ls'"),
58 GetLastError(), wpath);
59 return -1;
60 }
61
62 if (!GetFileInformationByHandle(hDir, bhfi)) {
63 error(_("[GLE %ld] health thread getting BHFI for '%ls'"),
64 GetLastError(), wpath);
65 CloseHandle(hDir);
66 return -1;
67 }
68
69 CloseHandle(hDir);
70 return 0;
71 }
72
73 /*
74 * Compare the relevant fields from two system unique IDs.
75 * We use this to see if two different handles to the same
76 * path actually refer to the same *instance* of the file
77 * or directory.
78 */
79 static int bhfi_eq(const BY_HANDLE_FILE_INFORMATION *bhfi_1,
80 const BY_HANDLE_FILE_INFORMATION *bhfi_2)
81 {
82 return (bhfi_1->dwVolumeSerialNumber == bhfi_2->dwVolumeSerialNumber &&
83 bhfi_1->nFileIndexHigh == bhfi_2->nFileIndexHigh &&
84 bhfi_1->nFileIndexLow == bhfi_2->nFileIndexLow);
85 }
86
87 /*
88 * Shutdown if the original worktree root directory been deleted,
89 * moved, or renamed?
90 *
91 * Since the main thread did a "chdir(getenv($HOME))" and our CWD
92 * is not in the worktree root directory and because the listener
93 * thread added FILE_SHARE_DELETE to the watch handle, it is possible
94 * for the root directory to be moved or deleted while we are still
95 * watching it. We want to detect that here and force a shutdown.
96 *
97 * Granted, a delete MAY cause some operations to fail, such as
98 * GetOverlappedResult(), but it is not guaranteed. And because
99 * ReadDirectoryChangesW() only reports on changes *WITHIN* the
100 * directory, not changes *ON* the directory, our watch will not
101 * receive a delete event for it.
102 *
103 * A move/rename of the worktree root will also not generate an event.
104 * And since the listener thread already has an open handle, it may
105 * continue to receive events for events within the directory.
106 * However, the pathname of the named-pipe was constructed using the
107 * original location of the worktree root. (Remember named-pipes are
108 * stored in the NPFS and not in the actual file system.) Clients
109 * trying to talk to the worktree after the move/rename will not
110 * reach our daemon process, since we're still listening on the
111 * pipe with original path.
112 *
113 * Furthermore, if the user does something like:
114 *
115 * $ mv repo repo.old
116 * $ git init repo
117 *
118 * A new daemon cannot be started in the new instance of "repo"
119 * because the named-pipe is still being used by the daemon on
120 * the original instance.
121 *
122 * So, detect move/rename/delete and shutdown. This should also
123 * handle unsafe drive removal.
124 *
125 * We use the file system unique ID to distinguish the original
126 * directory instance from a new instance and force a shutdown
127 * if the unique ID changes.
128 *
129 * Since a worktree move/rename/delete/unmount doesn't happen
130 * that often (and we can't get an immediate event anyway), we
131 * use a timeout and periodically poll it.
132 */
133 static int has_worktree_moved(struct fsmonitor_daemon_state *state,
134 enum interval_fn_ctx ctx)
135 {
136 struct fsm_health_data *data = state->health_data;
137 BY_HANDLE_FILE_INFORMATION bhfi;
138 int r;
139
140 switch (ctx) {
141 case CTX_TERM:
142 return 0;
143
144 case CTX_INIT:
145 if (xutftowcs_path(data->wt_moved.wpath,
146 state->path_worktree_watch.buf) < 0) {
147 error(_("could not convert to wide characters: '%s'"),
148 state->path_worktree_watch.buf);
149 return -1;
150 }
151
152 /*
153 * On the first call we lookup the unique sequence ID for
154 * the worktree root directory.
155 */
156 return lookup_bhfi(data->wt_moved.wpath, &data->wt_moved.bhfi);
157
158 case CTX_TIMER:
159 r = lookup_bhfi(data->wt_moved.wpath, &bhfi);
160 if (r)
161 return r;
162 if (!bhfi_eq(&data->wt_moved.bhfi, &bhfi)) {
163 error(_("BHFI changed '%ls'"), data->wt_moved.wpath);
164 return -1;
165 }
166 return 0;
167
168 default:
169 die(_("unhandled case in 'has_worktree_moved': %d"),
170 (int)ctx);
171 }
172
173 return 0;
174 }
175
176
177 int fsm_health__ctor(struct fsmonitor_daemon_state *state)
178 {
179 struct fsm_health_data *data;
180
181 CALLOC_ARRAY(data, 1);
182
183 data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
184
185 data->hHandles[HEALTH_SHUTDOWN] = data->hEventShutdown;
186 data->nr_handles++;
187
188 state->health_data = data;
189 return 0;
190 }
191
192 void fsm_health__dtor(struct fsmonitor_daemon_state *state)
193 {
194 struct fsm_health_data *data;
195
196 if (!state || !state->health_data)
197 return;
198
199 data = state->health_data;
200
201 CloseHandle(data->hEventShutdown);
202
203 FREE_AND_NULL(state->health_data);
204 }
205
206 /*
207 * A table of the polling functions.
208 */
209 static interval_fn *table[] = {
210 has_worktree_moved,
211 NULL, /* must be last */
212 };
213
214 /*
215 * Call all of the polling functions in the table.
216 * Shortcut and return first error.
217 *
218 * Return 0 if all succeeded.
219 */
220 static int call_all(struct fsmonitor_daemon_state *state,
221 enum interval_fn_ctx ctx)
222 {
223 int k;
224
225 for (k = 0; table[k]; k++) {
226 int r = table[k](state, ctx);
227 if (r)
228 return r;
229 }
230
231 return 0;
232 }
233
234 void fsm_health__loop(struct fsmonitor_daemon_state *state)
235 {
236 struct fsm_health_data *data = state->health_data;
237 int r;
238
239 r = call_all(state, CTX_INIT);
240 if (r < 0)
241 goto force_error_stop;
242 if (r > 0)
243 goto force_shutdown;
244
245 for (;;) {
246 DWORD dwWait = WaitForMultipleObjects(data->nr_handles,
247 data->hHandles,
248 FALSE, WAIT_FREQ_MS);
249
250 if (dwWait == WAIT_OBJECT_0 + HEALTH_SHUTDOWN)
251 goto clean_shutdown;
252
253 if (dwWait == WAIT_TIMEOUT) {
254 r = call_all(state, CTX_TIMER);
255 if (r < 0)
256 goto force_error_stop;
257 if (r > 0)
258 goto force_shutdown;
259 continue;
260 }
261
262 error(_("health thread wait failed [GLE %ld]"),
263 GetLastError());
264 goto force_error_stop;
265 }
266
267 force_error_stop:
268 state->health_error_code = -1;
269 force_shutdown:
270 ipc_server_stop_async(state->ipc_server_data);
271 clean_shutdown:
272 call_all(state, CTX_TERM);
273 return;
274 }
275
276 void fsm_health__stop_async(struct fsmonitor_daemon_state *state)
277 {
278 SetEvent(state->health_data->hHandles[HEALTH_SHUTDOWN]);
279 }