1 #include "git-compat-util.h"
3 #include "fsmonitor-ll.h"
4 #include "fsm-listen.h"
5 #include "fsmonitor--daemon.h"
10 * The documentation of ReadDirectoryChangesW() states that the maximum
11 * buffer size is 64K when the monitored directory is remote.
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
17 * So we try to use a very large buffer and silently fallback to 64K if
20 #define MAX_RDCW_BUF_FALLBACK (65536)
21 #define MAX_RDCW_BUF (65536 * 8)
25 char buffer
[MAX_RDCW_BUF
];
30 wchar_t wpath_longname
[MAX_PATH
+ 1];
31 DWORD wpath_longname_len
;
35 OVERLAPPED overlapped
;
38 * Is there an active ReadDirectoryChangesW() call pending. If so, we
39 * need to later call GetOverlappedResult() and possibly CancelIoEx().
44 * Are shortnames enabled on the containing drive? This is
45 * always true for "C:/" drives and usually never true for
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
56 wchar_t dotgit_shortname
[16]; /* for 8.3 name */
59 struct fsm_listen_data
61 struct one_watch
*watch_worktree
;
62 struct one_watch
*watch_gitdir
;
64 HANDLE hEventShutdown
;
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
;
74 * Convert the WCHAR path from the event into UTF8 and normalize it.
76 * `wpath_len` is in WCHARS not bytes.
78 static int normalize_path_in_utf8(wchar_t *wpath
, DWORD wpath_len
,
79 struct strbuf
*normalized_path
)
84 strbuf_reset(normalized_path
);
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.
94 reserve
= 2 * wpath_len
+ 1;
95 strbuf_grow(normalized_path
, reserve
);
98 len
= WideCharToMultiByte(CP_UTF8
, 0,
100 normalized_path
->buf
,
101 strbuf_avail(normalized_path
) - 1,
105 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER
) {
106 error(_("[GLE %ld] could not convert path to UTF-8: '%.*ls'"),
107 GetLastError(), (int)wpath_len
, wpath
);
111 strbuf_grow(normalized_path
,
112 strbuf_avail(normalized_path
) + reserve
);
116 strbuf_setlen(normalized_path
, len
);
117 return strbuf_normalize_path(normalized_path
);
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.
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.)
129 * Remember the spelling of the shortname for ".git" if it exists.
131 static void check_for_shortnames(struct one_watch
*watch
)
133 wchar_t buf_in
[MAX_PATH
+ 1];
134 wchar_t buf_out
[MAX_PATH
+ 1];
138 /* build L"<wt-root-path>/.git" */
139 swprintf(buf_in
, ARRAY_SIZE(buf_in
) - 1, L
"%ls.git",
140 watch
->wpath_longname
);
142 if (!GetShortPathNameW(buf_in
, buf_out
, ARRAY_SIZE(buf_out
)))
146 * Get the final filename component of the shortpath.
147 * We know that the path does not have a final slash.
149 for (last
= p
= buf_out
; *p
; p
++)
150 if (*p
== L
'/' || *p
== '\\')
153 if (!wcscmp(last
, L
".git"))
156 watch
->has_shortnames
= 1;
157 wcsncpy(watch
->dotgit_shortname
, last
,
158 ARRAY_SIZE(watch
->dotgit_shortname
));
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
166 * However, the documentation for GetLongPathNameW() says
167 * that there are filesystems that don't follow that pattern
168 * and warns against this optimization.
172 if (wcschr(watch
->dotgit_shortname
, L
'~'))
173 watch
->has_tilde
= 1;
176 enum get_relative_result
{
177 GRR_NO_CONVERSION_NEEDED
,
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.
189 * We use the longname form of the root as our basis and assume that
190 * it already has a trailing slash.
192 * `wpath_len` is in WCHARS not bytes.
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
)
199 wchar_t buf_in
[2 * MAX_PATH
+ 1];
200 wchar_t buf_out
[MAX_PATH
+ 1];
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.
209 root_len
= watch
->wpath_longname_len
;
210 if (root_len
+ wpath_len
>= ARRAY_SIZE(buf_in
)) {
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.
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;
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.
227 out_len
= GetLongPathNameW(buf_in
, buf_out
, ARRAY_SIZE(buf_out
));
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.)
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.
246 * We might revisit this in the future.
248 return GRR_NO_CONVERSION_NEEDED
;
251 if (!wcscmp(buf_in
, buf_out
)) {
253 * The path does not have a shortname alias.
255 return GRR_NO_CONVERSION_NEEDED
;
258 if (wcsncmp(buf_in
, buf_out
, root_len
)) {
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
272 if (out_len
- root_len
>= bufsize_wpath_longname
) {
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.
281 /* Return the worktree root-relative portion of the longname. */
283 wcscpy(wpath_longname
, buf_out
+ root_len
);
284 return GRR_HAVE_CONVERSION
;
287 void fsm_listen__stop_async(struct fsmonitor_daemon_state
*state
)
289 SetEvent(state
->listen_data
->hListener
[LISTENER_SHUTDOWN
]);
292 static struct one_watch
*create_watch(struct fsmonitor_daemon_state
*state
,
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 fsm_listen_data
*data
,
365 struct one_watch
*watch
)
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
;
375 ResetEvent(watch
->hEvent
);
377 memset(&watch
->overlapped
, 0, sizeof(watch
->overlapped
));
378 watch
->overlapped
.hEvent
= watch
->hEvent
;
381 * Queue an async call using Overlapped IO. This returns immediately.
382 * Our event handle will be signalled when the real result is available.
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.
387 watch
->is_active
= ReadDirectoryChangesW(
388 watch
->hDir
, watch
->buffer
, watch
->buf_len
, TRUE
,
389 dwNotifyFilter
, &watch
->count
, &watch
->overlapped
, NULL
);
391 if (watch
->is_active
)
394 error(_("ReadDirectoryChangedW failed on '%s' [GLE %ld]"),
395 watch
->path
.buf
, GetLastError());
399 static int recv_rdcw_watch(struct one_watch
*watch
)
403 watch
->is_active
= FALSE
;
406 * The overlapped result is ready. If the Read...() was successful
407 * we finally receive the actual result into our buffer.
409 if (GetOverlappedResult(watch
->hDir
, &watch
->overlapped
, &watch
->count
,
413 gle
= GetLastError();
414 if (gle
== ERROR_INVALID_PARAMETER
&&
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.
421 * See note about MAX_RDCW_BUF at the top.
423 watch
->buf_len
> MAX_RDCW_BUF_FALLBACK
) {
424 watch
->buf_len
= MAX_RDCW_BUF_FALLBACK
;
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
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
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
444 * Shutdown if we get any error.
447 error(_("GetOverlappedResult failed on '%s' [GLE %ld]"),
448 watch
->path
.buf
, gle
);
452 static void cancel_rdcw_watch(struct one_watch
*watch
)
456 if (!watch
|| !watch
->is_active
)
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.
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.
470 * (Ask me how much fun it was to track that one down).
472 CancelIoEx(watch
->hDir
, &watch
->overlapped
);
473 GetOverlappedResult(watch
->hDir
, &watch
->overlapped
, &count
, TRUE
);
474 watch
->is_active
= FALSE
;
478 * Process a single relative pathname event.
479 * Return 1 if we should shutdown.
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
,
491 case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX
:
492 /* special case cookie files within .git */
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
);
500 case IS_INSIDE_DOT_GIT
:
501 /* ignore everything inside of "<worktree>/.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
,
515 case IS_WORKDIR_PATH
:
516 /* queue normal pathname */
518 *batch
= fsmonitor_batch__new();
519 fsmonitor_batch__add_path(*batch
, path
->buf
);
523 case IS_INSIDE_GITDIR
:
524 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX
:
526 BUG("unexpected path classification '%d' for '%s'",
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.
538 * If <worktree>/.git is a file, then we only see events for the file
541 static int process_worktree_events(struct fsmonitor_daemon_state
*state
)
543 struct fsm_listen_data
*data
= state
->listen_data
;
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
;
549 wchar_t wpath_longname
[MAX_PATH
+ 1];
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.
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.)
562 * (The "overflow" case is not ambiguous with the "no data" case
563 * because we did an INFINITE wait.)
565 * This means we have a gap in coverage. Tell the daemon layer
569 trace2_data_string("fsmonitor", NULL
, "fsm-listen/kernel",
571 fsmonitor_force_resync(state
);
572 return LISTENER_HAVE_DATA_WORKTREE
;
576 * On Windows, `info` contains an "array" of paths that are
577 * relative to the root of whichever directory handle received
581 FILE_NOTIFY_INFORMATION
*info
= (void *)p
;
582 wchar_t *wpath
= info
->FileName
;
583 DWORD wpath_len
= info
->FileNameLength
/ sizeof(WCHAR
);
584 enum fsmonitor_path_type t
;
585 enum get_relative_result grr
;
587 if (watch
->has_shortnames
) {
588 if (!wcscmp(wpath
, watch
->dotgit_shortname
)) {
590 * This event exactly matches the
591 * spelling of the shortname of
592 * ".git", so we can skip some steps.
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
600 strbuf_addstr(&path
, ".git");
605 if (watch
->has_tilde
&& !wcschr(wpath
, L
'~')) {
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.
614 grr
= get_relative_longname(watch
, wpath
, wpath_len
,
616 ARRAY_SIZE(wpath_longname
));
618 case GRR_NO_CONVERSION_NEEDED
: /* use info buffer as is */
620 case GRR_HAVE_CONVERSION
:
621 wpath
= wpath_longname
;
622 wpath_len
= wcslen(wpath
);
631 if (normalize_path_in_utf8(wpath
, wpath_len
, &path
) == -1)
634 t
= fsmonitor_classify_path_workdir_relative(path
.buf
);
637 if (process_1_worktree_event(&cookie_list
, &batch
, &path
, t
,
642 if (!info
->NextEntryOffset
)
644 p
+= info
->NextEntryOffset
;
647 fsmonitor_publish(state
, batch
, &cookie_list
);
649 string_list_clear(&cookie_list
, 0);
650 strbuf_release(&path
);
651 return LISTENER_HAVE_DATA_WORKTREE
;
654 fsmonitor_batch__free_list(batch
);
655 string_list_clear(&cookie_list
, 0);
656 strbuf_release(&path
);
657 return LISTENER_SHUTDOWN
;
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.
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.
669 * Also, we do not care about shortnames within the external <gitdir>, since
670 * we never send these paths to clients.
672 static int process_gitdir_events(struct fsmonitor_daemon_state
*state
)
674 struct fsm_listen_data
*data
= state
->listen_data
;
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
;
681 trace2_data_string("fsmonitor", NULL
, "fsm-listen/kernel",
683 fsmonitor_force_resync(state
);
684 return LISTENER_HAVE_DATA_GITDIR
;
688 FILE_NOTIFY_INFORMATION
*info
= (void *)p
;
690 enum fsmonitor_path_type t
;
692 if (normalize_path_in_utf8(
694 info
->FileNameLength
/ sizeof(WCHAR
),
698 t
= fsmonitor_classify_path_gitdir_relative(path
.buf
);
701 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX
:
702 /* special case cookie files within gitdir */
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
);
710 case IS_INSIDE_GITDIR
:
714 BUG("unexpected path classification '%d' for '%s'",
719 if (!info
->NextEntryOffset
)
721 p
+= info
->NextEntryOffset
;
724 fsmonitor_publish(state
, NULL
, &cookie_list
);
725 string_list_clear(&cookie_list
, 0);
726 strbuf_release(&path
);
727 return LISTENER_HAVE_DATA_GITDIR
;
730 void fsm_listen__loop(struct fsmonitor_daemon_state
*state
)
732 struct fsm_listen_data
*data
= state
->listen_data
;
736 state
->listen_error_code
= 0;
738 if (start_rdcw_watch(data
, data
->watch_worktree
) == -1)
739 goto force_error_stop
;
741 if (data
->watch_gitdir
&&
742 start_rdcw_watch(data
, data
->watch_gitdir
) == -1)
743 goto force_error_stop
;
746 dwWait
= WaitForMultipleObjects(data
->nr_listener_handles
,
750 if (dwWait
== WAIT_OBJECT_0
+ LISTENER_HAVE_DATA_WORKTREE
) {
751 result
= recv_rdcw_watch(data
->watch_worktree
);
754 goto force_error_stop
;
757 /* retryable error */
758 if (start_rdcw_watch(data
, data
->watch_worktree
) == -1)
759 goto force_error_stop
;
764 if (process_worktree_events(state
) == LISTENER_SHUTDOWN
)
766 if (start_rdcw_watch(data
, data
->watch_worktree
) == -1)
767 goto force_error_stop
;
771 if (dwWait
== WAIT_OBJECT_0
+ LISTENER_HAVE_DATA_GITDIR
) {
772 result
= recv_rdcw_watch(data
->watch_gitdir
);
775 goto force_error_stop
;
778 /* retryable error */
779 if (start_rdcw_watch(data
, data
->watch_gitdir
) == -1)
780 goto force_error_stop
;
785 if (process_gitdir_events(state
) == LISTENER_SHUTDOWN
)
787 if (start_rdcw_watch(data
, data
->watch_gitdir
) == -1)
788 goto force_error_stop
;
792 if (dwWait
== WAIT_OBJECT_0
+ LISTENER_SHUTDOWN
)
795 error(_("could not read directory changes [GLE %ld]"),
797 goto force_error_stop
;
801 state
->listen_error_code
= -1;
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))).
809 ipc_server_stop_async(state
->ipc_server_data
);
812 cancel_rdcw_watch(data
->watch_worktree
);
813 cancel_rdcw_watch(data
->watch_gitdir
);
816 int fsm_listen__ctor(struct fsmonitor_daemon_state
*state
)
818 struct fsm_listen_data
*data
;
820 CALLOC_ARRAY(data
, 1);
822 data
->hEventShutdown
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
824 data
->watch_worktree
= create_watch(state
,
825 state
->path_worktree_watch
.buf
);
826 if (!data
->watch_worktree
)
829 check_for_shortnames(data
->watch_worktree
);
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
)
838 data
->hListener
[LISTENER_SHUTDOWN
] = data
->hEventShutdown
;
839 data
->nr_listener_handles
++;
841 data
->hListener
[LISTENER_HAVE_DATA_WORKTREE
] =
842 data
->watch_worktree
->hEvent
;
843 data
->nr_listener_handles
++;
845 if (data
->watch_gitdir
) {
846 data
->hListener
[LISTENER_HAVE_DATA_GITDIR
] =
847 data
->watch_gitdir
->hEvent
;
848 data
->nr_listener_handles
++;
851 state
->listen_data
= data
;
855 CloseHandle(data
->hEventShutdown
);
856 destroy_watch(data
->watch_worktree
);
857 destroy_watch(data
->watch_gitdir
);
862 void fsm_listen__dtor(struct fsmonitor_daemon_state
*state
)
864 struct fsm_listen_data
*data
;
866 if (!state
|| !state
->listen_data
)
869 data
= state
->listen_data
;
871 CloseHandle(data
->hEventShutdown
);
872 destroy_watch(data
->watch_worktree
);
873 destroy_watch(data
->watch_gitdir
);
875 FREE_AND_NULL(state
->listen_data
);