Merge branch 'ab/progress-cleanup' into seen
[git] / compat / fsmonitor / fsmonitor-fs-listen-win32.c
1 #include "cache.h"
2 #include "config.h"
3 #include "fsmonitor.h"
4 #include "fsmonitor-fs-listen.h"
5 #include "fsmonitor--daemon.h"
6
7 /*
8  * The documentation of ReadDirectoryChangesW() states that the maximum
9  * buffer size is 64K when the monitored directory is remote.
10  *
11  * Larger buffers may be used when the monitored directory is local and
12  * will help us receive events faster from the kernel and avoid dropped
13  * events.
14  *
15  * So we try to use a very large buffer and silently fallback to 64K if
16  * we get an error.
17  */
18 #define MAX_RDCW_BUF_FALLBACK (65536)
19 #define MAX_RDCW_BUF          (65536 * 8)
20
21 struct one_watch
22 {
23         char buffer[MAX_RDCW_BUF];
24         DWORD buf_len;
25         DWORD count;
26
27         struct strbuf path;
28         HANDLE hDir;
29         HANDLE hEvent;
30         OVERLAPPED overlapped;
31
32         /*
33          * Is there an active ReadDirectoryChangesW() call pending.  If so, we
34          * need to later call GetOverlappedResult() and possibly CancelIoEx().
35          */
36         BOOL is_active;
37 };
38
39 struct fsmonitor_daemon_backend_data
40 {
41         struct one_watch *watch_worktree;
42         struct one_watch *watch_gitdir;
43
44         HANDLE hEventShutdown;
45
46         HANDLE hListener[3]; /* we don't own these handles */
47 #define LISTENER_SHUTDOWN 0
48 #define LISTENER_HAVE_DATA_WORKTREE 1
49 #define LISTENER_HAVE_DATA_GITDIR 2
50         int nr_listener_handles;
51 };
52
53 /*
54  * Convert the WCHAR path from the notification into UTF8 and
55  * then normalize it.
56  */
57 static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info,
58                                   struct strbuf *normalized_path)
59 {
60         int reserve;
61         int len = 0;
62
63         strbuf_reset(normalized_path);
64         if (!info->FileNameLength)
65                 goto normalize;
66
67         /*
68          * Pre-reserve enough space in the UTF8 buffer for
69          * each Unicode WCHAR character to be mapped into a
70          * sequence of 2 UTF8 characters.  That should let us
71          * avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time.
72          */
73         reserve = info->FileNameLength + 1;
74         strbuf_grow(normalized_path, reserve);
75
76         for (;;) {
77                 len = WideCharToMultiByte(CP_UTF8, 0, info->FileName,
78                                           info->FileNameLength / sizeof(WCHAR),
79                                           normalized_path->buf,
80                                           strbuf_avail(normalized_path) - 1,
81                                           NULL, NULL);
82                 if (len > 0)
83                         goto normalize;
84                 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
85                         error("[GLE %ld] could not convert path to UTF-8: '%.*ls'",
86                               GetLastError(),
87                               (int)(info->FileNameLength / sizeof(WCHAR)),
88                               info->FileName);
89                         return -1;
90                 }
91
92                 strbuf_grow(normalized_path,
93                             strbuf_avail(normalized_path) + reserve);
94         }
95
96 normalize:
97         strbuf_setlen(normalized_path, len);
98         return strbuf_normalize_path(normalized_path);
99 }
100
101 void fsmonitor_fs_listen__stop_async(struct fsmonitor_daemon_state *state)
102 {
103         SetEvent(state->backend_data->hListener[LISTENER_SHUTDOWN]);
104 }
105
106 static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
107                                       const char *path)
108 {
109         struct one_watch *watch = NULL;
110         DWORD desired_access = FILE_LIST_DIRECTORY;
111         DWORD share_mode =
112                 FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
113         HANDLE hDir;
114
115         hDir = CreateFileA(path,
116                            desired_access, share_mode, NULL, OPEN_EXISTING,
117                            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
118                            NULL);
119         if (hDir == INVALID_HANDLE_VALUE) {
120                 error(_("[GLE %ld] could not watch '%s'"),
121                       GetLastError(), path);
122                 return NULL;
123         }
124
125         CALLOC_ARRAY(watch, 1);
126
127         watch->buf_len = sizeof(watch->buffer); /* assume full MAX_RDCW_BUF */
128
129         strbuf_init(&watch->path, 0);
130         strbuf_addstr(&watch->path, path);
131
132         watch->hDir = hDir;
133         watch->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
134
135         return watch;
136 }
137
138 static void destroy_watch(struct one_watch *watch)
139 {
140         if (!watch)
141                 return;
142
143         strbuf_release(&watch->path);
144         if (watch->hDir != INVALID_HANDLE_VALUE)
145                 CloseHandle(watch->hDir);
146         if (watch->hEvent != INVALID_HANDLE_VALUE)
147                 CloseHandle(watch->hEvent);
148
149         free(watch);
150 }
151
152 static int start_rdcw_watch(struct fsmonitor_daemon_backend_data *data,
153                             struct one_watch *watch)
154 {
155         DWORD dwNotifyFilter =
156                 FILE_NOTIFY_CHANGE_FILE_NAME |
157                 FILE_NOTIFY_CHANGE_DIR_NAME |
158                 FILE_NOTIFY_CHANGE_ATTRIBUTES |
159                 FILE_NOTIFY_CHANGE_SIZE |
160                 FILE_NOTIFY_CHANGE_LAST_WRITE |
161                 FILE_NOTIFY_CHANGE_CREATION;
162
163         ResetEvent(watch->hEvent);
164
165         memset(&watch->overlapped, 0, sizeof(watch->overlapped));
166         watch->overlapped.hEvent = watch->hEvent;
167
168 start_watch:
169         /*
170          * Queue an async call using Overlapped IO.  This returns immediately.
171          * Our event handle will be signalled when the real result is available.
172          *
173          * The return value here just means that we successfully queued it.
174          * We won't know if the Read...() actually produces data until later.
175          */
176         watch->is_active = ReadDirectoryChangesW(
177                 watch->hDir, watch->buffer, watch->buf_len, TRUE,
178                 dwNotifyFilter, &watch->count, &watch->overlapped, NULL);
179
180         /*
181          * The kernel throws an invalid parameter error when our buffer
182          * is too big and we are pointed at a remote directory (and possibly
183          * for other reasons).  Quietly set it down and try again.
184          *
185          * See note about MAX_RDCW_BUF at the top.
186          */
187         if (!watch->is_active &&
188             GetLastError() == ERROR_INVALID_PARAMETER &&
189             watch->buf_len > MAX_RDCW_BUF_FALLBACK) {
190                 watch->buf_len = MAX_RDCW_BUF_FALLBACK;
191                 goto start_watch;
192         }
193
194         if (watch->is_active)
195                 return 0;
196
197         error("ReadDirectoryChangedW failed on '%s' [GLE %ld]",
198               watch->path.buf, GetLastError());
199         return -1;
200 }
201
202 static int recv_rdcw_watch(struct one_watch *watch)
203 {
204         watch->is_active = FALSE;
205
206         /*
207          * The overlapped result is ready.  If the Read...() was successful
208          * we finally receive the actual result into our buffer.
209          */
210         if (GetOverlappedResult(watch->hDir, &watch->overlapped, &watch->count,
211                                 TRUE))
212                 return 0;
213
214         /*
215          * NEEDSWORK: If an external <gitdir> is deleted, the above
216          * returns an error.  I'm not sure that there's anything that
217          * we can do here other than failing -- the <worktree>/.git
218          * link file would be broken anyway.  We might try to check
219          * for that and return a better error message, but I'm not
220          * sure it is worth it.
221          */
222
223         error("GetOverlappedResult failed on '%s' [GLE %ld]",
224               watch->path.buf, GetLastError());
225         return -1;
226 }
227
228 static void cancel_rdcw_watch(struct one_watch *watch)
229 {
230         DWORD count;
231
232         if (!watch || !watch->is_active)
233                 return;
234
235         /*
236          * The calls to ReadDirectoryChangesW() and GetOverlappedResult()
237          * form a "pair" (my term) where we queue an IO and promise to
238          * hang around and wait for the kernel to give us the result.
239          *
240          * If for some reason after we queue the IO, we have to quit
241          * or otherwise not stick around for the second half, we must
242          * tell the kernel to abort the IO.  This prevents the kernel
243          * from writing to our buffer and/or signalling our event
244          * after we free them.
245          *
246          * (Ask me how much fun it was to track that one down).
247          */
248         CancelIoEx(watch->hDir, &watch->overlapped);
249         GetOverlappedResult(watch->hDir, &watch->overlapped, &count, TRUE);
250         watch->is_active = FALSE;
251 }
252
253 /*
254  * Process filesystem events that happen anywhere (recursively) under the
255  * <worktree> root directory.  For a normal working directory, this includes
256  * both version controlled files and the contents of the .git/ directory.
257  *
258  * If <worktree>/.git is a file, then we only see events for the file
259  * itself.
260  */
261 static int process_worktree_events(struct fsmonitor_daemon_state *state)
262 {
263         struct fsmonitor_daemon_backend_data *data = state->backend_data;
264         struct one_watch *watch = data->watch_worktree;
265         struct strbuf path = STRBUF_INIT;
266         struct string_list cookie_list = STRING_LIST_INIT_DUP;
267         struct fsmonitor_batch *batch = NULL;
268         const char *p = watch->buffer;
269
270         /*
271          * If the kernel gets more events than will fit in the kernel
272          * buffer associated with our RDCW handle, it drops them and
273          * returns a count of zero.
274          *
275          * Yes, the call returns WITHOUT error and with length zero.
276          *
277          * (The "overflow" case is not ambiguous with the "no data" case
278          * because we did an INFINITE wait.)
279          *
280          * This means we have a gap in coverage.  Tell the daemon layer
281          * to resync.
282          */
283         if (!watch->count) {
284                 trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel",
285                                    "overflow");
286                 fsmonitor_force_resync(state);
287                 return LISTENER_HAVE_DATA_WORKTREE;
288         }
289
290         /*
291          * On Windows, `info` contains an "array" of paths that are
292          * relative to the root of whichever directory handle received
293          * the event.
294          */
295         for (;;) {
296                 FILE_NOTIFY_INFORMATION *info = (void *)p;
297                 const char *slash;
298                 enum fsmonitor_path_type t;
299
300                 strbuf_reset(&path);
301                 if (normalize_path_in_utf8(info, &path) == -1)
302                         goto skip_this_path;
303
304                 t = fsmonitor_classify_path_workdir_relative(path.buf);
305
306                 switch (t) {
307                 case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
308                         /* special case cookie files within .git */
309
310                         /* Use just the filename of the cookie file. */
311                         slash = find_last_dir_sep(path.buf);
312                         string_list_append(&cookie_list,
313                                            slash ? slash + 1 : path.buf);
314                         break;
315
316                 case IS_INSIDE_DOT_GIT:
317                         /* ignore everything inside of "<worktree>/.git/" */
318                         break;
319
320                 case IS_DOT_GIT:
321                         /* "<worktree>/.git" was deleted (or renamed away) */
322                         if ((info->Action == FILE_ACTION_REMOVED) ||
323                             (info->Action == FILE_ACTION_RENAMED_OLD_NAME)) {
324                                 trace2_data_string("fsmonitor", NULL,
325                                                    "fsm-listen/dotgit",
326                                                    "removed");
327                                 goto force_shutdown;
328                         }
329                         break;
330
331                 case IS_WORKDIR_PATH:
332                         /* queue normal pathname */
333                         if (!batch)
334                                 batch = fsmonitor_batch__new();
335                         fsmonitor_batch__add_path(batch, path.buf);
336                         break;
337
338                 case IS_GITDIR:
339                 case IS_INSIDE_GITDIR:
340                 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
341                 default:
342                         BUG("unexpected path classification '%d' for '%s'",
343                             t, path.buf);
344                 }
345
346 skip_this_path:
347                 if (!info->NextEntryOffset)
348                         break;
349                 p += info->NextEntryOffset;
350         }
351
352         fsmonitor_publish(state, batch, &cookie_list);
353         batch = NULL;
354         string_list_clear(&cookie_list, 0);
355         strbuf_release(&path);
356         return LISTENER_HAVE_DATA_WORKTREE;
357
358 force_shutdown:
359         fsmonitor_batch__pop(batch);
360         string_list_clear(&cookie_list, 0);
361         strbuf_release(&path);
362         return LISTENER_SHUTDOWN;
363 }
364
365 /*
366  * Process filesystem events that happened anywhere (recursively) under the
367  * external <gitdir> (such as non-primary worktrees or submodules).
368  * We only care about cookie files that our client threads created here.
369  *
370  * Note that we DO NOT get filesystem events on the external <gitdir>
371  * itself (it is not inside something that we are watching).  In particular,
372  * we do not get an event if the external <gitdir> is deleted.
373  */
374 static int process_gitdir_events(struct fsmonitor_daemon_state *state)
375 {
376         struct fsmonitor_daemon_backend_data *data = state->backend_data;
377         struct one_watch *watch = data->watch_gitdir;
378         struct strbuf path = STRBUF_INIT;
379         struct string_list cookie_list = STRING_LIST_INIT_DUP;
380         const char *p = watch->buffer;
381
382         if (!watch->count) {
383                 trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel",
384                                    "overflow");
385                 fsmonitor_force_resync(state);
386                 return LISTENER_HAVE_DATA_GITDIR;
387         }
388
389         for (;;) {
390                 FILE_NOTIFY_INFORMATION *info = (void *)p;
391                 const char *slash;
392                 enum fsmonitor_path_type t;
393
394                 strbuf_reset(&path);
395                 if (normalize_path_in_utf8(info, &path) == -1)
396                         goto skip_this_path;
397
398                 t = fsmonitor_classify_path_gitdir_relative(path.buf);
399
400                 trace_printf_key(&trace_fsmonitor, "BBB: %s", path.buf);
401
402                 switch (t) {
403                 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
404                         /* special case cookie files within gitdir */
405
406                         /* Use just the filename of the cookie file. */
407                         slash = find_last_dir_sep(path.buf);
408                         string_list_append(&cookie_list,
409                                            slash ? slash + 1 : path.buf);
410                         break;
411
412                 case IS_INSIDE_GITDIR:
413                         goto skip_this_path;
414
415                 default:
416                         BUG("unexpected path classification '%d' for '%s'",
417                             t, path.buf);
418                 }
419
420 skip_this_path:
421                 if (!info->NextEntryOffset)
422                         break;
423                 p += info->NextEntryOffset;
424         }
425
426         fsmonitor_publish(state, NULL, &cookie_list);
427         string_list_clear(&cookie_list, 0);
428         strbuf_release(&path);
429         return LISTENER_HAVE_DATA_GITDIR;
430 }
431
432 void fsmonitor_fs_listen__loop(struct fsmonitor_daemon_state *state)
433 {
434         struct fsmonitor_daemon_backend_data *data = state->backend_data;
435         DWORD dwWait;
436
437         state->error_code = 0;
438
439         if (start_rdcw_watch(data, data->watch_worktree) == -1)
440                 goto force_error_stop;
441
442         if (data->watch_gitdir &&
443             start_rdcw_watch(data, data->watch_gitdir) == -1)
444                 goto force_error_stop;
445
446         for (;;) {
447                 dwWait = WaitForMultipleObjects(data->nr_listener_handles,
448                                                 data->hListener,
449                                                 FALSE, INFINITE);
450
451                 if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_WORKTREE) {
452                         if (recv_rdcw_watch(data->watch_worktree) == -1)
453                                 goto force_error_stop;
454                         if (process_worktree_events(state) == LISTENER_SHUTDOWN)
455                                 goto force_shutdown;
456                         if (start_rdcw_watch(data, data->watch_worktree) == -1)
457                                 goto force_error_stop;
458                         continue;
459                 }
460
461                 if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_GITDIR) {
462                         if (recv_rdcw_watch(data->watch_gitdir) == -1)
463                                 goto force_error_stop;
464                         if (process_gitdir_events(state) == LISTENER_SHUTDOWN)
465                                 goto force_shutdown;
466                         if (start_rdcw_watch(data, data->watch_gitdir) == -1)
467                                 goto force_error_stop;
468                         continue;
469                 }
470
471                 if (dwWait == WAIT_OBJECT_0 + LISTENER_SHUTDOWN)
472                         goto clean_shutdown;
473
474                 error(_("could not read directory changes [GLE %ld]"),
475                       GetLastError());
476                 goto force_error_stop;
477         }
478
479 force_error_stop:
480         state->error_code = -1;
481
482 force_shutdown:
483         /*
484          * Tell the IPC thead pool to stop (which completes the await
485          * in the main thread (which will also signal this thread (if
486          * we are still alive))).
487          */
488         ipc_server_stop_async(state->ipc_server_data);
489
490 clean_shutdown:
491         cancel_rdcw_watch(data->watch_worktree);
492         cancel_rdcw_watch(data->watch_gitdir);
493 }
494
495 int fsmonitor_fs_listen__ctor(struct fsmonitor_daemon_state *state)
496 {
497         struct fsmonitor_daemon_backend_data *data;
498
499         CALLOC_ARRAY(data, 1);
500
501         data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
502
503         data->watch_worktree = create_watch(state,
504                                             state->path_worktree_watch.buf);
505         if (!data->watch_worktree)
506                 goto failed;
507
508         if (state->nr_paths_watching > 1) {
509                 data->watch_gitdir = create_watch(state,
510                                                   state->path_gitdir_watch.buf);
511                 if (!data->watch_gitdir)
512                         goto failed;
513         }
514
515         data->hListener[LISTENER_SHUTDOWN] = data->hEventShutdown;
516         data->nr_listener_handles++;
517
518         data->hListener[LISTENER_HAVE_DATA_WORKTREE] =
519                 data->watch_worktree->hEvent;
520         data->nr_listener_handles++;
521
522         if (data->watch_gitdir) {
523                 data->hListener[LISTENER_HAVE_DATA_GITDIR] =
524                         data->watch_gitdir->hEvent;
525                 data->nr_listener_handles++;
526         }
527
528         state->backend_data = data;
529         return 0;
530
531 failed:
532         CloseHandle(data->hEventShutdown);
533         destroy_watch(data->watch_worktree);
534         destroy_watch(data->watch_gitdir);
535
536         return -1;
537 }
538
539 void fsmonitor_fs_listen__dtor(struct fsmonitor_daemon_state *state)
540 {
541         struct fsmonitor_daemon_backend_data *data;
542
543         if (!state || !state->backend_data)
544                 return;
545
546         data = state->backend_data;
547
548         CloseHandle(data->hEventShutdown);
549         destroy_watch(data->watch_worktree);
550         destroy_watch(data->watch_gitdir);
551
552         FREE_AND_NULL(state->backend_data);
553 }