]>
Commit | Line | Data |
---|---|---|
bc5c5ec0 | 1 | #include "git-compat-util.h" |
62c73671 | 2 | #include "config.h" |
68d68646 | 3 | #include "fsmonitor-ll.h" |
62c73671 | 4 | #include "fsm-listen.h" |
1448edfb | 5 | #include "fsmonitor--daemon.h" |
f394e093 | 6 | #include "gettext.h" |
74ea5c95 | 7 | #include "trace2.h" |
1448edfb JH |
8 | |
9 | /* | |
10 | * The documentation of ReadDirectoryChangesW() states that the maximum | |
11 | * buffer size is 64K when the monitored directory is remote. | |
12 | * | |
13 | * Larger buffers may be used when the monitored directory is local and | |
14 | * will help us receive events faster from the kernel and avoid dropped | |
15 | * events. | |
16 | * | |
17 | * So we try to use a very large buffer and silently fallback to 64K if | |
18 | * we get an error. | |
19 | */ | |
20 | #define MAX_RDCW_BUF_FALLBACK (65536) | |
21 | #define MAX_RDCW_BUF (65536 * 8) | |
22 | ||
23 | struct one_watch | |
24 | { | |
25 | char buffer[MAX_RDCW_BUF]; | |
26 | DWORD buf_len; | |
27 | DWORD count; | |
28 | ||
29 | struct strbuf path; | |
40f865dc JH |
30 | wchar_t wpath_longname[MAX_PATH + 1]; |
31 | DWORD wpath_longname_len; | |
32 | ||
1448edfb JH |
33 | HANDLE hDir; |
34 | HANDLE hEvent; | |
35 | OVERLAPPED overlapped; | |
36 | ||
37 | /* | |
38 | * Is there an active ReadDirectoryChangesW() call pending. If so, we | |
39 | * need to later call GetOverlappedResult() and possibly CancelIoEx(). | |
40 | */ | |
41 | BOOL is_active; | |
40f865dc JH |
42 | |
43 | /* | |
44 | * Are shortnames enabled on the containing drive? This is | |
45 | * always true for "C:/" drives and usually never true for | |
46 | * other drives. | |
47 | * | |
48 | * We only set this for the worktree because we only need to | |
49 | * convert shortname paths to longname paths for items we send | |
50 | * to clients. (We don't care about shortname expansion for | |
51 | * paths inside a GITDIR because we never send them to | |
52 | * clients.) | |
53 | */ | |
54 | BOOL has_shortnames; | |
55 | BOOL has_tilde; | |
56 | wchar_t dotgit_shortname[16]; /* for 8.3 name */ | |
1448edfb JH |
57 | }; |
58 | ||
207534e4 | 59 | struct fsm_listen_data |
1448edfb JH |
60 | { |
61 | struct one_watch *watch_worktree; | |
62 | struct one_watch *watch_gitdir; | |
63 | ||
64 | HANDLE hEventShutdown; | |
65 | ||
66 | HANDLE hListener[3]; /* we don't own these handles */ | |
67 | #define LISTENER_SHUTDOWN 0 | |
68 | #define LISTENER_HAVE_DATA_WORKTREE 1 | |
69 | #define LISTENER_HAVE_DATA_GITDIR 2 | |
70 | int nr_listener_handles; | |
71 | }; | |
72 | ||
73 | /* | |
40f865dc JH |
74 | * Convert the WCHAR path from the event into UTF8 and normalize it. |
75 | * | |
76 | * `wpath_len` is in WCHARS not bytes. | |
1448edfb | 77 | */ |
40f865dc | 78 | static int normalize_path_in_utf8(wchar_t *wpath, DWORD wpath_len, |
1448edfb JH |
79 | struct strbuf *normalized_path) |
80 | { | |
81 | int reserve; | |
82 | int len = 0; | |
83 | ||
84 | strbuf_reset(normalized_path); | |
40f865dc | 85 | if (!wpath_len) |
1448edfb JH |
86 | goto normalize; |
87 | ||
88 | /* | |
89 | * Pre-reserve enough space in the UTF8 buffer for | |
90 | * each Unicode WCHAR character to be mapped into a | |
91 | * sequence of 2 UTF8 characters. That should let us | |
92 | * avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time. | |
93 | */ | |
40f865dc | 94 | reserve = 2 * wpath_len + 1; |
1448edfb JH |
95 | strbuf_grow(normalized_path, reserve); |
96 | ||
97 | for (;;) { | |
40f865dc JH |
98 | len = WideCharToMultiByte(CP_UTF8, 0, |
99 | wpath, wpath_len, | |
1448edfb JH |
100 | normalized_path->buf, |
101 | strbuf_avail(normalized_path) - 1, | |
102 | NULL, NULL); | |
103 | if (len > 0) | |
104 | goto normalize; | |
105 | if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { | |
106 | error(_("[GLE %ld] could not convert path to UTF-8: '%.*ls'"), | |
40f865dc | 107 | GetLastError(), (int)wpath_len, wpath); |
1448edfb JH |
108 | return -1; |
109 | } | |
110 | ||
111 | strbuf_grow(normalized_path, | |
112 | strbuf_avail(normalized_path) + reserve); | |
113 | } | |
114 | ||
115 | normalize: | |
116 | strbuf_setlen(normalized_path, len); | |
117 | return strbuf_normalize_path(normalized_path); | |
118 | } | |
62c73671 | 119 | |
40f865dc JH |
120 | /* |
121 | * See if the worktree root directory has shortnames enabled. | |
122 | * This will help us decide if we need to do an expensive shortname | |
123 | * to longname conversion on every notification event. | |
124 | * | |
125 | * We do not want to create a file to test this, so we assume that the | |
126 | * root directory contains a ".git" file or directory. (Our caller | |
127 | * only calls us for the worktree root, so this should be fine.) | |
128 | * | |
129 | * Remember the spelling of the shortname for ".git" if it exists. | |
130 | */ | |
131 | static void check_for_shortnames(struct one_watch *watch) | |
132 | { | |
133 | wchar_t buf_in[MAX_PATH + 1]; | |
134 | wchar_t buf_out[MAX_PATH + 1]; | |
135 | wchar_t *last; | |
136 | wchar_t *p; | |
137 | ||
138 | /* build L"<wt-root-path>/.git" */ | |
139 | swprintf(buf_in, ARRAY_SIZE(buf_in) - 1, L"%ls.git", | |
140 | watch->wpath_longname); | |
141 | ||
142 | if (!GetShortPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out))) | |
143 | return; | |
144 | ||
145 | /* | |
146 | * Get the final filename component of the shortpath. | |
147 | * We know that the path does not have a final slash. | |
148 | */ | |
149 | for (last = p = buf_out; *p; p++) | |
150 | if (*p == L'/' || *p == '\\') | |
151 | last = p + 1; | |
152 | ||
153 | if (!wcscmp(last, L".git")) | |
154 | return; | |
155 | ||
156 | watch->has_shortnames = 1; | |
157 | wcsncpy(watch->dotgit_shortname, last, | |
158 | ARRAY_SIZE(watch->dotgit_shortname)); | |
159 | ||
160 | /* | |
161 | * The shortname for ".git" is usually of the form "GIT~1", so | |
162 | * we should be able to avoid shortname to longname mapping on | |
163 | * every notification event if the source string does not | |
164 | * contain a "~". | |
165 | * | |
166 | * However, the documentation for GetLongPathNameW() says | |
167 | * that there are filesystems that don't follow that pattern | |
168 | * and warns against this optimization. | |
169 | * | |
170 | * Lets test this. | |
171 | */ | |
172 | if (wcschr(watch->dotgit_shortname, L'~')) | |
173 | watch->has_tilde = 1; | |
174 | } | |
175 | ||
176 | enum get_relative_result { | |
177 | GRR_NO_CONVERSION_NEEDED, | |
178 | GRR_HAVE_CONVERSION, | |
179 | GRR_SHUTDOWN, | |
180 | }; | |
181 | ||
182 | /* | |
183 | * Info notification paths are relative to the root of the watch. | |
184 | * If our CWD is still at the root, then we can use relative paths | |
185 | * to convert from shortnames to longnames. If our process has a | |
186 | * different CWD, then we need to construct an absolute path, do | |
187 | * the conversion, and then return the root-relative portion. | |
188 | * | |
189 | * We use the longname form of the root as our basis and assume that | |
190 | * it already has a trailing slash. | |
191 | * | |
192 | * `wpath_len` is in WCHARS not bytes. | |
193 | */ | |
194 | static enum get_relative_result get_relative_longname( | |
195 | struct one_watch *watch, | |
196 | const wchar_t *wpath, DWORD wpath_len, | |
197 | wchar_t *wpath_longname, size_t bufsize_wpath_longname) | |
198 | { | |
199 | wchar_t buf_in[2 * MAX_PATH + 1]; | |
200 | wchar_t buf_out[MAX_PATH + 1]; | |
201 | DWORD root_len; | |
202 | DWORD out_len; | |
203 | ||
204 | /* | |
205 | * Build L"<wt-root-path>/<event-rel-path>" | |
206 | * Note that the <event-rel-path> might not be null terminated | |
207 | * so we avoid swprintf() constructions. | |
208 | */ | |
209 | root_len = watch->wpath_longname_len; | |
210 | if (root_len + wpath_len >= ARRAY_SIZE(buf_in)) { | |
211 | /* | |
212 | * This should not happen. We cannot append the observed | |
213 | * relative path onto the end of the worktree root path | |
214 | * without overflowing the buffer. Just give up. | |
215 | */ | |
216 | return GRR_SHUTDOWN; | |
217 | } | |
218 | wcsncpy(buf_in, watch->wpath_longname, root_len); | |
219 | wcsncpy(buf_in + root_len, wpath, wpath_len); | |
220 | buf_in[root_len + wpath_len] = 0; | |
221 | ||
222 | /* | |
223 | * We don't actually know if the source pathname is a | |
224 | * shortname or a longname. This Windows routine allows | |
225 | * either to be given as input. | |
226 | */ | |
227 | out_len = GetLongPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out)); | |
228 | if (!out_len) { | |
229 | /* | |
230 | * The shortname to longname conversion can fail for | |
231 | * various reasons, for example if the file has been | |
232 | * deleted. (That is, if we just received a | |
233 | * delete-file notification event and the file is | |
234 | * already gone, we can't ask the file system to | |
235 | * lookup the longname for it. Likewise, for moves | |
236 | * and renames where we are given the old name.) | |
237 | * | |
238 | * Since deleting or moving a file or directory by its | |
239 | * shortname is rather obscure, I'm going ignore the | |
240 | * failure and ask the caller to report the original | |
241 | * relative path. This seems kinder than failing here | |
242 | * and forcing a resync. Besides, forcing a resync on | |
243 | * every file/directory delete would effectively | |
244 | * cripple monitoring. | |
245 | * | |
246 | * We might revisit this in the future. | |
247 | */ | |
248 | return GRR_NO_CONVERSION_NEEDED; | |
249 | } | |
250 | ||
251 | if (!wcscmp(buf_in, buf_out)) { | |
252 | /* | |
253 | * The path does not have a shortname alias. | |
254 | */ | |
255 | return GRR_NO_CONVERSION_NEEDED; | |
256 | } | |
257 | ||
258 | if (wcsncmp(buf_in, buf_out, root_len)) { | |
259 | /* | |
260 | * The spelling of the root directory portion of the computed | |
261 | * longname has changed. This should not happen. Basically, | |
262 | * it means that we don't know where (without recomputing the | |
263 | * longname of just the root directory) to split out the | |
264 | * relative path. Since this should not happen, I'm just | |
265 | * going to let this fail and force a shutdown (because all | |
266 | * subsequent events are probably going to see the same | |
267 | * mismatch). | |
268 | */ | |
269 | return GRR_SHUTDOWN; | |
270 | } | |
271 | ||
272 | if (out_len - root_len >= bufsize_wpath_longname) { | |
273 | /* | |
274 | * This should not happen. We cannot copy the root-relative | |
275 | * portion of the path into the provided buffer without an | |
276 | * overrun. Just give up. | |
277 | */ | |
278 | return GRR_SHUTDOWN; | |
279 | } | |
280 | ||
281 | /* Return the worktree root-relative portion of the longname. */ | |
282 | ||
283 | wcscpy(wpath_longname, buf_out + root_len); | |
284 | return GRR_HAVE_CONVERSION; | |
285 | } | |
286 | ||
62c73671 JH |
287 | void fsm_listen__stop_async(struct fsmonitor_daemon_state *state) |
288 | { | |
207534e4 | 289 | SetEvent(state->listen_data->hListener[LISTENER_SHUTDOWN]); |
1448edfb JH |
290 | } |
291 | ||
292 | static struct one_watch *create_watch(struct fsmonitor_daemon_state *state, | |
293 | const char *path) | |
294 | { | |
295 | struct one_watch *watch = NULL; | |
296 | DWORD desired_access = FILE_LIST_DIRECTORY; | |
297 | DWORD share_mode = | |
298 | FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; | |
299 | HANDLE hDir; | |
40f865dc JH |
300 | DWORD len_longname; |
301 | wchar_t wpath[MAX_PATH + 1]; | |
302 | wchar_t wpath_longname[MAX_PATH + 1]; | |
1448edfb JH |
303 | |
304 | if (xutftowcs_path(wpath, path) < 0) { | |
305 | error(_("could not convert to wide characters: '%s'"), path); | |
306 | return NULL; | |
307 | } | |
308 | ||
309 | hDir = CreateFileW(wpath, | |
310 | desired_access, share_mode, NULL, OPEN_EXISTING, | |
311 | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, | |
312 | NULL); | |
313 | if (hDir == INVALID_HANDLE_VALUE) { | |
314 | error(_("[GLE %ld] could not watch '%s'"), | |
315 | GetLastError(), path); | |
316 | return NULL; | |
317 | } | |
318 | ||
40f865dc JH |
319 | len_longname = GetLongPathNameW(wpath, wpath_longname, |
320 | ARRAY_SIZE(wpath_longname)); | |
321 | if (!len_longname) { | |
322 | error(_("[GLE %ld] could not get longname of '%s'"), | |
323 | GetLastError(), path); | |
324 | CloseHandle(hDir); | |
325 | return NULL; | |
326 | } | |
327 | ||
328 | if (wpath_longname[len_longname - 1] != L'/' && | |
329 | wpath_longname[len_longname - 1] != L'\\') { | |
330 | wpath_longname[len_longname++] = L'/'; | |
331 | wpath_longname[len_longname] = 0; | |
332 | } | |
333 | ||
1448edfb JH |
334 | CALLOC_ARRAY(watch, 1); |
335 | ||
336 | watch->buf_len = sizeof(watch->buffer); /* assume full MAX_RDCW_BUF */ | |
337 | ||
338 | strbuf_init(&watch->path, 0); | |
339 | strbuf_addstr(&watch->path, path); | |
340 | ||
40f865dc JH |
341 | wcscpy(watch->wpath_longname, wpath_longname); |
342 | watch->wpath_longname_len = len_longname; | |
343 | ||
1448edfb JH |
344 | watch->hDir = hDir; |
345 | watch->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); | |
346 | ||
347 | return watch; | |
348 | } | |
349 | ||
350 | static void destroy_watch(struct one_watch *watch) | |
351 | { | |
352 | if (!watch) | |
353 | return; | |
354 | ||
355 | strbuf_release(&watch->path); | |
356 | if (watch->hDir != INVALID_HANDLE_VALUE) | |
357 | CloseHandle(watch->hDir); | |
358 | if (watch->hEvent != INVALID_HANDLE_VALUE) | |
359 | CloseHandle(watch->hEvent); | |
360 | ||
361 | free(watch); | |
362 | } | |
363 | ||
207534e4 | 364 | static int start_rdcw_watch(struct fsm_listen_data *data, |
1448edfb JH |
365 | struct one_watch *watch) |
366 | { | |
367 | DWORD dwNotifyFilter = | |
368 | FILE_NOTIFY_CHANGE_FILE_NAME | | |
369 | FILE_NOTIFY_CHANGE_DIR_NAME | | |
370 | FILE_NOTIFY_CHANGE_ATTRIBUTES | | |
371 | FILE_NOTIFY_CHANGE_SIZE | | |
372 | FILE_NOTIFY_CHANGE_LAST_WRITE | | |
373 | FILE_NOTIFY_CHANGE_CREATION; | |
374 | ||
375 | ResetEvent(watch->hEvent); | |
376 | ||
377 | memset(&watch->overlapped, 0, sizeof(watch->overlapped)); | |
378 | watch->overlapped.hEvent = watch->hEvent; | |
379 | ||
380 | /* | |
381 | * Queue an async call using Overlapped IO. This returns immediately. | |
382 | * Our event handle will be signalled when the real result is available. | |
383 | * | |
384 | * The return value here just means that we successfully queued it. | |
385 | * We won't know if the Read...() actually produces data until later. | |
386 | */ | |
387 | watch->is_active = ReadDirectoryChangesW( | |
388 | watch->hDir, watch->buffer, watch->buf_len, TRUE, | |
389 | dwNotifyFilter, &watch->count, &watch->overlapped, NULL); | |
390 | ||
391 | if (watch->is_active) | |
392 | return 0; | |
393 | ||
394 | error(_("ReadDirectoryChangedW failed on '%s' [GLE %ld]"), | |
395 | watch->path.buf, GetLastError()); | |
396 | return -1; | |
397 | } | |
398 | ||
399 | static int recv_rdcw_watch(struct one_watch *watch) | |
400 | { | |
401 | DWORD gle; | |
402 | ||
403 | watch->is_active = FALSE; | |
404 | ||
405 | /* | |
406 | * The overlapped result is ready. If the Read...() was successful | |
407 | * we finally receive the actual result into our buffer. | |
408 | */ | |
409 | if (GetOverlappedResult(watch->hDir, &watch->overlapped, &watch->count, | |
410 | TRUE)) | |
411 | return 0; | |
412 | ||
413 | gle = GetLastError(); | |
414 | if (gle == ERROR_INVALID_PARAMETER && | |
415 | /* | |
416 | * The kernel throws an invalid parameter error when our | |
417 | * buffer is too big and we are pointed at a remote | |
418 | * directory (and possibly for other reasons). Quietly | |
419 | * set it down and try again. | |
420 | * | |
421 | * See note about MAX_RDCW_BUF at the top. | |
422 | */ | |
423 | watch->buf_len > MAX_RDCW_BUF_FALLBACK) { | |
424 | watch->buf_len = MAX_RDCW_BUF_FALLBACK; | |
425 | return -2; | |
426 | } | |
427 | ||
428 | /* | |
39664e93 JH |
429 | * GetOverlappedResult() fails if the watched directory is |
430 | * deleted while we were waiting for an overlapped IO to | |
431 | * complete. The documentation did not list specific errors, | |
432 | * but I observed ERROR_ACCESS_DENIED (0x05) errors during | |
433 | * testing. | |
434 | * | |
435 | * Note that we only get notificaiton events for events | |
436 | * *within* the directory, not *on* the directory itself. | |
437 | * (These might be properies of the parent directory, for | |
438 | * example). | |
439 | * | |
440 | * NEEDSWORK: We might try to check for the deleted directory | |
441 | * case and return a better error message, but I'm not sure it | |
442 | * is worth it. | |
443 | * | |
444 | * Shutdown if we get any error. | |
1448edfb JH |
445 | */ |
446 | ||
447 | error(_("GetOverlappedResult failed on '%s' [GLE %ld]"), | |
448 | watch->path.buf, gle); | |
449 | return -1; | |
450 | } | |
451 | ||
452 | static void cancel_rdcw_watch(struct one_watch *watch) | |
453 | { | |
454 | DWORD count; | |
455 | ||
456 | if (!watch || !watch->is_active) | |
457 | return; | |
458 | ||
459 | /* | |
460 | * The calls to ReadDirectoryChangesW() and GetOverlappedResult() | |
461 | * form a "pair" (my term) where we queue an IO and promise to | |
462 | * hang around and wait for the kernel to give us the result. | |
463 | * | |
464 | * If for some reason after we queue the IO, we have to quit | |
465 | * or otherwise not stick around for the second half, we must | |
466 | * tell the kernel to abort the IO. This prevents the kernel | |
467 | * from writing to our buffer and/or signalling our event | |
468 | * after we free them. | |
469 | * | |
470 | * (Ask me how much fun it was to track that one down). | |
471 | */ | |
472 | CancelIoEx(watch->hDir, &watch->overlapped); | |
473 | GetOverlappedResult(watch->hDir, &watch->overlapped, &count, TRUE); | |
474 | watch->is_active = FALSE; | |
475 | } | |
476 | ||
40f865dc JH |
477 | /* |
478 | * Process a single relative pathname event. | |
479 | * Return 1 if we should shutdown. | |
480 | */ | |
481 | static int process_1_worktree_event( | |
482 | struct string_list *cookie_list, | |
483 | struct fsmonitor_batch **batch, | |
484 | const struct strbuf *path, | |
485 | enum fsmonitor_path_type t, | |
486 | DWORD info_action) | |
487 | { | |
488 | const char *slash; | |
489 | ||
490 | switch (t) { | |
491 | case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX: | |
492 | /* special case cookie files within .git */ | |
493 | ||
494 | /* Use just the filename of the cookie file. */ | |
495 | slash = find_last_dir_sep(path->buf); | |
496 | string_list_append(cookie_list, | |
497 | slash ? slash + 1 : path->buf); | |
498 | break; | |
499 | ||
500 | case IS_INSIDE_DOT_GIT: | |
501 | /* ignore everything inside of "<worktree>/.git/" */ | |
502 | break; | |
503 | ||
504 | case IS_DOT_GIT: | |
505 | /* "<worktree>/.git" was deleted (or renamed away) */ | |
506 | if ((info_action == FILE_ACTION_REMOVED) || | |
507 | (info_action == FILE_ACTION_RENAMED_OLD_NAME)) { | |
508 | trace2_data_string("fsmonitor", NULL, | |
509 | "fsm-listen/dotgit", | |
510 | "removed"); | |
511 | return 1; | |
512 | } | |
513 | break; | |
514 | ||
515 | case IS_WORKDIR_PATH: | |
516 | /* queue normal pathname */ | |
517 | if (!*batch) | |
518 | *batch = fsmonitor_batch__new(); | |
519 | fsmonitor_batch__add_path(*batch, path->buf); | |
520 | break; | |
521 | ||
522 | case IS_GITDIR: | |
523 | case IS_INSIDE_GITDIR: | |
524 | case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX: | |
525 | default: | |
526 | BUG("unexpected path classification '%d' for '%s'", | |
527 | t, path->buf); | |
528 | } | |
529 | ||
530 | return 0; | |
531 | } | |
532 | ||
1448edfb JH |
533 | /* |
534 | * Process filesystem events that happen anywhere (recursively) under the | |
535 | * <worktree> root directory. For a normal working directory, this includes | |
536 | * both version controlled files and the contents of the .git/ directory. | |
537 | * | |
538 | * If <worktree>/.git is a file, then we only see events for the file | |
539 | * itself. | |
540 | */ | |
541 | static int process_worktree_events(struct fsmonitor_daemon_state *state) | |
542 | { | |
207534e4 | 543 | struct fsm_listen_data *data = state->listen_data; |
1448edfb JH |
544 | struct one_watch *watch = data->watch_worktree; |
545 | struct strbuf path = STRBUF_INIT; | |
546 | struct string_list cookie_list = STRING_LIST_INIT_DUP; | |
547 | struct fsmonitor_batch *batch = NULL; | |
548 | const char *p = watch->buffer; | |
40f865dc | 549 | wchar_t wpath_longname[MAX_PATH + 1]; |
1448edfb JH |
550 | |
551 | /* | |
552 | * If the kernel gets more events than will fit in the kernel | |
553 | * buffer associated with our RDCW handle, it drops them and | |
554 | * returns a count of zero. | |
555 | * | |
556 | * Yes, the call returns WITHOUT error and with length zero. | |
557 | * This is the documented behavior. (My testing has confirmed | |
558 | * that it also sets the last error to ERROR_NOTIFY_ENUM_DIR, | |
559 | * but we do not rely on that since the function did not | |
560 | * return an error and it is not documented.) | |
561 | * | |
562 | * (The "overflow" case is not ambiguous with the "no data" case | |
563 | * because we did an INFINITE wait.) | |
564 | * | |
565 | * This means we have a gap in coverage. Tell the daemon layer | |
566 | * to resync. | |
567 | */ | |
568 | if (!watch->count) { | |
569 | trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel", | |
570 | "overflow"); | |
571 | fsmonitor_force_resync(state); | |
572 | return LISTENER_HAVE_DATA_WORKTREE; | |
573 | } | |
574 | ||
575 | /* | |
576 | * On Windows, `info` contains an "array" of paths that are | |
577 | * relative to the root of whichever directory handle received | |
578 | * the event. | |
579 | */ | |
580 | for (;;) { | |
581 | FILE_NOTIFY_INFORMATION *info = (void *)p; | |
40f865dc JH |
582 | wchar_t *wpath = info->FileName; |
583 | DWORD wpath_len = info->FileNameLength / sizeof(WCHAR); | |
1448edfb | 584 | enum fsmonitor_path_type t; |
40f865dc JH |
585 | enum get_relative_result grr; |
586 | ||
587 | if (watch->has_shortnames) { | |
588 | if (!wcscmp(wpath, watch->dotgit_shortname)) { | |
589 | /* | |
590 | * This event exactly matches the | |
591 | * spelling of the shortname of | |
592 | * ".git", so we can skip some steps. | |
593 | * | |
594 | * (This case is odd because the user | |
595 | * can "rm -rf GIT~1" and we cannot | |
596 | * use the filesystem to map it back | |
597 | * to ".git".) | |
598 | */ | |
599 | strbuf_reset(&path); | |
600 | strbuf_addstr(&path, ".git"); | |
601 | t = IS_DOT_GIT; | |
602 | goto process_it; | |
603 | } | |
1448edfb | 604 | |
40f865dc JH |
605 | if (watch->has_tilde && !wcschr(wpath, L'~')) { |
606 | /* | |
607 | * Shortnames on this filesystem have tildes | |
608 | * and the notification path does not have | |
609 | * one, so we assume that it is a longname. | |
610 | */ | |
611 | goto normalize_it; | |
612 | } | |
1448edfb | 613 | |
40f865dc JH |
614 | grr = get_relative_longname(watch, wpath, wpath_len, |
615 | wpath_longname, | |
616 | ARRAY_SIZE(wpath_longname)); | |
617 | switch (grr) { | |
618 | case GRR_NO_CONVERSION_NEEDED: /* use info buffer as is */ | |
619 | break; | |
620 | case GRR_HAVE_CONVERSION: | |
621 | wpath = wpath_longname; | |
622 | wpath_len = wcslen(wpath); | |
623 | break; | |
624 | default: | |
625 | case GRR_SHUTDOWN: | |
1448edfb JH |
626 | goto force_shutdown; |
627 | } | |
40f865dc | 628 | } |
1448edfb | 629 | |
40f865dc JH |
630 | normalize_it: |
631 | if (normalize_path_in_utf8(wpath, wpath_len, &path) == -1) | |
632 | goto skip_this_path; | |
1448edfb | 633 | |
40f865dc JH |
634 | t = fsmonitor_classify_path_workdir_relative(path.buf); |
635 | ||
636 | process_it: | |
637 | if (process_1_worktree_event(&cookie_list, &batch, &path, t, | |
638 | info->Action)) | |
639 | goto force_shutdown; | |
1448edfb JH |
640 | |
641 | skip_this_path: | |
642 | if (!info->NextEntryOffset) | |
643 | break; | |
644 | p += info->NextEntryOffset; | |
645 | } | |
646 | ||
647 | fsmonitor_publish(state, batch, &cookie_list); | |
648 | batch = NULL; | |
649 | string_list_clear(&cookie_list, 0); | |
650 | strbuf_release(&path); | |
651 | return LISTENER_HAVE_DATA_WORKTREE; | |
652 | ||
653 | force_shutdown: | |
654 | fsmonitor_batch__free_list(batch); | |
655 | string_list_clear(&cookie_list, 0); | |
656 | strbuf_release(&path); | |
657 | return LISTENER_SHUTDOWN; | |
658 | } | |
659 | ||
660 | /* | |
661 | * Process filesystem events that happened anywhere (recursively) under the | |
662 | * external <gitdir> (such as non-primary worktrees or submodules). | |
663 | * We only care about cookie files that our client threads created here. | |
664 | * | |
665 | * Note that we DO NOT get filesystem events on the external <gitdir> | |
666 | * itself (it is not inside something that we are watching). In particular, | |
667 | * we do not get an event if the external <gitdir> is deleted. | |
40f865dc JH |
668 | * |
669 | * Also, we do not care about shortnames within the external <gitdir>, since | |
670 | * we never send these paths to clients. | |
1448edfb JH |
671 | */ |
672 | static int process_gitdir_events(struct fsmonitor_daemon_state *state) | |
673 | { | |
207534e4 | 674 | struct fsm_listen_data *data = state->listen_data; |
1448edfb JH |
675 | struct one_watch *watch = data->watch_gitdir; |
676 | struct strbuf path = STRBUF_INIT; | |
677 | struct string_list cookie_list = STRING_LIST_INIT_DUP; | |
678 | const char *p = watch->buffer; | |
679 | ||
680 | if (!watch->count) { | |
681 | trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel", | |
682 | "overflow"); | |
683 | fsmonitor_force_resync(state); | |
684 | return LISTENER_HAVE_DATA_GITDIR; | |
685 | } | |
686 | ||
687 | for (;;) { | |
688 | FILE_NOTIFY_INFORMATION *info = (void *)p; | |
689 | const char *slash; | |
690 | enum fsmonitor_path_type t; | |
691 | ||
40f865dc JH |
692 | if (normalize_path_in_utf8( |
693 | info->FileName, | |
694 | info->FileNameLength / sizeof(WCHAR), | |
695 | &path) == -1) | |
1448edfb JH |
696 | goto skip_this_path; |
697 | ||
698 | t = fsmonitor_classify_path_gitdir_relative(path.buf); | |
699 | ||
700 | switch (t) { | |
701 | case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX: | |
702 | /* special case cookie files within gitdir */ | |
703 | ||
704 | /* Use just the filename of the cookie file. */ | |
705 | slash = find_last_dir_sep(path.buf); | |
706 | string_list_append(&cookie_list, | |
707 | slash ? slash + 1 : path.buf); | |
708 | break; | |
709 | ||
710 | case IS_INSIDE_GITDIR: | |
711 | goto skip_this_path; | |
712 | ||
713 | default: | |
714 | BUG("unexpected path classification '%d' for '%s'", | |
715 | t, path.buf); | |
716 | } | |
717 | ||
718 | skip_this_path: | |
719 | if (!info->NextEntryOffset) | |
720 | break; | |
721 | p += info->NextEntryOffset; | |
722 | } | |
723 | ||
724 | fsmonitor_publish(state, NULL, &cookie_list); | |
725 | string_list_clear(&cookie_list, 0); | |
726 | strbuf_release(&path); | |
727 | return LISTENER_HAVE_DATA_GITDIR; | |
62c73671 JH |
728 | } |
729 | ||
730 | void fsm_listen__loop(struct fsmonitor_daemon_state *state) | |
731 | { | |
207534e4 | 732 | struct fsm_listen_data *data = state->listen_data; |
1448edfb JH |
733 | DWORD dwWait; |
734 | int result; | |
735 | ||
207534e4 | 736 | state->listen_error_code = 0; |
1448edfb JH |
737 | |
738 | if (start_rdcw_watch(data, data->watch_worktree) == -1) | |
739 | goto force_error_stop; | |
740 | ||
741 | if (data->watch_gitdir && | |
742 | start_rdcw_watch(data, data->watch_gitdir) == -1) | |
743 | goto force_error_stop; | |
744 | ||
745 | for (;;) { | |
746 | dwWait = WaitForMultipleObjects(data->nr_listener_handles, | |
747 | data->hListener, | |
748 | FALSE, INFINITE); | |
749 | ||
750 | if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_WORKTREE) { | |
751 | result = recv_rdcw_watch(data->watch_worktree); | |
752 | if (result == -1) { | |
753 | /* hard error */ | |
754 | goto force_error_stop; | |
755 | } | |
756 | if (result == -2) { | |
757 | /* retryable error */ | |
758 | if (start_rdcw_watch(data, data->watch_worktree) == -1) | |
759 | goto force_error_stop; | |
760 | continue; | |
761 | } | |
762 | ||
763 | /* have data */ | |
764 | if (process_worktree_events(state) == LISTENER_SHUTDOWN) | |
765 | goto force_shutdown; | |
766 | if (start_rdcw_watch(data, data->watch_worktree) == -1) | |
767 | goto force_error_stop; | |
768 | continue; | |
769 | } | |
770 | ||
771 | if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_GITDIR) { | |
772 | result = recv_rdcw_watch(data->watch_gitdir); | |
773 | if (result == -1) { | |
774 | /* hard error */ | |
775 | goto force_error_stop; | |
776 | } | |
777 | if (result == -2) { | |
778 | /* retryable error */ | |
779 | if (start_rdcw_watch(data, data->watch_gitdir) == -1) | |
780 | goto force_error_stop; | |
781 | continue; | |
782 | } | |
783 | ||
784 | /* have data */ | |
785 | if (process_gitdir_events(state) == LISTENER_SHUTDOWN) | |
786 | goto force_shutdown; | |
787 | if (start_rdcw_watch(data, data->watch_gitdir) == -1) | |
788 | goto force_error_stop; | |
789 | continue; | |
790 | } | |
791 | ||
792 | if (dwWait == WAIT_OBJECT_0 + LISTENER_SHUTDOWN) | |
793 | goto clean_shutdown; | |
794 | ||
795 | error(_("could not read directory changes [GLE %ld]"), | |
796 | GetLastError()); | |
797 | goto force_error_stop; | |
798 | } | |
799 | ||
800 | force_error_stop: | |
207534e4 | 801 | state->listen_error_code = -1; |
1448edfb JH |
802 | |
803 | force_shutdown: | |
804 | /* | |
805 | * Tell the IPC thead pool to stop (which completes the await | |
806 | * in the main thread (which will also signal this thread (if | |
807 | * we are still alive))). | |
808 | */ | |
809 | ipc_server_stop_async(state->ipc_server_data); | |
810 | ||
811 | clean_shutdown: | |
812 | cancel_rdcw_watch(data->watch_worktree); | |
813 | cancel_rdcw_watch(data->watch_gitdir); | |
62c73671 JH |
814 | } |
815 | ||
816 | int fsm_listen__ctor(struct fsmonitor_daemon_state *state) | |
817 | { | |
207534e4 | 818 | struct fsm_listen_data *data; |
1448edfb JH |
819 | |
820 | CALLOC_ARRAY(data, 1); | |
821 | ||
822 | data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL); | |
823 | ||
824 | data->watch_worktree = create_watch(state, | |
825 | state->path_worktree_watch.buf); | |
826 | if (!data->watch_worktree) | |
827 | goto failed; | |
828 | ||
40f865dc JH |
829 | check_for_shortnames(data->watch_worktree); |
830 | ||
1448edfb JH |
831 | if (state->nr_paths_watching > 1) { |
832 | data->watch_gitdir = create_watch(state, | |
833 | state->path_gitdir_watch.buf); | |
834 | if (!data->watch_gitdir) | |
835 | goto failed; | |
836 | } | |
837 | ||
838 | data->hListener[LISTENER_SHUTDOWN] = data->hEventShutdown; | |
839 | data->nr_listener_handles++; | |
840 | ||
841 | data->hListener[LISTENER_HAVE_DATA_WORKTREE] = | |
842 | data->watch_worktree->hEvent; | |
843 | data->nr_listener_handles++; | |
844 | ||
845 | if (data->watch_gitdir) { | |
846 | data->hListener[LISTENER_HAVE_DATA_GITDIR] = | |
847 | data->watch_gitdir->hEvent; | |
848 | data->nr_listener_handles++; | |
849 | } | |
850 | ||
207534e4 | 851 | state->listen_data = data; |
1448edfb JH |
852 | return 0; |
853 | ||
854 | failed: | |
855 | CloseHandle(data->hEventShutdown); | |
856 | destroy_watch(data->watch_worktree); | |
857 | destroy_watch(data->watch_gitdir); | |
858 | ||
62c73671 JH |
859 | return -1; |
860 | } | |
861 | ||
862 | void fsm_listen__dtor(struct fsmonitor_daemon_state *state) | |
863 | { | |
207534e4 | 864 | struct fsm_listen_data *data; |
1448edfb | 865 | |
207534e4 | 866 | if (!state || !state->listen_data) |
1448edfb JH |
867 | return; |
868 | ||
207534e4 | 869 | data = state->listen_data; |
1448edfb JH |
870 | |
871 | CloseHandle(data->hEventShutdown); | |
872 | destroy_watch(data->watch_worktree); | |
873 | destroy_watch(data->watch_gitdir); | |
874 | ||
207534e4 | 875 | FREE_AND_NULL(state->listen_data); |
62c73671 | 876 | } |