4 #include "fsmonitor-fs-listen.h"
5 #include "fsmonitor--daemon.h"
8 * The documentation of ReadDirectoryChangesW() states that the maximum
9 * buffer size is 64K when the monitored directory is remote.
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
15 * So we try to use a very large buffer and silently fallback to 64K if
18 #define MAX_RDCW_BUF_FALLBACK (65536)
19 #define MAX_RDCW_BUF (65536 * 8)
23 char buffer[MAX_RDCW_BUF];
30 OVERLAPPED overlapped;
33 * Is there an active ReadDirectoryChangesW() call pending. If so, we
34 * need to later call GetOverlappedResult() and possibly CancelIoEx().
39 struct fsmonitor_daemon_backend_data
41 struct one_watch *watch_worktree;
42 struct one_watch *watch_gitdir;
44 HANDLE hEventShutdown;
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;
54 * Convert the WCHAR path from the notification into UTF8 and
57 static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info,
58 struct strbuf *normalized_path)
63 strbuf_reset(normalized_path);
64 if (!info->FileNameLength)
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.
73 reserve = info->FileNameLength + 1;
74 strbuf_grow(normalized_path, reserve);
77 len = WideCharToMultiByte(CP_UTF8, 0, info->FileName,
78 info->FileNameLength / sizeof(WCHAR),
80 strbuf_avail(normalized_path) - 1,
84 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
85 error("[GLE %ld] could not convert path to UTF-8: '%.*ls'",
87 (int)(info->FileNameLength / sizeof(WCHAR)),
92 strbuf_grow(normalized_path,
93 strbuf_avail(normalized_path) + reserve);
97 strbuf_setlen(normalized_path, len);
98 return strbuf_normalize_path(normalized_path);
101 void fsmonitor_fs_listen__stop_async(struct fsmonitor_daemon_state *state)
103 SetEvent(state->backend_data->hListener[LISTENER_SHUTDOWN]);
106 static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
109 struct one_watch *watch = NULL;
110 DWORD desired_access = FILE_LIST_DIRECTORY;
112 FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
115 hDir = CreateFileA(path,
116 desired_access, share_mode, NULL, OPEN_EXISTING,
117 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
119 if (hDir == INVALID_HANDLE_VALUE) {
120 error(_("[GLE %ld] could not watch '%s'"),
121 GetLastError(), path);
125 CALLOC_ARRAY(watch, 1);
127 watch->buf_len = sizeof(watch->buffer); /* assume full MAX_RDCW_BUF */
129 strbuf_init(&watch->path, 0);
130 strbuf_addstr(&watch->path, path);
133 watch->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
138 static void destroy_watch(struct one_watch *watch)
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);
152 static int start_rdcw_watch(struct fsmonitor_daemon_backend_data *data,
153 struct one_watch *watch)
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;
163 ResetEvent(watch->hEvent);
165 memset(&watch->overlapped, 0, sizeof(watch->overlapped));
166 watch->overlapped.hEvent = watch->hEvent;
170 * Queue an async call using Overlapped IO. This returns immediately.
171 * Our event handle will be signalled when the real result is available.
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.
176 watch->is_active = ReadDirectoryChangesW(
177 watch->hDir, watch->buffer, watch->buf_len, TRUE,
178 dwNotifyFilter, &watch->count, &watch->overlapped, NULL);
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.
185 * See note about MAX_RDCW_BUF at the top.
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;
194 if (watch->is_active)
197 error("ReadDirectoryChangedW failed on '%s' [GLE %ld]",
198 watch->path.buf, GetLastError());
202 static int recv_rdcw_watch(struct one_watch *watch)
204 watch->is_active = FALSE;
207 * The overlapped result is ready. If the Read...() was successful
208 * we finally receive the actual result into our buffer.
210 if (GetOverlappedResult(watch->hDir, &watch->overlapped, &watch->count,
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.
223 error("GetOverlappedResult failed on '%s' [GLE %ld]",
224 watch->path.buf, GetLastError());
228 static void cancel_rdcw_watch(struct one_watch *watch)
232 if (!watch || !watch->is_active)
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.
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.
246 * (Ask me how much fun it was to track that one down).
248 CancelIoEx(watch->hDir, &watch->overlapped);
249 GetOverlappedResult(watch->hDir, &watch->overlapped, &count, TRUE);
250 watch->is_active = FALSE;
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.
258 * If <worktree>/.git is a file, then we only see events for the file
261 static int process_worktree_events(struct fsmonitor_daemon_state *state)
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;
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.
275 * Yes, the call returns WITHOUT error and with length zero.
277 * (The "overflow" case is not ambiguous with the "no data" case
278 * because we did an INFINITE wait.)
280 * This means we have a gap in coverage. Tell the daemon layer
284 trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel",
286 fsmonitor_force_resync(state);
287 return LISTENER_HAVE_DATA_WORKTREE;
291 * On Windows, `info` contains an "array" of paths that are
292 * relative to the root of whichever directory handle received
296 FILE_NOTIFY_INFORMATION *info = (void *)p;
298 enum fsmonitor_path_type t;
301 if (normalize_path_in_utf8(info, &path) == -1)
304 t = fsmonitor_classify_path_workdir_relative(path.buf);
307 case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
308 /* special case cookie files within .git */
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);
316 case IS_INSIDE_DOT_GIT:
317 /* ignore everything inside of "<worktree>/.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,
331 case IS_WORKDIR_PATH:
332 /* queue normal pathname */
334 batch = fsmonitor_batch__new();
335 fsmonitor_batch__add_path(batch, path.buf);
339 case IS_INSIDE_GITDIR:
340 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
342 BUG("unexpected path classification '%d' for '%s'",
347 if (!info->NextEntryOffset)
349 p += info->NextEntryOffset;
352 fsmonitor_publish(state, batch, &cookie_list);
354 string_list_clear(&cookie_list, 0);
355 strbuf_release(&path);
356 return LISTENER_HAVE_DATA_WORKTREE;
359 fsmonitor_batch__pop(batch);
360 string_list_clear(&cookie_list, 0);
361 strbuf_release(&path);
362 return LISTENER_SHUTDOWN;
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.
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.
374 static int process_gitdir_events(struct fsmonitor_daemon_state *state)
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;
383 trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel",
385 fsmonitor_force_resync(state);
386 return LISTENER_HAVE_DATA_GITDIR;
390 FILE_NOTIFY_INFORMATION *info = (void *)p;
392 enum fsmonitor_path_type t;
395 if (normalize_path_in_utf8(info, &path) == -1)
398 t = fsmonitor_classify_path_gitdir_relative(path.buf);
400 trace_printf_key(&trace_fsmonitor, "BBB: %s", path.buf);
403 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
404 /* special case cookie files within gitdir */
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);
412 case IS_INSIDE_GITDIR:
416 BUG("unexpected path classification '%d' for '%s'",
421 if (!info->NextEntryOffset)
423 p += info->NextEntryOffset;
426 fsmonitor_publish(state, NULL, &cookie_list);
427 string_list_clear(&cookie_list, 0);
428 strbuf_release(&path);
429 return LISTENER_HAVE_DATA_GITDIR;
432 void fsmonitor_fs_listen__loop(struct fsmonitor_daemon_state *state)
434 struct fsmonitor_daemon_backend_data *data = state->backend_data;
437 state->error_code = 0;
439 if (start_rdcw_watch(data, data->watch_worktree) == -1)
440 goto force_error_stop;
442 if (data->watch_gitdir &&
443 start_rdcw_watch(data, data->watch_gitdir) == -1)
444 goto force_error_stop;
447 dwWait = WaitForMultipleObjects(data->nr_listener_handles,
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)
456 if (start_rdcw_watch(data, data->watch_worktree) == -1)
457 goto force_error_stop;
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)
466 if (start_rdcw_watch(data, data->watch_gitdir) == -1)
467 goto force_error_stop;
471 if (dwWait == WAIT_OBJECT_0 + LISTENER_SHUTDOWN)
474 error(_("could not read directory changes [GLE %ld]"),
476 goto force_error_stop;
480 state->error_code = -1;
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))).
488 ipc_server_stop_async(state->ipc_server_data);
491 cancel_rdcw_watch(data->watch_worktree);
492 cancel_rdcw_watch(data->watch_gitdir);
495 int fsmonitor_fs_listen__ctor(struct fsmonitor_daemon_state *state)
497 struct fsmonitor_daemon_backend_data *data;
499 CALLOC_ARRAY(data, 1);
501 data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
503 data->watch_worktree = create_watch(state,
504 state->path_worktree_watch.buf);
505 if (!data->watch_worktree)
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)
515 data->hListener[LISTENER_SHUTDOWN] = data->hEventShutdown;
516 data->nr_listener_handles++;
518 data->hListener[LISTENER_HAVE_DATA_WORKTREE] =
519 data->watch_worktree->hEvent;
520 data->nr_listener_handles++;
522 if (data->watch_gitdir) {
523 data->hListener[LISTENER_HAVE_DATA_GITDIR] =
524 data->watch_gitdir->hEvent;
525 data->nr_listener_handles++;
528 state->backend_data = data;
532 CloseHandle(data->hEventShutdown);
533 destroy_watch(data->watch_worktree);
534 destroy_watch(data->watch_gitdir);
539 void fsmonitor_fs_listen__dtor(struct fsmonitor_daemon_state *state)
541 struct fsmonitor_daemon_backend_data *data;
543 if (!state || !state->backend_data)
546 data = state->backend_data;
548 CloseHandle(data->hEventShutdown);
549 destroy_watch(data->watch_worktree);
550 destroy_watch(data->watch_gitdir);
552 FREE_AND_NULL(state->backend_data);