You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
6.9 KiB
279 lines
6.9 KiB
#include "cache.h" |
|
#include "config.h" |
|
#include "fsmonitor.h" |
|
#include "fsm-health.h" |
|
#include "fsmonitor--daemon.h" |
|
#include "gettext.h" |
|
|
|
/* |
|
* Every minute wake up and test our health. |
|
*/ |
|
#define WAIT_FREQ_MS (60 * 1000) |
|
|
|
/* |
|
* State machine states for each of the interval functions |
|
* used for polling our health. |
|
*/ |
|
enum interval_fn_ctx { |
|
CTX_INIT = 0, |
|
CTX_TERM, |
|
CTX_TIMER |
|
}; |
|
|
|
typedef int (interval_fn)(struct fsmonitor_daemon_state *state, |
|
enum interval_fn_ctx ctx); |
|
|
|
struct fsm_health_data |
|
{ |
|
HANDLE hEventShutdown; |
|
|
|
HANDLE hHandles[1]; /* the array does not own these handles */ |
|
#define HEALTH_SHUTDOWN 0 |
|
int nr_handles; /* number of active event handles */ |
|
|
|
struct wt_moved |
|
{ |
|
wchar_t wpath[MAX_PATH + 1]; |
|
BY_HANDLE_FILE_INFORMATION bhfi; |
|
} wt_moved; |
|
}; |
|
|
|
/* |
|
* Lookup the system unique ID for the path. This is as close as |
|
* we get to an inode number, but this also contains volume info, |
|
* so it is a little stronger. |
|
*/ |
|
static int lookup_bhfi(wchar_t *wpath, |
|
BY_HANDLE_FILE_INFORMATION *bhfi) |
|
{ |
|
DWORD desired_access = FILE_LIST_DIRECTORY; |
|
DWORD share_mode = |
|
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; |
|
HANDLE hDir; |
|
|
|
hDir = CreateFileW(wpath, desired_access, share_mode, NULL, |
|
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); |
|
if (hDir == INVALID_HANDLE_VALUE) { |
|
error(_("[GLE %ld] health thread could not open '%ls'"), |
|
GetLastError(), wpath); |
|
return -1; |
|
} |
|
|
|
if (!GetFileInformationByHandle(hDir, bhfi)) { |
|
error(_("[GLE %ld] health thread getting BHFI for '%ls'"), |
|
GetLastError(), wpath); |
|
CloseHandle(hDir); |
|
return -1; |
|
} |
|
|
|
CloseHandle(hDir); |
|
return 0; |
|
} |
|
|
|
/* |
|
* Compare the relevant fields from two system unique IDs. |
|
* We use this to see if two different handles to the same |
|
* path actually refer to the same *instance* of the file |
|
* or directory. |
|
*/ |
|
static int bhfi_eq(const BY_HANDLE_FILE_INFORMATION *bhfi_1, |
|
const BY_HANDLE_FILE_INFORMATION *bhfi_2) |
|
{ |
|
return (bhfi_1->dwVolumeSerialNumber == bhfi_2->dwVolumeSerialNumber && |
|
bhfi_1->nFileIndexHigh == bhfi_2->nFileIndexHigh && |
|
bhfi_1->nFileIndexLow == bhfi_2->nFileIndexLow); |
|
} |
|
|
|
/* |
|
* Shutdown if the original worktree root directory been deleted, |
|
* moved, or renamed? |
|
* |
|
* Since the main thread did a "chdir(getenv($HOME))" and our CWD |
|
* is not in the worktree root directory and because the listener |
|
* thread added FILE_SHARE_DELETE to the watch handle, it is possible |
|
* for the root directory to be moved or deleted while we are still |
|
* watching it. We want to detect that here and force a shutdown. |
|
* |
|
* Granted, a delete MAY cause some operations to fail, such as |
|
* GetOverlappedResult(), but it is not guaranteed. And because |
|
* ReadDirectoryChangesW() only reports on changes *WITHIN* the |
|
* directory, not changes *ON* the directory, our watch will not |
|
* receive a delete event for it. |
|
* |
|
* A move/rename of the worktree root will also not generate an event. |
|
* And since the listener thread already has an open handle, it may |
|
* continue to receive events for events within the directory. |
|
* However, the pathname of the named-pipe was constructed using the |
|
* original location of the worktree root. (Remember named-pipes are |
|
* stored in the NPFS and not in the actual file system.) Clients |
|
* trying to talk to the worktree after the move/rename will not |
|
* reach our daemon process, since we're still listening on the |
|
* pipe with original path. |
|
* |
|
* Furthermore, if the user does something like: |
|
* |
|
* $ mv repo repo.old |
|
* $ git init repo |
|
* |
|
* A new daemon cannot be started in the new instance of "repo" |
|
* because the named-pipe is still being used by the daemon on |
|
* the original instance. |
|
* |
|
* So, detect move/rename/delete and shutdown. This should also |
|
* handle unsafe drive removal. |
|
* |
|
* We use the file system unique ID to distinguish the original |
|
* directory instance from a new instance and force a shutdown |
|
* if the unique ID changes. |
|
* |
|
* Since a worktree move/rename/delete/unmount doesn't happen |
|
* that often (and we can't get an immediate event anyway), we |
|
* use a timeout and periodically poll it. |
|
*/ |
|
static int has_worktree_moved(struct fsmonitor_daemon_state *state, |
|
enum interval_fn_ctx ctx) |
|
{ |
|
struct fsm_health_data *data = state->health_data; |
|
BY_HANDLE_FILE_INFORMATION bhfi; |
|
int r; |
|
|
|
switch (ctx) { |
|
case CTX_TERM: |
|
return 0; |
|
|
|
case CTX_INIT: |
|
if (xutftowcs_path(data->wt_moved.wpath, |
|
state->path_worktree_watch.buf) < 0) { |
|
error(_("could not convert to wide characters: '%s'"), |
|
state->path_worktree_watch.buf); |
|
return -1; |
|
} |
|
|
|
/* |
|
* On the first call we lookup the unique sequence ID for |
|
* the worktree root directory. |
|
*/ |
|
return lookup_bhfi(data->wt_moved.wpath, &data->wt_moved.bhfi); |
|
|
|
case CTX_TIMER: |
|
r = lookup_bhfi(data->wt_moved.wpath, &bhfi); |
|
if (r) |
|
return r; |
|
if (!bhfi_eq(&data->wt_moved.bhfi, &bhfi)) { |
|
error(_("BHFI changed '%ls'"), data->wt_moved.wpath); |
|
return -1; |
|
} |
|
return 0; |
|
|
|
default: |
|
die(_("unhandled case in 'has_worktree_moved': %d"), |
|
(int)ctx); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
int fsm_health__ctor(struct fsmonitor_daemon_state *state) |
|
{ |
|
struct fsm_health_data *data; |
|
|
|
CALLOC_ARRAY(data, 1); |
|
|
|
data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL); |
|
|
|
data->hHandles[HEALTH_SHUTDOWN] = data->hEventShutdown; |
|
data->nr_handles++; |
|
|
|
state->health_data = data; |
|
return 0; |
|
} |
|
|
|
void fsm_health__dtor(struct fsmonitor_daemon_state *state) |
|
{ |
|
struct fsm_health_data *data; |
|
|
|
if (!state || !state->health_data) |
|
return; |
|
|
|
data = state->health_data; |
|
|
|
CloseHandle(data->hEventShutdown); |
|
|
|
FREE_AND_NULL(state->health_data); |
|
} |
|
|
|
/* |
|
* A table of the polling functions. |
|
*/ |
|
static interval_fn *table[] = { |
|
has_worktree_moved, |
|
NULL, /* must be last */ |
|
}; |
|
|
|
/* |
|
* Call all of the polling functions in the table. |
|
* Shortcut and return first error. |
|
* |
|
* Return 0 if all succeeded. |
|
*/ |
|
static int call_all(struct fsmonitor_daemon_state *state, |
|
enum interval_fn_ctx ctx) |
|
{ |
|
int k; |
|
|
|
for (k = 0; table[k]; k++) { |
|
int r = table[k](state, ctx); |
|
if (r) |
|
return r; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
void fsm_health__loop(struct fsmonitor_daemon_state *state) |
|
{ |
|
struct fsm_health_data *data = state->health_data; |
|
int r; |
|
|
|
r = call_all(state, CTX_INIT); |
|
if (r < 0) |
|
goto force_error_stop; |
|
if (r > 0) |
|
goto force_shutdown; |
|
|
|
for (;;) { |
|
DWORD dwWait = WaitForMultipleObjects(data->nr_handles, |
|
data->hHandles, |
|
FALSE, WAIT_FREQ_MS); |
|
|
|
if (dwWait == WAIT_OBJECT_0 + HEALTH_SHUTDOWN) |
|
goto clean_shutdown; |
|
|
|
if (dwWait == WAIT_TIMEOUT) { |
|
r = call_all(state, CTX_TIMER); |
|
if (r < 0) |
|
goto force_error_stop; |
|
if (r > 0) |
|
goto force_shutdown; |
|
continue; |
|
} |
|
|
|
error(_("health thread wait failed [GLE %ld]"), |
|
GetLastError()); |
|
goto force_error_stop; |
|
} |
|
|
|
force_error_stop: |
|
state->health_error_code = -1; |
|
force_shutdown: |
|
ipc_server_stop_async(state->ipc_server_data); |
|
clean_shutdown: |
|
call_all(state, CTX_TERM); |
|
return; |
|
} |
|
|
|
void fsm_health__stop_async(struct fsmonitor_daemon_state *state) |
|
{ |
|
SetEvent(state->health_data->hHandles[HEALTH_SHUTDOWN]); |
|
}
|
|
|