compat/mingw: support POSIX semantics for atomic renames
[git/gitster.git] / compat / fsmonitor / fsm-health-win32.c
blob2aa8c219acee4def1711e5f84d45571e34be4e16
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"
9 /*
10 * Every minute wake up and test our health.
12 #define WAIT_FREQ_MS (60 * 1000)
15 * State machine states for each of the interval functions
16 * used for polling our health.
18 enum interval_fn_ctx {
19 CTX_INIT = 0,
20 CTX_TERM,
21 CTX_TIMER
24 typedef int (interval_fn)(struct fsmonitor_daemon_state *state,
25 enum interval_fn_ctx ctx);
27 struct fsm_health_data
29 HANDLE hEventShutdown;
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 */
35 struct wt_moved
37 wchar_t wpath[MAX_PATH + 1];
38 BY_HANDLE_FILE_INFORMATION bhfi;
39 } wt_moved;
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.
47 static int lookup_bhfi(wchar_t *wpath,
48 BY_HANDLE_FILE_INFORMATION *bhfi)
50 DWORD desired_access = FILE_LIST_DIRECTORY;
51 DWORD share_mode =
52 FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
53 HANDLE hDir;
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;
63 if (!GetFileInformationByHandle(hDir, bhfi)) {
64 error(_("[GLE %ld] health thread getting BHFI for '%ls'"),
65 GetLastError(), wpath);
66 CloseHandle(hDir);
67 return -1;
70 CloseHandle(hDir);
71 return 0;
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.
80 static int bhfi_eq(const BY_HANDLE_FILE_INFORMATION *bhfi_1,
81 const BY_HANDLE_FILE_INFORMATION *bhfi_2)
83 return (bhfi_1->dwVolumeSerialNumber == bhfi_2->dwVolumeSerialNumber &&
84 bhfi_1->nFileIndexHigh == bhfi_2->nFileIndexHigh &&
85 bhfi_1->nFileIndexLow == bhfi_2->nFileIndexLow);
89 * Shutdown if the original worktree root directory been deleted,
90 * moved, or renamed?
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.
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.
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.
114 * Furthermore, if the user does something like:
116 * $ mv repo repo.old
117 * $ git init repo
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.
123 * So, detect move/rename/delete and shutdown. This should also
124 * handle unsafe drive removal.
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.
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.
134 static int has_worktree_moved(struct fsmonitor_daemon_state *state,
135 enum interval_fn_ctx ctx)
137 struct fsm_health_data *data = state->health_data;
138 BY_HANDLE_FILE_INFORMATION bhfi;
139 int r;
141 switch (ctx) {
142 case CTX_TERM:
143 return 0;
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;
154 * On the first call we lookup the unique sequence ID for
155 * the worktree root directory.
157 return lookup_bhfi(data->wt_moved.wpath, &data->wt_moved.bhfi);
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;
167 return 0;
169 default:
170 die(_("unhandled case in 'has_worktree_moved': %d"),
171 (int)ctx);
174 return 0;
178 int fsm_health__ctor(struct fsmonitor_daemon_state *state)
180 struct fsm_health_data *data;
182 CALLOC_ARRAY(data, 1);
184 data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
186 data->hHandles[HEALTH_SHUTDOWN] = data->hEventShutdown;
187 data->nr_handles++;
189 state->health_data = data;
190 return 0;
193 void fsm_health__dtor(struct fsmonitor_daemon_state *state)
195 struct fsm_health_data *data;
197 if (!state || !state->health_data)
198 return;
200 data = state->health_data;
202 CloseHandle(data->hEventShutdown);
204 FREE_AND_NULL(state->health_data);
208 * A table of the polling functions.
210 static interval_fn *table[] = {
211 has_worktree_moved,
212 NULL, /* must be last */
216 * Call all of the polling functions in the table.
217 * Shortcut and return first error.
219 * Return 0 if all succeeded.
221 static int call_all(struct fsmonitor_daemon_state *state,
222 enum interval_fn_ctx ctx)
224 int k;
226 for (k = 0; table[k]; k++) {
227 int r = table[k](state, ctx);
228 if (r)
229 return r;
232 return 0;
235 void fsm_health__loop(struct fsmonitor_daemon_state *state)
237 struct fsm_health_data *data = state->health_data;
238 int r;
240 r = call_all(state, CTX_INIT);
241 if (r < 0)
242 goto force_error_stop;
243 if (r > 0)
244 goto force_shutdown;
246 for (;;) {
247 DWORD dwWait = WaitForMultipleObjects(data->nr_handles,
248 data->hHandles,
249 FALSE, WAIT_FREQ_MS);
251 if (dwWait == WAIT_OBJECT_0 + HEALTH_SHUTDOWN)
252 goto clean_shutdown;
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;
263 error(_("health thread wait failed [GLE %ld]"),
264 GetLastError());
265 goto force_error_stop;
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;
277 void fsm_health__stop_async(struct fsmonitor_daemon_state *state)
279 SetEvent(state->health_data->hHandles[HEALTH_SHUTDOWN]);