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