1 #include "git-compat-util.h"
3 #include "fsmonitor-ll.h"
4 #include "fsm-listen.h"
5 #include "fsmonitor--daemon.h"
7 #include "simple-ipc.h"
11 * The documentation of ReadDirectoryChangesW() states that the maximum
12 * buffer size is 64K when the monitored directory is remote.
14 * Larger buffers may be used when the monitored directory is local and
15 * will help us receive events faster from the kernel and avoid dropped
18 * So we try to use a very large buffer and silently fallback to 64K if
21 #define MAX_RDCW_BUF_FALLBACK (65536)
22 #define MAX_RDCW_BUF (65536 * 8)
26 char buffer
[MAX_RDCW_BUF
];
31 wchar_t wpath_longname
[MAX_PATH
+ 1];
32 DWORD wpath_longname_len
;
36 OVERLAPPED overlapped
;
39 * Is there an active ReadDirectoryChangesW() call pending. If so, we
40 * need to later call GetOverlappedResult() and possibly CancelIoEx().
45 * Are shortnames enabled on the containing drive? This is
46 * always true for "C:/" drives and usually never true for
49 * We only set this for the worktree because we only need to
50 * convert shortname paths to longname paths for items we send
51 * to clients. (We don't care about shortname expansion for
52 * paths inside a GITDIR because we never send them to
57 wchar_t dotgit_shortname
[16]; /* for 8.3 name */
60 struct fsm_listen_data
62 struct one_watch
*watch_worktree
;
63 struct one_watch
*watch_gitdir
;
65 HANDLE hEventShutdown
;
67 HANDLE hListener
[3]; /* we don't own these handles */
68 #define LISTENER_SHUTDOWN 0
69 #define LISTENER_HAVE_DATA_WORKTREE 1
70 #define LISTENER_HAVE_DATA_GITDIR 2
71 int nr_listener_handles
;
75 * Convert the WCHAR path from the event into UTF8 and normalize it.
77 * `wpath_len` is in WCHARS not bytes.
79 static int normalize_path_in_utf8(wchar_t *wpath
, DWORD wpath_len
,
80 struct strbuf
*normalized_path
)
85 strbuf_reset(normalized_path
);
90 * Pre-reserve enough space in the UTF8 buffer for
91 * each Unicode WCHAR character to be mapped into a
92 * sequence of 2 UTF8 characters. That should let us
93 * avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time.
95 reserve
= 2 * wpath_len
+ 1;
96 strbuf_grow(normalized_path
, reserve
);
99 len
= WideCharToMultiByte(CP_UTF8
, 0,
101 normalized_path
->buf
,
102 strbuf_avail(normalized_path
) - 1,
106 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER
) {
107 error(_("[GLE %ld] could not convert path to UTF-8: '%.*ls'"),
108 GetLastError(), (int)wpath_len
, wpath
);
112 strbuf_grow(normalized_path
,
113 strbuf_avail(normalized_path
) + reserve
);
117 strbuf_setlen(normalized_path
, len
);
118 return strbuf_normalize_path(normalized_path
);
122 * See if the worktree root directory has shortnames enabled.
123 * This will help us decide if we need to do an expensive shortname
124 * to longname conversion on every notification event.
126 * We do not want to create a file to test this, so we assume that the
127 * root directory contains a ".git" file or directory. (Our caller
128 * only calls us for the worktree root, so this should be fine.)
130 * Remember the spelling of the shortname for ".git" if it exists.
132 static void check_for_shortnames(struct one_watch
*watch
)
134 wchar_t buf_in
[MAX_PATH
+ 1];
135 wchar_t buf_out
[MAX_PATH
+ 1];
139 /* build L"<wt-root-path>/.git" */
140 swprintf(buf_in
, ARRAY_SIZE(buf_in
) - 1, L
"%ls.git",
141 watch
->wpath_longname
);
143 if (!GetShortPathNameW(buf_in
, buf_out
, ARRAY_SIZE(buf_out
)))
147 * Get the final filename component of the shortpath.
148 * We know that the path does not have a final slash.
150 for (last
= p
= buf_out
; *p
; p
++)
151 if (*p
== L
'/' || *p
== '\\')
154 if (!wcscmp(last
, L
".git"))
157 watch
->has_shortnames
= 1;
158 wcsncpy(watch
->dotgit_shortname
, last
,
159 ARRAY_SIZE(watch
->dotgit_shortname
));
162 * The shortname for ".git" is usually of the form "GIT~1", so
163 * we should be able to avoid shortname to longname mapping on
164 * every notification event if the source string does not
167 * However, the documentation for GetLongPathNameW() says
168 * that there are filesystems that don't follow that pattern
169 * and warns against this optimization.
173 if (wcschr(watch
->dotgit_shortname
, L
'~'))
174 watch
->has_tilde
= 1;
177 enum get_relative_result
{
178 GRR_NO_CONVERSION_NEEDED
,
184 * Info notification paths are relative to the root of the watch.
185 * If our CWD is still at the root, then we can use relative paths
186 * to convert from shortnames to longnames. If our process has a
187 * different CWD, then we need to construct an absolute path, do
188 * the conversion, and then return the root-relative portion.
190 * We use the longname form of the root as our basis and assume that
191 * it already has a trailing slash.
193 * `wpath_len` is in WCHARS not bytes.
195 static enum get_relative_result
get_relative_longname(
196 struct one_watch
*watch
,
197 const wchar_t *wpath
, DWORD wpath_len
,
198 wchar_t *wpath_longname
, size_t bufsize_wpath_longname
)
200 wchar_t buf_in
[2 * MAX_PATH
+ 1];
201 wchar_t buf_out
[MAX_PATH
+ 1];
206 * Build L"<wt-root-path>/<event-rel-path>"
207 * Note that the <event-rel-path> might not be null terminated
208 * so we avoid swprintf() constructions.
210 root_len
= watch
->wpath_longname_len
;
211 if (root_len
+ wpath_len
>= ARRAY_SIZE(buf_in
)) {
213 * This should not happen. We cannot append the observed
214 * relative path onto the end of the worktree root path
215 * without overflowing the buffer. Just give up.
219 wcsncpy(buf_in
, watch
->wpath_longname
, root_len
);
220 wcsncpy(buf_in
+ root_len
, wpath
, wpath_len
);
221 buf_in
[root_len
+ wpath_len
] = 0;
224 * We don't actually know if the source pathname is a
225 * shortname or a longname. This Windows routine allows
226 * either to be given as input.
228 out_len
= GetLongPathNameW(buf_in
, buf_out
, ARRAY_SIZE(buf_out
));
231 * The shortname to longname conversion can fail for
232 * various reasons, for example if the file has been
233 * deleted. (That is, if we just received a
234 * delete-file notification event and the file is
235 * already gone, we can't ask the file system to
236 * lookup the longname for it. Likewise, for moves
237 * and renames where we are given the old name.)
239 * Since deleting or moving a file or directory by its
240 * shortname is rather obscure, I'm going ignore the
241 * failure and ask the caller to report the original
242 * relative path. This seems kinder than failing here
243 * and forcing a resync. Besides, forcing a resync on
244 * every file/directory delete would effectively
245 * cripple monitoring.
247 * We might revisit this in the future.
249 return GRR_NO_CONVERSION_NEEDED
;
252 if (!wcscmp(buf_in
, buf_out
)) {
254 * The path does not have a shortname alias.
256 return GRR_NO_CONVERSION_NEEDED
;
259 if (wcsncmp(buf_in
, buf_out
, root_len
)) {
261 * The spelling of the root directory portion of the computed
262 * longname has changed. This should not happen. Basically,
263 * it means that we don't know where (without recomputing the
264 * longname of just the root directory) to split out the
265 * relative path. Since this should not happen, I'm just
266 * going to let this fail and force a shutdown (because all
267 * subsequent events are probably going to see the same
273 if (out_len
- root_len
>= bufsize_wpath_longname
) {
275 * This should not happen. We cannot copy the root-relative
276 * portion of the path into the provided buffer without an
277 * overrun. Just give up.
282 /* Return the worktree root-relative portion of the longname. */
284 wcscpy(wpath_longname
, buf_out
+ root_len
);
285 return GRR_HAVE_CONVERSION
;
288 void fsm_listen__stop_async(struct fsmonitor_daemon_state
*state
)
290 SetEvent(state
->listen_data
->hListener
[LISTENER_SHUTDOWN
]);
293 static struct one_watch
*create_watch(const char *path
)
295 struct one_watch
*watch
= NULL
;
296 DWORD desired_access
= FILE_LIST_DIRECTORY
;
298 FILE_SHARE_WRITE
| FILE_SHARE_READ
| FILE_SHARE_DELETE
;
301 wchar_t wpath
[MAX_PATH
+ 1];
302 wchar_t wpath_longname
[MAX_PATH
+ 1];
304 if (xutftowcs_path(wpath
, path
) < 0) {
305 error(_("could not convert to wide characters: '%s'"), path
);
309 hDir
= CreateFileW(wpath
,
310 desired_access
, share_mode
, NULL
, OPEN_EXISTING
,
311 FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OVERLAPPED
,
313 if (hDir
== INVALID_HANDLE_VALUE
) {
314 error(_("[GLE %ld] could not watch '%s'"),
315 GetLastError(), path
);
319 len_longname
= GetLongPathNameW(wpath
, wpath_longname
,
320 ARRAY_SIZE(wpath_longname
));
322 error(_("[GLE %ld] could not get longname of '%s'"),
323 GetLastError(), path
);
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;
334 CALLOC_ARRAY(watch
, 1);
336 watch
->buf_len
= sizeof(watch
->buffer
); /* assume full MAX_RDCW_BUF */
338 strbuf_init(&watch
->path
, 0);
339 strbuf_addstr(&watch
->path
, path
);
341 wcscpy(watch
->wpath_longname
, wpath_longname
);
342 watch
->wpath_longname_len
= len_longname
;
345 watch
->hEvent
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
350 static void destroy_watch(struct one_watch
*watch
)
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
);
364 static int start_rdcw_watch(struct one_watch
*watch
)
366 DWORD dwNotifyFilter
=
367 FILE_NOTIFY_CHANGE_FILE_NAME
|
368 FILE_NOTIFY_CHANGE_DIR_NAME
|
369 FILE_NOTIFY_CHANGE_ATTRIBUTES
|
370 FILE_NOTIFY_CHANGE_SIZE
|
371 FILE_NOTIFY_CHANGE_LAST_WRITE
|
372 FILE_NOTIFY_CHANGE_CREATION
;
374 ResetEvent(watch
->hEvent
);
376 memset(&watch
->overlapped
, 0, sizeof(watch
->overlapped
));
377 watch
->overlapped
.hEvent
= watch
->hEvent
;
380 * Queue an async call using Overlapped IO. This returns immediately.
381 * Our event handle will be signalled when the real result is available.
383 * The return value here just means that we successfully queued it.
384 * We won't know if the Read...() actually produces data until later.
386 watch
->is_active
= ReadDirectoryChangesW(
387 watch
->hDir
, watch
->buffer
, watch
->buf_len
, TRUE
,
388 dwNotifyFilter
, &watch
->count
, &watch
->overlapped
, NULL
);
390 if (watch
->is_active
)
393 error(_("ReadDirectoryChangedW failed on '%s' [GLE %ld]"),
394 watch
->path
.buf
, GetLastError());
398 static int recv_rdcw_watch(struct one_watch
*watch
)
402 watch
->is_active
= FALSE
;
405 * The overlapped result is ready. If the Read...() was successful
406 * we finally receive the actual result into our buffer.
408 if (GetOverlappedResult(watch
->hDir
, &watch
->overlapped
, &watch
->count
,
412 gle
= GetLastError();
413 if (gle
== ERROR_INVALID_PARAMETER
&&
415 * The kernel throws an invalid parameter error when our
416 * buffer is too big and we are pointed at a remote
417 * directory (and possibly for other reasons). Quietly
418 * set it down and try again.
420 * See note about MAX_RDCW_BUF at the top.
422 watch
->buf_len
> MAX_RDCW_BUF_FALLBACK
) {
423 watch
->buf_len
= MAX_RDCW_BUF_FALLBACK
;
428 * GetOverlappedResult() fails if the watched directory is
429 * deleted while we were waiting for an overlapped IO to
430 * complete. The documentation did not list specific errors,
431 * but I observed ERROR_ACCESS_DENIED (0x05) errors during
434 * Note that we only get notificaiton events for events
435 * *within* the directory, not *on* the directory itself.
436 * (These might be properies of the parent directory, for
439 * NEEDSWORK: We might try to check for the deleted directory
440 * case and return a better error message, but I'm not sure it
443 * Shutdown if we get any error.
446 error(_("GetOverlappedResult failed on '%s' [GLE %ld]"),
447 watch
->path
.buf
, gle
);
451 static void cancel_rdcw_watch(struct one_watch
*watch
)
455 if (!watch
|| !watch
->is_active
)
459 * The calls to ReadDirectoryChangesW() and GetOverlappedResult()
460 * form a "pair" (my term) where we queue an IO and promise to
461 * hang around and wait for the kernel to give us the result.
463 * If for some reason after we queue the IO, we have to quit
464 * or otherwise not stick around for the second half, we must
465 * tell the kernel to abort the IO. This prevents the kernel
466 * from writing to our buffer and/or signalling our event
467 * after we free them.
469 * (Ask me how much fun it was to track that one down).
471 CancelIoEx(watch
->hDir
, &watch
->overlapped
);
472 GetOverlappedResult(watch
->hDir
, &watch
->overlapped
, &count
, TRUE
);
473 watch
->is_active
= FALSE
;
477 * Process a single relative pathname event.
478 * Return 1 if we should shutdown.
480 static int process_1_worktree_event(
481 struct string_list
*cookie_list
,
482 struct fsmonitor_batch
**batch
,
483 const struct strbuf
*path
,
484 enum fsmonitor_path_type t
,
490 case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX
:
491 /* special case cookie files within .git */
493 /* Use just the filename of the cookie file. */
494 slash
= find_last_dir_sep(path
->buf
);
495 string_list_append(cookie_list
,
496 slash
? slash
+ 1 : path
->buf
);
499 case IS_INSIDE_DOT_GIT
:
500 /* ignore everything inside of "<worktree>/.git/" */
504 /* "<worktree>/.git" was deleted (or renamed away) */
505 if ((info_action
== FILE_ACTION_REMOVED
) ||
506 (info_action
== FILE_ACTION_RENAMED_OLD_NAME
)) {
507 trace2_data_string("fsmonitor", NULL
,
514 case IS_WORKDIR_PATH
:
515 /* queue normal pathname */
517 *batch
= fsmonitor_batch__new();
518 fsmonitor_batch__add_path(*batch
, path
->buf
);
522 case IS_INSIDE_GITDIR
:
523 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX
:
525 BUG("unexpected path classification '%d' for '%s'",
533 * Process filesystem events that happen anywhere (recursively) under the
534 * <worktree> root directory. For a normal working directory, this includes
535 * both version controlled files and the contents of the .git/ directory.
537 * If <worktree>/.git is a file, then we only see events for the file
540 static int process_worktree_events(struct fsmonitor_daemon_state
*state
)
542 struct fsm_listen_data
*data
= state
->listen_data
;
543 struct one_watch
*watch
= data
->watch_worktree
;
544 struct strbuf path
= STRBUF_INIT
;
545 struct string_list cookie_list
= STRING_LIST_INIT_DUP
;
546 struct fsmonitor_batch
*batch
= NULL
;
547 const char *p
= watch
->buffer
;
548 wchar_t wpath_longname
[MAX_PATH
+ 1];
551 * If the kernel gets more events than will fit in the kernel
552 * buffer associated with our RDCW handle, it drops them and
553 * returns a count of zero.
555 * Yes, the call returns WITHOUT error and with length zero.
556 * This is the documented behavior. (My testing has confirmed
557 * that it also sets the last error to ERROR_NOTIFY_ENUM_DIR,
558 * but we do not rely on that since the function did not
559 * return an error and it is not documented.)
561 * (The "overflow" case is not ambiguous with the "no data" case
562 * because we did an INFINITE wait.)
564 * This means we have a gap in coverage. Tell the daemon layer
568 trace2_data_string("fsmonitor", NULL
, "fsm-listen/kernel",
570 fsmonitor_force_resync(state
);
571 return LISTENER_HAVE_DATA_WORKTREE
;
575 * On Windows, `info` contains an "array" of paths that are
576 * relative to the root of whichever directory handle received
580 FILE_NOTIFY_INFORMATION
*info
= (void *)p
;
581 wchar_t *wpath
= info
->FileName
;
582 DWORD wpath_len
= info
->FileNameLength
/ sizeof(WCHAR
);
583 enum fsmonitor_path_type t
;
584 enum get_relative_result grr
;
586 if (watch
->has_shortnames
) {
587 if (!wcscmp(wpath
, watch
->dotgit_shortname
)) {
589 * This event exactly matches the
590 * spelling of the shortname of
591 * ".git", so we can skip some steps.
593 * (This case is odd because the user
594 * can "rm -rf GIT~1" and we cannot
595 * use the filesystem to map it back
599 strbuf_addstr(&path
, ".git");
604 if (watch
->has_tilde
&& !wcschr(wpath
, L
'~')) {
606 * Shortnames on this filesystem have tildes
607 * and the notification path does not have
608 * one, so we assume that it is a longname.
613 grr
= get_relative_longname(watch
, wpath
, wpath_len
,
615 ARRAY_SIZE(wpath_longname
));
617 case GRR_NO_CONVERSION_NEEDED
: /* use info buffer as is */
619 case GRR_HAVE_CONVERSION
:
620 wpath
= wpath_longname
;
621 wpath_len
= wcslen(wpath
);
630 if (normalize_path_in_utf8(wpath
, wpath_len
, &path
) == -1)
633 t
= fsmonitor_classify_path_workdir_relative(path
.buf
);
636 if (process_1_worktree_event(&cookie_list
, &batch
, &path
, t
,
641 if (!info
->NextEntryOffset
)
643 p
+= info
->NextEntryOffset
;
646 fsmonitor_publish(state
, batch
, &cookie_list
);
648 string_list_clear(&cookie_list
, 0);
649 strbuf_release(&path
);
650 return LISTENER_HAVE_DATA_WORKTREE
;
653 fsmonitor_batch__free_list(batch
);
654 string_list_clear(&cookie_list
, 0);
655 strbuf_release(&path
);
656 return LISTENER_SHUTDOWN
;
660 * Process filesystem events that happened anywhere (recursively) under the
661 * external <gitdir> (such as non-primary worktrees or submodules).
662 * We only care about cookie files that our client threads created here.
664 * Note that we DO NOT get filesystem events on the external <gitdir>
665 * itself (it is not inside something that we are watching). In particular,
666 * we do not get an event if the external <gitdir> is deleted.
668 * Also, we do not care about shortnames within the external <gitdir>, since
669 * we never send these paths to clients.
671 static int process_gitdir_events(struct fsmonitor_daemon_state
*state
)
673 struct fsm_listen_data
*data
= state
->listen_data
;
674 struct one_watch
*watch
= data
->watch_gitdir
;
675 struct strbuf path
= STRBUF_INIT
;
676 struct string_list cookie_list
= STRING_LIST_INIT_DUP
;
677 const char *p
= watch
->buffer
;
680 trace2_data_string("fsmonitor", NULL
, "fsm-listen/kernel",
682 fsmonitor_force_resync(state
);
683 return LISTENER_HAVE_DATA_GITDIR
;
687 FILE_NOTIFY_INFORMATION
*info
= (void *)p
;
689 enum fsmonitor_path_type t
;
691 if (normalize_path_in_utf8(
693 info
->FileNameLength
/ sizeof(WCHAR
),
697 t
= fsmonitor_classify_path_gitdir_relative(path
.buf
);
700 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX
:
701 /* special case cookie files within gitdir */
703 /* Use just the filename of the cookie file. */
704 slash
= find_last_dir_sep(path
.buf
);
705 string_list_append(&cookie_list
,
706 slash
? slash
+ 1 : path
.buf
);
709 case IS_INSIDE_GITDIR
:
713 BUG("unexpected path classification '%d' for '%s'",
718 if (!info
->NextEntryOffset
)
720 p
+= info
->NextEntryOffset
;
723 fsmonitor_publish(state
, NULL
, &cookie_list
);
724 string_list_clear(&cookie_list
, 0);
725 strbuf_release(&path
);
726 return LISTENER_HAVE_DATA_GITDIR
;
729 void fsm_listen__loop(struct fsmonitor_daemon_state
*state
)
731 struct fsm_listen_data
*data
= state
->listen_data
;
735 state
->listen_error_code
= 0;
737 if (start_rdcw_watch(data
->watch_worktree
) == -1)
738 goto force_error_stop
;
740 if (data
->watch_gitdir
&&
741 start_rdcw_watch(data
->watch_gitdir
) == -1)
742 goto force_error_stop
;
745 * Now that we've established the rdcw watches, we can start
748 ipc_server_start_async(state
->ipc_server_data
);
751 dwWait
= WaitForMultipleObjects(data
->nr_listener_handles
,
755 if (dwWait
== WAIT_OBJECT_0
+ LISTENER_HAVE_DATA_WORKTREE
) {
756 result
= recv_rdcw_watch(data
->watch_worktree
);
759 goto force_error_stop
;
762 /* retryable error */
763 if (start_rdcw_watch(data
->watch_worktree
) == -1)
764 goto force_error_stop
;
769 if (process_worktree_events(state
) == LISTENER_SHUTDOWN
)
771 if (start_rdcw_watch(data
->watch_worktree
) == -1)
772 goto force_error_stop
;
776 if (dwWait
== WAIT_OBJECT_0
+ LISTENER_HAVE_DATA_GITDIR
) {
777 result
= recv_rdcw_watch(data
->watch_gitdir
);
780 goto force_error_stop
;
783 /* retryable error */
784 if (start_rdcw_watch(data
->watch_gitdir
) == -1)
785 goto force_error_stop
;
790 if (process_gitdir_events(state
) == LISTENER_SHUTDOWN
)
792 if (start_rdcw_watch(data
->watch_gitdir
) == -1)
793 goto force_error_stop
;
797 if (dwWait
== WAIT_OBJECT_0
+ LISTENER_SHUTDOWN
)
800 error(_("could not read directory changes [GLE %ld]"),
802 goto force_error_stop
;
806 state
->listen_error_code
= -1;
810 * Tell the IPC thead pool to stop (which completes the await
811 * in the main thread (which will also signal this thread (if
812 * we are still alive))).
814 ipc_server_stop_async(state
->ipc_server_data
);
817 cancel_rdcw_watch(data
->watch_worktree
);
818 cancel_rdcw_watch(data
->watch_gitdir
);
821 int fsm_listen__ctor(struct fsmonitor_daemon_state
*state
)
823 struct fsm_listen_data
*data
;
825 CALLOC_ARRAY(data
, 1);
827 data
->hEventShutdown
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
829 data
->watch_worktree
= create_watch(state
->path_worktree_watch
.buf
);
830 if (!data
->watch_worktree
)
833 check_for_shortnames(data
->watch_worktree
);
835 if (state
->nr_paths_watching
> 1) {
836 data
->watch_gitdir
= create_watch(state
->path_gitdir_watch
.buf
);
837 if (!data
->watch_gitdir
)
841 data
->hListener
[LISTENER_SHUTDOWN
] = data
->hEventShutdown
;
842 data
->nr_listener_handles
++;
844 data
->hListener
[LISTENER_HAVE_DATA_WORKTREE
] =
845 data
->watch_worktree
->hEvent
;
846 data
->nr_listener_handles
++;
848 if (data
->watch_gitdir
) {
849 data
->hListener
[LISTENER_HAVE_DATA_GITDIR
] =
850 data
->watch_gitdir
->hEvent
;
851 data
->nr_listener_handles
++;
854 state
->listen_data
= data
;
858 CloseHandle(data
->hEventShutdown
);
859 destroy_watch(data
->watch_worktree
);
860 destroy_watch(data
->watch_gitdir
);
865 void fsm_listen__dtor(struct fsmonitor_daemon_state
*state
)
867 struct fsm_listen_data
*data
;
869 if (!state
|| !state
->listen_data
)
872 data
= state
->listen_data
;
874 CloseHandle(data
->hEventShutdown
);
875 destroy_watch(data
->watch_worktree
);
876 destroy_watch(data
->watch_gitdir
);
878 FREE_AND_NULL(state
->listen_data
);