Skip to content

Commit bd8ab71

Browse files
jeffhostetlerdscho
authored andcommitted
compat/fsmonitor/fsm-health-win32: force shutdown daemon if worktree root moves
Force shutdown fsmonitor daemon if the worktree root directory is moved, renamed, or deleted. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent 3f46db7 commit bd8ab71

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed

compat/fsmonitor/fsm-health-win32.c

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ enum interval_fn_ctx { CTX_INIT = 0, CTX_TERM, CTX_TIMER };
1414
typedef int (interval_fn)(struct fsmonitor_daemon_state *state,
1515
enum interval_fn_ctx ctx);
1616

17+
static interval_fn has_worktree_moved;
18+
1719
static interval_fn *table[] = {
20+
has_worktree_moved,
1821
NULL, /* must be last */
1922
};
2023

@@ -45,6 +48,12 @@ struct fsm_health_data
4548
HANDLE hHandles[1]; /* the array does not own these handles */
4649
#define HEALTH_SHUTDOWN 0
4750
int nr_handles; /* number of active event handles */
51+
52+
struct wt_moved
53+
{
54+
wchar_t wpath[MAX_PATH + 1];
55+
BY_HANDLE_FILE_INFORMATION bhfi;
56+
} wt_moved;
4857
};
4958

5059
int fsm_health__ctor(struct fsmonitor_daemon_state *state)
@@ -76,6 +85,130 @@ void fsm_health__dtor(struct fsmonitor_daemon_state *state)
7685
FREE_AND_NULL(state->health_data);
7786
}
7887

88+
static int lookup_bhfi(wchar_t *wpath,
89+
BY_HANDLE_FILE_INFORMATION *bhfi)
90+
{
91+
DWORD desired_access = FILE_LIST_DIRECTORY;
92+
DWORD share_mode =
93+
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
94+
HANDLE hDir;
95+
96+
hDir = CreateFileW(wpath, desired_access, share_mode, NULL,
97+
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
98+
if (hDir == INVALID_HANDLE_VALUE) {
99+
error(_("[GLE %ld] health thread could not open '%ls'"),
100+
GetLastError(), wpath);
101+
return -1;
102+
}
103+
104+
if (!GetFileInformationByHandle(hDir, bhfi)) {
105+
error(_("[GLE %ld] health thread getting BHFI for '%ls'"),
106+
GetLastError(), wpath);
107+
CloseHandle(hDir);
108+
return -1;
109+
}
110+
111+
CloseHandle(hDir);
112+
return 0;
113+
}
114+
115+
static int bhfi_eq(const BY_HANDLE_FILE_INFORMATION *bhfi_1,
116+
const BY_HANDLE_FILE_INFORMATION *bhfi_2)
117+
{
118+
return (bhfi_1->dwVolumeSerialNumber == bhfi_2->dwVolumeSerialNumber &&
119+
bhfi_1->nFileIndexHigh == bhfi_2->nFileIndexHigh &&
120+
bhfi_1->nFileIndexLow == bhfi_2->nFileIndexLow);
121+
}
122+
123+
/*
124+
* Shutdown if the original worktree root directory been deleted,
125+
* moved, or renamed?
126+
*
127+
* Since the main thread did a "chdir(getenv($HOME))" and our CWD
128+
* is not in the worktree root directory and because the listener
129+
* thread added FILE_SHARE_DELETE to the watch handle, it is possible
130+
* for the root directory to be moved or deleted while we are still
131+
* watching it. We want to detect that here and force a shutdown.
132+
*
133+
* Granted, a delete MAY cause some operations to fail, such as
134+
* GetOverlappedResult(), but it is not guaranteed. And because
135+
* ReadDirectoryChangesW() only reports on changes *WITHIN* the
136+
* directory, not changes *ON* the directory, our watch will not
137+
* receive a delete event for it.
138+
*
139+
* A move/rename of the worktree root will also not generate an event.
140+
* And since the listener thread already has an open handle, it may
141+
* continue to receive events for events within the directory.
142+
* However, the pathname of the named-pipe was constructed using the
143+
* original location of the worktree root. (Remember named-pipes are
144+
* stored in the NPFS and not in the actual file system.) Clients
145+
* trying to talk to the worktree after the move/rename will not
146+
* reach our daemon process, since we're still listening on the
147+
* pipe with original path.
148+
*
149+
* Furthermore, if the user does something like:
150+
*
151+
* $ mv repo repo.old
152+
* $ git init repo
153+
*
154+
* A new daemon cannot be started in the new instance of "repo"
155+
* because the named-pipe is still being used by the daemon on
156+
* the original instance.
157+
*
158+
* So, detect move/rename/delete and shutdown. This should also
159+
* handle unsafe drive removal.
160+
*
161+
* We use the file system unique ID to distinguish the original
162+
* directory instance from a new instance and force a shutdown
163+
* if the unique ID changes.
164+
*
165+
* Since a worktree move/rename/delete/unmount doesn't happen
166+
* that often (and we can't get an immediate event anyway), we
167+
* use a timeout and periodically poll it.
168+
*/
169+
static int has_worktree_moved(struct fsmonitor_daemon_state *state,
170+
enum interval_fn_ctx ctx)
171+
{
172+
struct fsm_health_data *data = state->health_data;
173+
BY_HANDLE_FILE_INFORMATION bhfi;
174+
int r;
175+
176+
switch (ctx) {
177+
case CTX_TERM:
178+
return 0;
179+
180+
case CTX_INIT:
181+
if (xutftowcs_path(data->wt_moved.wpath,
182+
state->path_worktree_watch.buf) < 0) {
183+
error(_("could not convert to wide characters: '%s'"),
184+
state->path_worktree_watch.buf);
185+
return -1;
186+
}
187+
188+
/*
189+
* On the first call we lookup the unique sequence ID for
190+
* the worktree root directory.
191+
*/
192+
return lookup_bhfi(data->wt_moved.wpath, &data->wt_moved.bhfi);
193+
194+
case CTX_TIMER:
195+
r = lookup_bhfi(data->wt_moved.wpath, &bhfi);
196+
if (r)
197+
return r;
198+
if (!bhfi_eq(&data->wt_moved.bhfi, &bhfi)) {
199+
error(_("BHFI changed '%ls'"), data->wt_moved.wpath);
200+
return -1;
201+
}
202+
return 0;
203+
204+
default:
205+
die("unhandled case in 'has_worktree_moved': %d",
206+
(int)ctx);
207+
}
208+
209+
return 0;
210+
}
211+
79212
void fsm_health__loop(struct fsmonitor_daemon_state *state)
80213
{
81214
struct fsm_health_data *data = state->health_data;

0 commit comments

Comments
 (0)