mirror of
https://github.com/git/git
synced 2024-09-19 08:21:36 +00:00
74ea5c9574
Dozens of files made use of trace and trace2 functions, without explicitly including trace.h or trace2.h. This made it more difficult to find which files could remove a dependence on cache.h. Make C files explicitly include trace.h or trace2.h if they are using them. Signed-off-by: Elijah Newren <newren@gmail.com> Acked-by: Calvin Wan <calvinwan@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
877 lines
24 KiB
C
877 lines
24 KiB
C
#include "cache.h"
|
|
#include "config.h"
|
|
#include "fsmonitor.h"
|
|
#include "fsm-listen.h"
|
|
#include "fsmonitor--daemon.h"
|
|
#include "gettext.h"
|
|
#include "trace2.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);
|
|
}
|