@@ -14,7 +14,10 @@ enum interval_fn_ctx { CTX_INIT = 0, CTX_TERM, CTX_TIMER };
14
14
typedef int (interval_fn )(struct fsmonitor_daemon_state * state ,
15
15
enum interval_fn_ctx ctx );
16
16
17
+ static interval_fn has_worktree_moved ;
18
+
17
19
static interval_fn * table [] = {
20
+ has_worktree_moved ,
18
21
NULL , /* must be last */
19
22
};
20
23
@@ -45,6 +48,12 @@ struct fsm_health_data
45
48
HANDLE hHandles [1 ]; /* the array does not own these handles */
46
49
#define HEALTH_SHUTDOWN 0
47
50
int nr_handles ; /* number of active event handles */
51
+
52
+ struct wt_moved
53
+ {
54
+ wchar_t wpath [MAX_PATH + 1 ];
55
+ BY_HANDLE_FILE_INFORMATION bhfi ;
56
+ } wt_moved ;
48
57
};
49
58
50
59
int fsm_health__ctor (struct fsmonitor_daemon_state * state )
@@ -76,6 +85,130 @@ void fsm_health__dtor(struct fsmonitor_daemon_state *state)
76
85
FREE_AND_NULL (state -> health_data );
77
86
}
78
87
88
+ static int lookup_bhfi (wchar_t * wpath ,
89
+ BY_HANDLE_FILE_INFORMATION * bhfi )
90
+ {
91
+ DWORD desired_access = FILE_LIST_DIRECTORY ;
92
+ DWORD share_mode =
93
+ FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE ;
94
+ HANDLE hDir ;
95
+
96
+ hDir = CreateFileW (wpath , desired_access , share_mode , NULL ,
97
+ OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
98
+ if (hDir == INVALID_HANDLE_VALUE ) {
99
+ error (_ ("[GLE %ld] health thread could not open '%ls'" ),
100
+ GetLastError (), wpath );
101
+ return -1 ;
102
+ }
103
+
104
+ if (!GetFileInformationByHandle (hDir , bhfi )) {
105
+ error (_ ("[GLE %ld] health thread getting BHFI for '%ls'" ),
106
+ GetLastError (), wpath );
107
+ CloseHandle (hDir );
108
+ return -1 ;
109
+ }
110
+
111
+ CloseHandle (hDir );
112
+ return 0 ;
113
+ }
114
+
115
+ static int bhfi_eq (const BY_HANDLE_FILE_INFORMATION * bhfi_1 ,
116
+ const BY_HANDLE_FILE_INFORMATION * bhfi_2 )
117
+ {
118
+ return (bhfi_1 -> dwVolumeSerialNumber == bhfi_2 -> dwVolumeSerialNumber &&
119
+ bhfi_1 -> nFileIndexHigh == bhfi_2 -> nFileIndexHigh &&
120
+ bhfi_1 -> nFileIndexLow == bhfi_2 -> nFileIndexLow );
121
+ }
122
+
123
+ /*
124
+ * Shutdown if the original worktree root directory been deleted,
125
+ * moved, or renamed?
126
+ *
127
+ * Since the main thread did a "chdir(getenv($HOME))" and our CWD
128
+ * is not in the worktree root directory and because the listener
129
+ * thread added FILE_SHARE_DELETE to the watch handle, it is possible
130
+ * for the root directory to be moved or deleted while we are still
131
+ * watching it. We want to detect that here and force a shutdown.
132
+ *
133
+ * Granted, a delete MAY cause some operations to fail, such as
134
+ * GetOverlappedResult(), but it is not guaranteed. And because
135
+ * ReadDirectoryChangesW() only reports on changes *WITHIN* the
136
+ * directory, not changes *ON* the directory, our watch will not
137
+ * receive a delete event for it.
138
+ *
139
+ * A move/rename of the worktree root will also not generate an event.
140
+ * And since the listener thread already has an open handle, it may
141
+ * continue to receive events for events within the directory.
142
+ * However, the pathname of the named-pipe was constructed using the
143
+ * original location of the worktree root. (Remember named-pipes are
144
+ * stored in the NPFS and not in the actual file system.) Clients
145
+ * trying to talk to the worktree after the move/rename will not
146
+ * reach our daemon process, since we're still listening on the
147
+ * pipe with original path.
148
+ *
149
+ * Furthermore, if the user does something like:
150
+ *
151
+ * $ mv repo repo.old
152
+ * $ git init repo
153
+ *
154
+ * A new daemon cannot be started in the new instance of "repo"
155
+ * because the named-pipe is still being used by the daemon on
156
+ * the original instance.
157
+ *
158
+ * So, detect move/rename/delete and shutdown. This should also
159
+ * handle unsafe drive removal.
160
+ *
161
+ * We use the file system unique ID to distinguish the original
162
+ * directory instance from a new instance and force a shutdown
163
+ * if the unique ID changes.
164
+ *
165
+ * Since a worktree move/rename/delete/unmount doesn't happen
166
+ * that often (and we can't get an immediate event anyway), we
167
+ * use a timeout and periodically poll it.
168
+ */
169
+ static int has_worktree_moved (struct fsmonitor_daemon_state * state ,
170
+ enum interval_fn_ctx ctx )
171
+ {
172
+ struct fsm_health_data * data = state -> health_data ;
173
+ BY_HANDLE_FILE_INFORMATION bhfi ;
174
+ int r ;
175
+
176
+ switch (ctx ) {
177
+ case CTX_TERM :
178
+ return 0 ;
179
+
180
+ case CTX_INIT :
181
+ if (xutftowcs_path (data -> wt_moved .wpath ,
182
+ state -> path_worktree_watch .buf ) < 0 ) {
183
+ error (_ ("could not convert to wide characters: '%s'" ),
184
+ state -> path_worktree_watch .buf );
185
+ return -1 ;
186
+ }
187
+
188
+ /*
189
+ * On the first call we lookup the unique sequence ID for
190
+ * the worktree root directory.
191
+ */
192
+ return lookup_bhfi (data -> wt_moved .wpath , & data -> wt_moved .bhfi );
193
+
194
+ case CTX_TIMER :
195
+ r = lookup_bhfi (data -> wt_moved .wpath , & bhfi );
196
+ if (r )
197
+ return r ;
198
+ if (!bhfi_eq (& data -> wt_moved .bhfi , & bhfi )) {
199
+ error (_ ("BHFI changed '%ls'" ), data -> wt_moved .wpath );
200
+ return -1 ;
201
+ }
202
+ return 0 ;
203
+
204
+ default :
205
+ die ("unhandled case in 'has_worktree_moved': %d" ,
206
+ (int )ctx );
207
+ }
208
+
209
+ return 0 ;
210
+ }
211
+
79
212
void fsm_health__loop (struct fsmonitor_daemon_state * state )
80
213
{
81
214
struct fsm_health_data * data = state -> health_data ;
0 commit comments