1 #include "git-compat-util.h"
3 #include "fsmonitor-ll.h"
4 #include "fsm-health.h"
5 #include "fsmonitor--daemon.h"
7 #include "simple-ipc.h"
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
{
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 */
37 wchar_t wpath
[MAX_PATH
+ 1];
38 BY_HANDLE_FILE_INFORMATION bhfi
;
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
;
52 FILE_SHARE_WRITE
| FILE_SHARE_READ
| FILE_SHARE_DELETE
;
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
);
63 if (!GetFileInformationByHandle(hDir
, bhfi
)) {
64 error(_("[GLE %ld] health thread getting BHFI for '%ls'"),
65 GetLastError(), wpath
);
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
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,
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:
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
;
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
);
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
);
160 r
= lookup_bhfi(data
->wt_moved
.wpath
, &bhfi
);
163 if (!bhfi_eq(&data
->wt_moved
.bhfi
, &bhfi
)) {
164 error(_("BHFI changed '%ls'"), data
->wt_moved
.wpath
);
170 die(_("unhandled case in 'has_worktree_moved': %d"),
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
;
189 state
->health_data
= data
;
193 void fsm_health__dtor(struct fsmonitor_daemon_state
*state
)
195 struct fsm_health_data
*data
;
197 if (!state
|| !state
->health_data
)
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
[] = {
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
)
226 for (k
= 0; table
[k
]; k
++) {
227 int r
= table
[k
](state
, ctx
);
235 void fsm_health__loop(struct fsmonitor_daemon_state
*state
)
237 struct fsm_health_data
*data
= state
->health_data
;
240 r
= call_all(state
, CTX_INIT
);
242 goto force_error_stop
;
247 DWORD dwWait
= WaitForMultipleObjects(data
->nr_handles
,
249 FALSE
, WAIT_FREQ_MS
);
251 if (dwWait
== WAIT_OBJECT_0
+ HEALTH_SHUTDOWN
)
254 if (dwWait
== WAIT_TIMEOUT
) {
255 r
= call_all(state
, CTX_TIMER
);
257 goto force_error_stop
;
263 error(_("health thread wait failed [GLE %ld]"),
265 goto force_error_stop
;
269 state
->health_error_code
= -1;
271 ipc_server_stop_async(state
->ipc_server_data
);
273 call_all(state
, CTX_TERM
);
277 void fsm_health__stop_async(struct fsmonitor_daemon_state
*state
)
279 SetEvent(state
->health_data
->hHandles
[HEALTH_SHUTDOWN
]);