]> git.ipfire.org Git - thirdparty/git.git/blame - compat/fsmonitor/fsm-health-win32.c
Merge branch 'jk/ci-retire-allow-ref'
[thirdparty/git.git] / compat / fsmonitor / fsm-health-win32.c
CommitLineData
bc5c5ec0 1#include "git-compat-util.h"
d0605550 2#include "config.h"
68d68646 3#include "fsmonitor-ll.h"
d0605550
JH
4#include "fsm-health.h"
5#include "fsmonitor--daemon.h"
f394e093 6#include "gettext.h"
d0605550 7
90a70fa8
JH
8/*
9 * Every minute wake up and test our health.
10 */
11#define WAIT_FREQ_MS (60 * 1000)
12
13/*
14 * State machine states for each of the interval functions
15 * used for polling our health.
16 */
17enum interval_fn_ctx {
18 CTX_INIT = 0,
19 CTX_TERM,
20 CTX_TIMER
21};
22
23typedef int (interval_fn)(struct fsmonitor_daemon_state *state,
24 enum interval_fn_ctx ctx);
25
d0605550
JH
26struct fsm_health_data
27{
28 HANDLE hEventShutdown;
29
30 HANDLE hHandles[1]; /* the array does not own these handles */
31#define HEALTH_SHUTDOWN 0
32 int nr_handles; /* number of active event handles */
6504cfd3
JH
33
34 struct wt_moved
35 {
36 wchar_t wpath[MAX_PATH + 1];
37 BY_HANDLE_FILE_INFORMATION bhfi;
38 } wt_moved;
d0605550
JH
39};
40
6504cfd3
JH
41/*
42 * Lookup the system unique ID for the path. This is as close as
43 * we get to an inode number, but this also contains volume info,
44 * so it is a little stronger.
45 */
46static int lookup_bhfi(wchar_t *wpath,
47 BY_HANDLE_FILE_INFORMATION *bhfi)
48{
49 DWORD desired_access = FILE_LIST_DIRECTORY;
50 DWORD share_mode =
51 FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
52 HANDLE hDir;
53
54 hDir = CreateFileW(wpath, desired_access, share_mode, NULL,
55 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
56 if (hDir == INVALID_HANDLE_VALUE) {
57 error(_("[GLE %ld] health thread could not open '%ls'"),
58 GetLastError(), wpath);
59 return -1;
60 }
61
62 if (!GetFileInformationByHandle(hDir, bhfi)) {
63 error(_("[GLE %ld] health thread getting BHFI for '%ls'"),
64 GetLastError(), wpath);
65 CloseHandle(hDir);
66 return -1;
67 }
68
69 CloseHandle(hDir);
70 return 0;
71}
72
73/*
74 * Compare the relevant fields from two system unique IDs.
75 * We use this to see if two different handles to the same
76 * path actually refer to the same *instance* of the file
77 * or directory.
78 */
79static int bhfi_eq(const BY_HANDLE_FILE_INFORMATION *bhfi_1,
80 const BY_HANDLE_FILE_INFORMATION *bhfi_2)
81{
82 return (bhfi_1->dwVolumeSerialNumber == bhfi_2->dwVolumeSerialNumber &&
83 bhfi_1->nFileIndexHigh == bhfi_2->nFileIndexHigh &&
84 bhfi_1->nFileIndexLow == bhfi_2->nFileIndexLow);
85}
86
87/*
88 * Shutdown if the original worktree root directory been deleted,
89 * moved, or renamed?
90 *
91 * Since the main thread did a "chdir(getenv($HOME))" and our CWD
92 * is not in the worktree root directory and because the listener
93 * thread added FILE_SHARE_DELETE to the watch handle, it is possible
94 * for the root directory to be moved or deleted while we are still
95 * watching it. We want to detect that here and force a shutdown.
96 *
97 * Granted, a delete MAY cause some operations to fail, such as
98 * GetOverlappedResult(), but it is not guaranteed. And because
99 * ReadDirectoryChangesW() only reports on changes *WITHIN* the
100 * directory, not changes *ON* the directory, our watch will not
101 * receive a delete event for it.
102 *
103 * A move/rename of the worktree root will also not generate an event.
104 * And since the listener thread already has an open handle, it may
105 * continue to receive events for events within the directory.
106 * However, the pathname of the named-pipe was constructed using the
107 * original location of the worktree root. (Remember named-pipes are
108 * stored in the NPFS and not in the actual file system.) Clients
109 * trying to talk to the worktree after the move/rename will not
110 * reach our daemon process, since we're still listening on the
111 * pipe with original path.
112 *
113 * Furthermore, if the user does something like:
114 *
115 * $ mv repo repo.old
116 * $ git init repo
117 *
118 * A new daemon cannot be started in the new instance of "repo"
119 * because the named-pipe is still being used by the daemon on
120 * the original instance.
121 *
122 * So, detect move/rename/delete and shutdown. This should also
123 * handle unsafe drive removal.
124 *
125 * We use the file system unique ID to distinguish the original
126 * directory instance from a new instance and force a shutdown
127 * if the unique ID changes.
128 *
129 * Since a worktree move/rename/delete/unmount doesn't happen
130 * that often (and we can't get an immediate event anyway), we
131 * use a timeout and periodically poll it.
132 */
133static int has_worktree_moved(struct fsmonitor_daemon_state *state,
134 enum interval_fn_ctx ctx)
135{
136 struct fsm_health_data *data = state->health_data;
137 BY_HANDLE_FILE_INFORMATION bhfi;
138 int r;
139
140 switch (ctx) {
141 case CTX_TERM:
142 return 0;
143
144 case CTX_INIT:
145 if (xutftowcs_path(data->wt_moved.wpath,
146 state->path_worktree_watch.buf) < 0) {
147 error(_("could not convert to wide characters: '%s'"),
148 state->path_worktree_watch.buf);
149 return -1;
150 }
151
152 /*
153 * On the first call we lookup the unique sequence ID for
154 * the worktree root directory.
155 */
156 return lookup_bhfi(data->wt_moved.wpath, &data->wt_moved.bhfi);
157
158 case CTX_TIMER:
159 r = lookup_bhfi(data->wt_moved.wpath, &bhfi);
160 if (r)
161 return r;
162 if (!bhfi_eq(&data->wt_moved.bhfi, &bhfi)) {
163 error(_("BHFI changed '%ls'"), data->wt_moved.wpath);
164 return -1;
165 }
166 return 0;
167
168 default:
169 die(_("unhandled case in 'has_worktree_moved': %d"),
170 (int)ctx);
171 }
172
173 return 0;
174}
175
176
d0605550
JH
177int fsm_health__ctor(struct fsmonitor_daemon_state *state)
178{
179 struct fsm_health_data *data;
180
181 CALLOC_ARRAY(data, 1);
182
183 data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
184
185 data->hHandles[HEALTH_SHUTDOWN] = data->hEventShutdown;
186 data->nr_handles++;
187
188 state->health_data = data;
189 return 0;
190}
191
192void fsm_health__dtor(struct fsmonitor_daemon_state *state)
193{
194 struct fsm_health_data *data;
195
196 if (!state || !state->health_data)
197 return;
198
199 data = state->health_data;
200
201 CloseHandle(data->hEventShutdown);
202
203 FREE_AND_NULL(state->health_data);
204}
205
90a70fa8
JH
206/*
207 * A table of the polling functions.
208 */
209static interval_fn *table[] = {
6504cfd3 210 has_worktree_moved,
90a70fa8
JH
211 NULL, /* must be last */
212};
213
214/*
215 * Call all of the polling functions in the table.
216 * Shortcut and return first error.
217 *
218 * Return 0 if all succeeded.
219 */
220static int call_all(struct fsmonitor_daemon_state *state,
221 enum interval_fn_ctx ctx)
222{
223 int k;
224
225 for (k = 0; table[k]; k++) {
226 int r = table[k](state, ctx);
227 if (r)
228 return r;
229 }
230
231 return 0;
232}
233
d0605550
JH
234void fsm_health__loop(struct fsmonitor_daemon_state *state)
235{
236 struct fsm_health_data *data = state->health_data;
90a70fa8
JH
237 int r;
238
239 r = call_all(state, CTX_INIT);
240 if (r < 0)
241 goto force_error_stop;
242 if (r > 0)
243 goto force_shutdown;
d0605550
JH
244
245 for (;;) {
246 DWORD dwWait = WaitForMultipleObjects(data->nr_handles,
247 data->hHandles,
90a70fa8 248 FALSE, WAIT_FREQ_MS);
d0605550
JH
249
250 if (dwWait == WAIT_OBJECT_0 + HEALTH_SHUTDOWN)
251 goto clean_shutdown;
252
90a70fa8
JH
253 if (dwWait == WAIT_TIMEOUT) {
254 r = call_all(state, CTX_TIMER);
255 if (r < 0)
256 goto force_error_stop;
257 if (r > 0)
258 goto force_shutdown;
259 continue;
260 }
261
d0605550
JH
262 error(_("health thread wait failed [GLE %ld]"),
263 GetLastError());
264 goto force_error_stop;
265 }
266
267force_error_stop:
268 state->health_error_code = -1;
90a70fa8 269force_shutdown:
d0605550
JH
270 ipc_server_stop_async(state->ipc_server_data);
271clean_shutdown:
90a70fa8 272 call_all(state, CTX_TERM);
d0605550
JH
273 return;
274}
275
276void fsm_health__stop_async(struct fsmonitor_daemon_state *state)
277{
278 SetEvent(state->health_data->hHandles[HEALTH_SHUTDOWN]);
279}