| #include "cache.h" |
| #include "config.h" |
| #include "fsmonitor.h" |
| #include "fsm-listen.h" |
| #include "fsmonitor--daemon.h" |
| |
| /* |
| * The documentation of ReadDirectoryChangesW() states that the maximum |
| * buffer size is 64K when the monitored directory is remote. |
| * |
| * Larger buffers may be used when the monitored directory is local and |
| * will help us receive events faster from the kernel and avoid dropped |
| * events. |
| * |
| * So we try to use a very large buffer and silently fallback to 64K if |
| * we get an error. |
| */ |
| #define MAX_RDCW_BUF_FALLBACK (65536) |
| #define MAX_RDCW_BUF (65536 * 8) |
| |
| struct one_watch |
| { |
| char buffer[MAX_RDCW_BUF]; |
| DWORD buf_len; |
| DWORD count; |
| |
| struct strbuf path; |
| wchar_t wpath_longname[MAX_PATH + 1]; |
| DWORD wpath_longname_len; |
| |
| HANDLE hDir; |
| HANDLE hEvent; |
| OVERLAPPED overlapped; |
| |
| /* |
| * Is there an active ReadDirectoryChangesW() call pending. If so, we |
| * need to later call GetOverlappedResult() and possibly CancelIoEx(). |
| */ |
| BOOL is_active; |
| |
| /* |
| * Are shortnames enabled on the containing drive? This is |
| * always true for "C:/" drives and usually never true for |
| * other drives. |
| * |
| * We only set this for the worktree because we only need to |
| * convert shortname paths to longname paths for items we send |
| * to clients. (We don't care about shortname expansion for |
| * paths inside a GITDIR because we never send them to |
| * clients.) |
| */ |
| BOOL has_shortnames; |
| BOOL has_tilde; |
| wchar_t dotgit_shortname[16]; /* for 8.3 name */ |
| }; |
| |
| struct fsm_listen_data |
| { |
| struct one_watch *watch_worktree; |
| struct one_watch *watch_gitdir; |
| |
| HANDLE hEventShutdown; |
| |
| HANDLE hListener[3]; /* we don't own these handles */ |
| #define LISTENER_SHUTDOWN 0 |
| #define LISTENER_HAVE_DATA_WORKTREE 1 |
| #define LISTENER_HAVE_DATA_GITDIR 2 |
| int nr_listener_handles; |
| }; |
| |
| /* |
| * Convert the WCHAR path from the event into UTF8 and normalize it. |
| * |
| * `wpath_len` is in WCHARS not bytes. |
| */ |
| static int normalize_path_in_utf8(wchar_t *wpath, DWORD wpath_len, |
| struct strbuf *normalized_path) |
| { |
| int reserve; |
| int len = 0; |
| |
| strbuf_reset(normalized_path); |
| if (!wpath_len) |
| goto normalize; |
| |
| /* |
| * Pre-reserve enough space in the UTF8 buffer for |
| * each Unicode WCHAR character to be mapped into a |
| * sequence of 2 UTF8 characters. That should let us |
| * avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time. |
| */ |
| reserve = 2 * wpath_len + 1; |
| strbuf_grow(normalized_path, reserve); |
| |
| for (;;) { |
| len = WideCharToMultiByte(CP_UTF8, 0, |
| wpath, wpath_len, |
| normalized_path->buf, |
| strbuf_avail(normalized_path) - 1, |
| NULL, NULL); |
| if (len > 0) |
| goto normalize; |
| if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { |
| error(_("[GLE %ld] could not convert path to UTF-8: '%.*ls'"), |
| GetLastError(), (int)wpath_len, wpath); |
| return -1; |
| } |
| |
| strbuf_grow(normalized_path, |
| strbuf_avail(normalized_path) + reserve); |
| } |
| |
| normalize: |
| strbuf_setlen(normalized_path, len); |
| return strbuf_normalize_path(normalized_path); |
| } |
| |
| /* |
| * See if the worktree root directory has shortnames enabled. |
| * This will help us decide if we need to do an expensive shortname |
| * to longname conversion on every notification event. |
| * |
| * We do not want to create a file to test this, so we assume that the |
| * root directory contains a ".git" file or directory. (Our caller |
| * only calls us for the worktree root, so this should be fine.) |
| * |
| * Remember the spelling of the shortname for ".git" if it exists. |
| */ |
| static void check_for_shortnames(struct one_watch *watch) |
| { |
| wchar_t buf_in[MAX_PATH + 1]; |
| wchar_t buf_out[MAX_PATH + 1]; |
| wchar_t *last; |
| wchar_t *p; |
| |
| /* build L"<wt-root-path>/.git" */ |
| swprintf(buf_in, ARRAY_SIZE(buf_in) - 1, L"%ls.git", |
| watch->wpath_longname); |
| |
| if (!GetShortPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out))) |
| return; |
| |
| /* |
| * Get the final filename component of the shortpath. |
| * We know that the path does not have a final slash. |
| */ |
| for (last = p = buf_out; *p; p++) |
| if (*p == L'/' || *p == '\\') |
| last = p + 1; |
| |
| if (!wcscmp(last, L".git")) |
| return; |
| |
| watch->has_shortnames = 1; |
| wcsncpy(watch->dotgit_shortname, last, |
| ARRAY_SIZE(watch->dotgit_shortname)); |
| |
| /* |
| * The shortname for ".git" is usually of the form "GIT~1", so |
| * we should be able to avoid shortname to longname mapping on |
| * every notification event if the source string does not |
| * contain a "~". |
| * |
| * However, the documentation for GetLongPathNameW() says |
| * that there are filesystems that don't follow that pattern |
| * and warns against this optimization. |
| * |
| * Lets test this. |
| */ |
| if (wcschr(watch->dotgit_shortname, L'~')) |
| watch->has_tilde = 1; |
| } |
| |
| enum get_relative_result { |
| GRR_NO_CONVERSION_NEEDED, |
| GRR_HAVE_CONVERSION, |
| GRR_SHUTDOWN, |
| }; |
| |
| /* |
| * Info notification paths are relative to the root of the watch. |
| * If our CWD is still at the root, then we can use relative paths |
| * to convert from shortnames to longnames. If our process has a |
| * different CWD, then we need to construct an absolute path, do |
| * the conversion, and then return the root-relative portion. |
| * |
| * We use the longname form of the root as our basis and assume that |
| * it already has a trailing slash. |
| * |
| * `wpath_len` is in WCHARS not bytes. |
| */ |
| static enum get_relative_result get_relative_longname( |
| struct one_watch *watch, |
| const wchar_t *wpath, DWORD wpath_len, |
| wchar_t *wpath_longname, size_t bufsize_wpath_longname) |
| { |
| wchar_t buf_in[2 * MAX_PATH + 1]; |
| wchar_t buf_out[MAX_PATH + 1]; |
| DWORD root_len; |
| DWORD out_len; |
| |
| /* |
| * Build L"<wt-root-path>/<event-rel-path>" |
| * Note that the <event-rel-path> might not be null terminated |
| * so we avoid swprintf() constructions. |
| */ |
| root_len = watch->wpath_longname_len; |
| if (root_len + wpath_len >= ARRAY_SIZE(buf_in)) { |
| /* |
| * This should not happen. We cannot append the observed |
| * relative path onto the end of the worktree root path |
| * without overflowing the buffer. Just give up. |
| */ |
| return GRR_SHUTDOWN; |
| } |
| wcsncpy(buf_in, watch->wpath_longname, root_len); |
| wcsncpy(buf_in + root_len, wpath, wpath_len); |
| buf_in[root_len + wpath_len] = 0; |
| |
| /* |
| * We don't actually know if the source pathname is a |
| * shortname or a longname. This Windows routine allows |
| * either to be given as input. |
| */ |
| out_len = GetLongPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out)); |
| if (!out_len) { |
| /* |
| * The shortname to longname conversion can fail for |
| * various reasons, for example if the file has been |
| * deleted. (That is, if we just received a |
| * delete-file notification event and the file is |
| * already gone, we can't ask the file system to |
| * lookup the longname for it. Likewise, for moves |
| * and renames where we are given the old name.) |
| * |
| * Since deleting or moving a file or directory by its |
| * shortname is rather obscure, I'm going ignore the |
| * failure and ask the caller to report the original |
| * relative path. This seems kinder than failing here |
| * and forcing a resync. Besides, forcing a resync on |
| * every file/directory delete would effectively |
| * cripple monitoring. |
| * |
| * We might revisit this in the future. |
| */ |
| return GRR_NO_CONVERSION_NEEDED; |
| } |
| |
| if (!wcscmp(buf_in, buf_out)) { |
| /* |
| * The path does not have a shortname alias. |
| */ |
| return GRR_NO_CONVERSION_NEEDED; |
| } |
| |
| if (wcsncmp(buf_in, buf_out, root_len)) { |
| /* |
| * The spelling of the root directory portion of the computed |
| * longname has changed. This should not happen. Basically, |
| * it means that we don't know where (without recomputing the |
| * longname of just the root directory) to split out the |
| * relative path. Since this should not happen, I'm just |
| * going to let this fail and force a shutdown (because all |
| * subsequent events are probably going to see the same |
| * mismatch). |
| */ |
| return GRR_SHUTDOWN; |
| } |
| |
| if (out_len - root_len >= bufsize_wpath_longname) { |
| /* |
| * This should not happen. We cannot copy the root-relative |
| * portion of the path into the provided buffer without an |
| * overrun. Just give up. |
| */ |
| return GRR_SHUTDOWN; |
| } |
| |
| /* Return the worktree root-relative portion of the longname. */ |
| |
| wcscpy(wpath_longname, buf_out + root_len); |
| return GRR_HAVE_CONVERSION; |
| } |
| |
| void fsm_listen__stop_async(struct fsmonitor_daemon_state *state) |
| { |
| SetEvent(state->listen_data->hListener[LISTENER_SHUTDOWN]); |
| } |
| |
| static struct one_watch *create_watch(struct fsmonitor_daemon_state *state, |
| const char *path) |
| { |
| struct one_watch *watch = NULL; |
| DWORD desired_access = FILE_LIST_DIRECTORY; |
| DWORD share_mode = |
| FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; |
| HANDLE hDir; |
| DWORD len_longname; |
| wchar_t wpath[MAX_PATH + 1]; |
| wchar_t wpath_longname[MAX_PATH + 1]; |
| |
| if (xutftowcs_path(wpath, path) < 0) { |
| error(_("could not convert to wide characters: '%s'"), path); |
| return NULL; |
| } |
| |
| hDir = CreateFileW(wpath, |
| desired_access, share_mode, NULL, OPEN_EXISTING, |
| FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, |
| NULL); |
| if (hDir == INVALID_HANDLE_VALUE) { |
| error(_("[GLE %ld] could not watch '%s'"), |
| GetLastError(), path); |
| return NULL; |
| } |
| |
| len_longname = GetLongPathNameW(wpath, wpath_longname, |
| ARRAY_SIZE(wpath_longname)); |
| if (!len_longname) { |
| error(_("[GLE %ld] could not get longname of '%s'"), |
| GetLastError(), path); |
| CloseHandle(hDir); |
| return NULL; |
| } |
| |
| if (wpath_longname[len_longname - 1] != L'/' && |
| wpath_longname[len_longname - 1] != L'\\') { |
| wpath_longname[len_longname++] = L'/'; |
| wpath_longname[len_longname] = 0; |
| } |
| |
| CALLOC_ARRAY(watch, 1); |
| |
| watch->buf_len = sizeof(watch->buffer); /* assume full MAX_RDCW_BUF */ |
| |
| strbuf_init(&watch->path, 0); |
| strbuf_addstr(&watch->path, path); |
| |
| wcscpy(watch->wpath_longname, wpath_longname); |
| watch->wpath_longname_len = len_longname; |
| |
| watch->hDir = hDir; |
| watch->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
| |
| return watch; |
| } |
| |
| static void destroy_watch(struct one_watch *watch) |
| { |
| if (!watch) |
| return; |
| |
| strbuf_release(&watch->path); |
| if (watch->hDir != INVALID_HANDLE_VALUE) |
| CloseHandle(watch->hDir); |
| if (watch->hEvent != INVALID_HANDLE_VALUE) |
| CloseHandle(watch->hEvent); |
| |
| free(watch); |
| } |
| |
| static int start_rdcw_watch(struct fsm_listen_data *data, |
| struct one_watch *watch) |
| { |
| DWORD dwNotifyFilter = |
| FILE_NOTIFY_CHANGE_FILE_NAME | |
| FILE_NOTIFY_CHANGE_DIR_NAME | |
| FILE_NOTIFY_CHANGE_ATTRIBUTES | |
| FILE_NOTIFY_CHANGE_SIZE | |
| FILE_NOTIFY_CHANGE_LAST_WRITE | |
| FILE_NOTIFY_CHANGE_CREATION; |
| |
| ResetEvent(watch->hEvent); |
| |
| memset(&watch->overlapped, 0, sizeof(watch->overlapped)); |
| watch->overlapped.hEvent = watch->hEvent; |
| |
| /* |
| * Queue an async call using Overlapped IO. This returns immediately. |
| * Our event handle will be signalled when the real result is available. |
| * |
| * The return value here just means that we successfully queued it. |
| * We won't know if the Read...() actually produces data until later. |
| */ |
| watch->is_active = ReadDirectoryChangesW( |
| watch->hDir, watch->buffer, watch->buf_len, TRUE, |
| dwNotifyFilter, &watch->count, &watch->overlapped, NULL); |
| |
| if (watch->is_active) |
| return 0; |
| |
| error(_("ReadDirectoryChangedW failed on '%s' [GLE %ld]"), |
| watch->path.buf, GetLastError()); |
| return -1; |
| } |
| |
| static int recv_rdcw_watch(struct one_watch *watch) |
| { |
| DWORD gle; |
| |
| watch->is_active = FALSE; |
| |
| /* |
| * The overlapped result is ready. If the Read...() was successful |
| * we finally receive the actual result into our buffer. |
| */ |
| if (GetOverlappedResult(watch->hDir, &watch->overlapped, &watch->count, |
| TRUE)) |
| return 0; |
| |
| gle = GetLastError(); |
| if (gle == ERROR_INVALID_PARAMETER && |
| /* |
| * The kernel throws an invalid parameter error when our |
| * buffer is too big and we are pointed at a remote |
| * directory (and possibly for other reasons). Quietly |
| * set it down and try again. |
| * |
| * See note about MAX_RDCW_BUF at the top. |
| */ |
| watch->buf_len > MAX_RDCW_BUF_FALLBACK) { |
| watch->buf_len = MAX_RDCW_BUF_FALLBACK; |
| return -2; |
| } |
| |
| /* |
| * GetOverlappedResult() fails if the watched directory is |
| * deleted while we were waiting for an overlapped IO to |
| * complete. The documentation did not list specific errors, |
| * but I observed ERROR_ACCESS_DENIED (0x05) errors during |
| * testing. |
| * |
| * Note that we only get notificaiton events for events |
| * *within* the directory, not *on* the directory itself. |
| * (These might be properies of the parent directory, for |
| * example). |
| * |
| * NEEDSWORK: We might try to check for the deleted directory |
| * case and return a better error message, but I'm not sure it |
| * is worth it. |
| * |
| * Shutdown if we get any error. |
| */ |
| |
| error(_("GetOverlappedResult failed on '%s' [GLE %ld]"), |
| watch->path.buf, gle); |
| return -1; |
| } |
| |
| static void cancel_rdcw_watch(struct one_watch *watch) |
| { |
| DWORD count; |
| |
| if (!watch || !watch->is_active) |
| return; |
| |
| /* |
| * The calls to ReadDirectoryChangesW() and GetOverlappedResult() |
| * form a "pair" (my term) where we queue an IO and promise to |
| * hang around and wait for the kernel to give us the result. |
| * |
| * If for some reason after we queue the IO, we have to quit |
| * or otherwise not stick around for the second half, we must |
| * tell the kernel to abort the IO. This prevents the kernel |
| * from writing to our buffer and/or signalling our event |
| * after we free them. |
| * |
| * (Ask me how much fun it was to track that one down). |
| */ |
| CancelIoEx(watch->hDir, &watch->overlapped); |
| GetOverlappedResult(watch->hDir, &watch->overlapped, &count, TRUE); |
| watch->is_active = FALSE; |
| } |
| |
| /* |
| * Process a single relative pathname event. |
| * Return 1 if we should shutdown. |
| */ |
| static int process_1_worktree_event( |
| struct string_list *cookie_list, |
| struct fsmonitor_batch **batch, |
| const struct strbuf *path, |
| enum fsmonitor_path_type t, |
| DWORD info_action) |
| { |
| const char *slash; |
| |
| switch (t) { |
| case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX: |
| /* special case cookie files within .git */ |
| |
| /* Use just the filename of the cookie file. */ |
| slash = find_last_dir_sep(path->buf); |
| string_list_append(cookie_list, |
| slash ? slash + 1 : path->buf); |
| break; |
| |
| case IS_INSIDE_DOT_GIT: |
| /* ignore everything inside of "<worktree>/.git/" */ |
| break; |
| |
| case IS_DOT_GIT: |
| /* "<worktree>/.git" was deleted (or renamed away) */ |
| if ((info_action == FILE_ACTION_REMOVED) || |
| (info_action == FILE_ACTION_RENAMED_OLD_NAME)) { |
| trace2_data_string("fsmonitor", NULL, |
| "fsm-listen/dotgit", |
| "removed"); |
| return 1; |
| } |
| break; |
| |
| case IS_WORKDIR_PATH: |
| /* queue normal pathname */ |
| if (!*batch) |
| *batch = fsmonitor_batch__new(); |
| fsmonitor_batch__add_path(*batch, path->buf); |
| break; |
| |
| case IS_GITDIR: |
| case IS_INSIDE_GITDIR: |
| case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX: |
| default: |
| BUG("unexpected path classification '%d' for '%s'", |
| t, path->buf); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Process filesystem events that happen anywhere (recursively) under the |
| * <worktree> root directory. For a normal working directory, this includes |
| * both version controlled files and the contents of the .git/ directory. |
| * |
| * If <worktree>/.git is a file, then we only see events for the file |
| * itself. |
| */ |
| static int process_worktree_events(struct fsmonitor_daemon_state *state) |
| { |
| struct fsm_listen_data *data = state->listen_data; |
| struct one_watch *watch = data->watch_worktree; |
| struct strbuf path = STRBUF_INIT; |
| struct string_list cookie_list = STRING_LIST_INIT_DUP; |
| struct fsmonitor_batch *batch = NULL; |
| const char *p = watch->buffer; |
| wchar_t wpath_longname[MAX_PATH + 1]; |
| |
| /* |
| * If the kernel gets more events than will fit in the kernel |
| * buffer associated with our RDCW handle, it drops them and |
| * returns a count of zero. |
| * |
| * Yes, the call returns WITHOUT error and with length zero. |
| * This is the documented behavior. (My testing has confirmed |
| * that it also sets the last error to ERROR_NOTIFY_ENUM_DIR, |
| * but we do not rely on that since the function did not |
| * return an error and it is not documented.) |
| * |
| * (The "overflow" case is not ambiguous with the "no data" case |
| * because we did an INFINITE wait.) |
| * |
| * This means we have a gap in coverage. Tell the daemon layer |
| * to resync. |
| */ |
| if (!watch->count) { |
| trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel", |
| "overflow"); |
| fsmonitor_force_resync(state); |
| return LISTENER_HAVE_DATA_WORKTREE; |
| } |
| |
| /* |
| * On Windows, `info` contains an "array" of paths that are |
| * relative to the root of whichever directory handle received |
| * the event. |
| */ |
| for (;;) { |
| FILE_NOTIFY_INFORMATION *info = (void *)p; |
| wchar_t *wpath = info->FileName; |
| DWORD wpath_len = info->FileNameLength / sizeof(WCHAR); |
| enum fsmonitor_path_type t; |
| enum get_relative_result grr; |
| |
| if (watch->has_shortnames) { |
| if (!wcscmp(wpath, watch->dotgit_shortname)) { |
| /* |
| * This event exactly matches the |
| * spelling of the shortname of |
| * ".git", so we can skip some steps. |
| * |
| * (This case is odd because the user |
| * can "rm -rf GIT~1" and we cannot |
| * use the filesystem to map it back |
| * to ".git".) |
| */ |
| strbuf_reset(&path); |
| strbuf_addstr(&path, ".git"); |
| t = IS_DOT_GIT; |
| goto process_it; |
| } |
| |
| if (watch->has_tilde && !wcschr(wpath, L'~')) { |
| /* |
| * Shortnames on this filesystem have tildes |
| * and the notification path does not have |
| * one, so we assume that it is a longname. |
| */ |
| goto normalize_it; |
| } |
| |
| grr = get_relative_longname(watch, wpath, wpath_len, |
| wpath_longname, |
| ARRAY_SIZE(wpath_longname)); |
| switch (grr) { |
| case GRR_NO_CONVERSION_NEEDED: /* use info buffer as is */ |
| break; |
| case GRR_HAVE_CONVERSION: |
| wpath = wpath_longname; |
| wpath_len = wcslen(wpath); |
| break; |
| default: |
| case GRR_SHUTDOWN: |
| goto force_shutdown; |
| } |
| } |
| |
| normalize_it: |
| if (normalize_path_in_utf8(wpath, wpath_len, &path) == -1) |
| goto skip_this_path; |
| |
| t = fsmonitor_classify_path_workdir_relative(path.buf); |
| |
| process_it: |
| if (process_1_worktree_event(&cookie_list, &batch, &path, t, |
| info->Action)) |
| goto force_shutdown; |
| |
| skip_this_path: |
| if (!info->NextEntryOffset) |
| break; |
| p += info->NextEntryOffset; |
| } |
| |
| fsmonitor_publish(state, batch, &cookie_list); |
| batch = NULL; |
| string_list_clear(&cookie_list, 0); |
| strbuf_release(&path); |
| return LISTENER_HAVE_DATA_WORKTREE; |
| |
| force_shutdown: |
| fsmonitor_batch__free_list(batch); |
| string_list_clear(&cookie_list, 0); |
| strbuf_release(&path); |
| return LISTENER_SHUTDOWN; |
| } |
| |
| /* |
| * Process filesystem events that happened anywhere (recursively) under the |
| * external <gitdir> (such as non-primary worktrees or submodules). |
| * We only care about cookie files that our client threads created here. |
| * |
| * Note that we DO NOT get filesystem events on the external <gitdir> |
| * itself (it is not inside something that we are watching). In particular, |
| * we do not get an event if the external <gitdir> is deleted. |
| * |
| * Also, we do not care about shortnames within the external <gitdir>, since |
| * we never send these paths to clients. |
| */ |
| static int process_gitdir_events(struct fsmonitor_daemon_state *state) |
| { |
| struct fsm_listen_data *data = state->listen_data; |
| struct one_watch *watch = data->watch_gitdir; |
| struct strbuf path = STRBUF_INIT; |
| struct string_list cookie_list = STRING_LIST_INIT_DUP; |
| const char *p = watch->buffer; |
| |
| if (!watch->count) { |
| trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel", |
| "overflow"); |
| fsmonitor_force_resync(state); |
| return LISTENER_HAVE_DATA_GITDIR; |
| } |
| |
| for (;;) { |
| FILE_NOTIFY_INFORMATION *info = (void *)p; |
| const char *slash; |
| enum fsmonitor_path_type t; |
| |
| if (normalize_path_in_utf8( |
| info->FileName, |
| info->FileNameLength / sizeof(WCHAR), |
| &path) == -1) |
| goto skip_this_path; |
| |
| t = fsmonitor_classify_path_gitdir_relative(path.buf); |
| |
| switch (t) { |
| case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX: |
| /* special case cookie files within gitdir */ |
| |
| /* Use just the filename of the cookie file. */ |
| slash = find_last_dir_sep(path.buf); |
| string_list_append(&cookie_list, |
| slash ? slash + 1 : path.buf); |
| break; |
| |
| case IS_INSIDE_GITDIR: |
| goto skip_this_path; |
| |
| default: |
| BUG("unexpected path classification '%d' for '%s'", |
| t, path.buf); |
| } |
| |
| skip_this_path: |
| if (!info->NextEntryOffset) |
| break; |
| p += info->NextEntryOffset; |
| } |
| |
| fsmonitor_publish(state, NULL, &cookie_list); |
| string_list_clear(&cookie_list, 0); |
| strbuf_release(&path); |
| return LISTENER_HAVE_DATA_GITDIR; |
| } |
| |
| void fsm_listen__loop(struct fsmonitor_daemon_state *state) |
| { |
| struct fsm_listen_data *data = state->listen_data; |
| DWORD dwWait; |
| int result; |
| |
| state->listen_error_code = 0; |
| |
| if (start_rdcw_watch(data, data->watch_worktree) == -1) |
| goto force_error_stop; |
| |
| if (data->watch_gitdir && |
| start_rdcw_watch(data, data->watch_gitdir) == -1) |
| goto force_error_stop; |
| |
| for (;;) { |
| dwWait = WaitForMultipleObjects(data->nr_listener_handles, |
| data->hListener, |
| FALSE, INFINITE); |
| |
| if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_WORKTREE) { |
| result = recv_rdcw_watch(data->watch_worktree); |
| if (result == -1) { |
| /* hard error */ |
| goto force_error_stop; |
| } |
| if (result == -2) { |
| /* retryable error */ |
| if (start_rdcw_watch(data, data->watch_worktree) == -1) |
| goto force_error_stop; |
| continue; |
| } |
| |
| /* have data */ |
| if (process_worktree_events(state) == LISTENER_SHUTDOWN) |
| goto force_shutdown; |
| if (start_rdcw_watch(data, data->watch_worktree) == -1) |
| goto force_error_stop; |
| continue; |
| } |
| |
| if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_GITDIR) { |
| result = recv_rdcw_watch(data->watch_gitdir); |
| if (result == -1) { |
| /* hard error */ |
| goto force_error_stop; |
| } |
| if (result == -2) { |
| /* retryable error */ |
| if (start_rdcw_watch(data, data->watch_gitdir) == -1) |
| goto force_error_stop; |
| continue; |
| } |
| |
| /* have data */ |
| if (process_gitdir_events(state) == LISTENER_SHUTDOWN) |
| goto force_shutdown; |
| if (start_rdcw_watch(data, data->watch_gitdir) == -1) |
| goto force_error_stop; |
| continue; |
| } |
| |
| if (dwWait == WAIT_OBJECT_0 + LISTENER_SHUTDOWN) |
| goto clean_shutdown; |
| |
| error(_("could not read directory changes [GLE %ld]"), |
| GetLastError()); |
| goto force_error_stop; |
| } |
| |
| force_error_stop: |
| state->listen_error_code = -1; |
| |
| force_shutdown: |
| /* |
| * Tell the IPC thead pool to stop (which completes the await |
| * in the main thread (which will also signal this thread (if |
| * we are still alive))). |
| */ |
| ipc_server_stop_async(state->ipc_server_data); |
| |
| clean_shutdown: |
| cancel_rdcw_watch(data->watch_worktree); |
| cancel_rdcw_watch(data->watch_gitdir); |
| } |
| |
| int fsm_listen__ctor(struct fsmonitor_daemon_state *state) |
| { |
| struct fsm_listen_data *data; |
| |
| CALLOC_ARRAY(data, 1); |
| |
| data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL); |
| |
| data->watch_worktree = create_watch(state, |
| state->path_worktree_watch.buf); |
| if (!data->watch_worktree) |
| goto failed; |
| |
| check_for_shortnames(data->watch_worktree); |
| |
| if (state->nr_paths_watching > 1) { |
| data->watch_gitdir = create_watch(state, |
| state->path_gitdir_watch.buf); |
| if (!data->watch_gitdir) |
| goto failed; |
| } |
| |
| data->hListener[LISTENER_SHUTDOWN] = data->hEventShutdown; |
| data->nr_listener_handles++; |
| |
| data->hListener[LISTENER_HAVE_DATA_WORKTREE] = |
| data->watch_worktree->hEvent; |
| data->nr_listener_handles++; |
| |
| if (data->watch_gitdir) { |
| data->hListener[LISTENER_HAVE_DATA_GITDIR] = |
| data->watch_gitdir->hEvent; |
| data->nr_listener_handles++; |
| } |
| |
| state->listen_data = data; |
| return 0; |
| |
| failed: |
| CloseHandle(data->hEventShutdown); |
| destroy_watch(data->watch_worktree); |
| destroy_watch(data->watch_gitdir); |
| |
| return -1; |
| } |
| |
| void fsm_listen__dtor(struct fsmonitor_daemon_state *state) |
| { |
| struct fsm_listen_data *data; |
| |
| if (!state || !state->listen_data) |
| return; |
| |
| data = state->listen_data; |
| |
| CloseHandle(data->hEventShutdown); |
| destroy_watch(data->watch_worktree); |
| destroy_watch(data->watch_gitdir); |
| |
| FREE_AND_NULL(state->listen_data); |
| } |