1 #include "git-compat-util.h"
3 #include "fsmonitor-ll.h"
4 #include "fsm-health.h"
5 #include "fsmonitor--daemon.h"
9 * Every minute wake up and test our health.
11 #define WAIT_FREQ_MS (60 * 1000)
14 * State machine states for each of the interval functions
15 * used for polling our health.
17 enum interval_fn_ctx
{
23 typedef int (interval_fn
)(struct fsmonitor_daemon_state
*state
,
24 enum interval_fn_ctx ctx
);
26 struct fsm_health_data
28 HANDLE hEventShutdown
;
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 */
36 wchar_t wpath
[MAX_PATH
+ 1];
37 BY_HANDLE_FILE_INFORMATION bhfi
;
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.
46 static int lookup_bhfi(wchar_t *wpath
,
47 BY_HANDLE_FILE_INFORMATION
*bhfi
)
49 DWORD desired_access
= FILE_LIST_DIRECTORY
;
51 FILE_SHARE_WRITE
| FILE_SHARE_READ
| FILE_SHARE_DELETE
;
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
);
62 if (!GetFileInformationByHandle(hDir
, bhfi
)) {
63 error(_("[GLE %ld] health thread getting BHFI for '%ls'"),
64 GetLastError(), wpath
);
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
79 static int bhfi_eq(const BY_HANDLE_FILE_INFORMATION
*bhfi_1
,
80 const BY_HANDLE_FILE_INFORMATION
*bhfi_2
)
82 return (bhfi_1
->dwVolumeSerialNumber
== bhfi_2
->dwVolumeSerialNumber
&&
83 bhfi_1
->nFileIndexHigh
== bhfi_2
->nFileIndexHigh
&&
84 bhfi_1
->nFileIndexLow
== bhfi_2
->nFileIndexLow
);
88 * Shutdown if the original worktree root directory been deleted,
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.
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.
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.
113 * Furthermore, if the user does something like:
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.
122 * So, detect move/rename/delete and shutdown. This should also
123 * handle unsafe drive removal.
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.
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.
133 static int has_worktree_moved(struct fsmonitor_daemon_state
*state
,
134 enum interval_fn_ctx ctx
)
136 struct fsm_health_data
*data
= state
->health_data
;
137 BY_HANDLE_FILE_INFORMATION bhfi
;
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
);
153 * On the first call we lookup the unique sequence ID for
154 * the worktree root directory.
156 return lookup_bhfi(data
->wt_moved
.wpath
, &data
->wt_moved
.bhfi
);
159 r
= lookup_bhfi(data
->wt_moved
.wpath
, &bhfi
);
162 if (!bhfi_eq(&data
->wt_moved
.bhfi
, &bhfi
)) {
163 error(_("BHFI changed '%ls'"), data
->wt_moved
.wpath
);
169 die(_("unhandled case in 'has_worktree_moved': %d"),
177 int fsm_health__ctor(struct fsmonitor_daemon_state
*state
)
179 struct fsm_health_data
*data
;
181 CALLOC_ARRAY(data
, 1);
183 data
->hEventShutdown
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
185 data
->hHandles
[HEALTH_SHUTDOWN
] = data
->hEventShutdown
;
188 state
->health_data
= data
;
192 void fsm_health__dtor(struct fsmonitor_daemon_state
*state
)
194 struct fsm_health_data
*data
;
196 if (!state
|| !state
->health_data
)
199 data
= state
->health_data
;
201 CloseHandle(data
->hEventShutdown
);
203 FREE_AND_NULL(state
->health_data
);
207 * A table of the polling functions.
209 static interval_fn
*table
[] = {
211 NULL
, /* must be last */
215 * Call all of the polling functions in the table.
216 * Shortcut and return first error.
218 * Return 0 if all succeeded.
220 static int call_all(struct fsmonitor_daemon_state
*state
,
221 enum interval_fn_ctx ctx
)
225 for (k
= 0; table
[k
]; k
++) {
226 int r
= table
[k
](state
, ctx
);
234 void fsm_health__loop(struct fsmonitor_daemon_state
*state
)
236 struct fsm_health_data
*data
= state
->health_data
;
239 r
= call_all(state
, CTX_INIT
);
241 goto force_error_stop
;
246 DWORD dwWait
= WaitForMultipleObjects(data
->nr_handles
,
248 FALSE
, WAIT_FREQ_MS
);
250 if (dwWait
== WAIT_OBJECT_0
+ HEALTH_SHUTDOWN
)
253 if (dwWait
== WAIT_TIMEOUT
) {
254 r
= call_all(state
, CTX_TIMER
);
256 goto force_error_stop
;
262 error(_("health thread wait failed [GLE %ld]"),
264 goto force_error_stop
;
268 state
->health_error_code
= -1;
270 ipc_server_stop_async(state
->ipc_server_data
);
272 call_all(state
, CTX_TERM
);
276 void fsm_health__stop_async(struct fsmonitor_daemon_state
*state
)
278 SetEvent(state
->health_data
->hHandles
[HEALTH_SHUTDOWN
]);