git

Форк
0
/
fsm-listen-win32.c 
873 строки · 23.3 Кб
1
#include "git-compat-util.h"
2
#include "config.h"
3
#include "fsmonitor-ll.h"
4
#include "fsm-listen.h"
5
#include "fsmonitor--daemon.h"
6
#include "gettext.h"
7
#include "simple-ipc.h"
8
#include "trace2.h"
9

10
/*
11
 * The documentation of ReadDirectoryChangesW() states that the maximum
12
 * buffer size is 64K when the monitored directory is remote.
13
 *
14
 * Larger buffers may be used when the monitored directory is local and
15
 * will help us receive events faster from the kernel and avoid dropped
16
 * events.
17
 *
18
 * So we try to use a very large buffer and silently fallback to 64K if
19
 * we get an error.
20
 */
21
#define MAX_RDCW_BUF_FALLBACK (65536)
22
#define MAX_RDCW_BUF          (65536 * 8)
23

24
struct one_watch
25
{
26
	char buffer[MAX_RDCW_BUF];
27
	DWORD buf_len;
28
	DWORD count;
29

30
	struct strbuf path;
31
	wchar_t wpath_longname[MAX_PATH + 1];
32
	DWORD wpath_longname_len;
33

34
	HANDLE hDir;
35
	HANDLE hEvent;
36
	OVERLAPPED overlapped;
37

38
	/*
39
	 * Is there an active ReadDirectoryChangesW() call pending.  If so, we
40
	 * need to later call GetOverlappedResult() and possibly CancelIoEx().
41
	 */
42
	BOOL is_active;
43

44
	/*
45
	 * Are shortnames enabled on the containing drive?  This is
46
	 * always true for "C:/" drives and usually never true for
47
	 * other drives.
48
	 *
49
	 * We only set this for the worktree because we only need to
50
	 * convert shortname paths to longname paths for items we send
51
	 * to clients.  (We don't care about shortname expansion for
52
	 * paths inside a GITDIR because we never send them to
53
	 * clients.)
54
	 */
55
	BOOL has_shortnames;
56
	BOOL has_tilde;
57
	wchar_t dotgit_shortname[16]; /* for 8.3 name */
58
};
59

60
struct fsm_listen_data
61
{
62
	struct one_watch *watch_worktree;
63
	struct one_watch *watch_gitdir;
64

65
	HANDLE hEventShutdown;
66

67
	HANDLE hListener[3]; /* we don't own these handles */
68
#define LISTENER_SHUTDOWN 0
69
#define LISTENER_HAVE_DATA_WORKTREE 1
70
#define LISTENER_HAVE_DATA_GITDIR 2
71
	int nr_listener_handles;
72
};
73

74
/*
75
 * Convert the WCHAR path from the event into UTF8 and normalize it.
76
 *
77
 * `wpath_len` is in WCHARS not bytes.
78
 */
79
static int normalize_path_in_utf8(wchar_t *wpath, DWORD wpath_len,
80
				  struct strbuf *normalized_path)
81
{
82
	int reserve;
83
	int len = 0;
84

85
	strbuf_reset(normalized_path);
86
	if (!wpath_len)
87
		goto normalize;
88

89
	/*
90
	 * Pre-reserve enough space in the UTF8 buffer for
91
	 * each Unicode WCHAR character to be mapped into a
92
	 * sequence of 2 UTF8 characters.  That should let us
93
	 * avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time.
94
	 */
95
	reserve = 2 * wpath_len + 1;
96
	strbuf_grow(normalized_path, reserve);
97

98
	for (;;) {
99
		len = WideCharToMultiByte(CP_UTF8, 0,
100
					  wpath, wpath_len,
101
					  normalized_path->buf,
102
					  strbuf_avail(normalized_path) - 1,
103
					  NULL, NULL);
104
		if (len > 0)
105
			goto normalize;
106
		if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
107
			error(_("[GLE %ld] could not convert path to UTF-8: '%.*ls'"),
108
			      GetLastError(), (int)wpath_len, wpath);
109
			return -1;
110
		}
111

112
		strbuf_grow(normalized_path,
113
			    strbuf_avail(normalized_path) + reserve);
114
	}
115

116
normalize:
117
	strbuf_setlen(normalized_path, len);
118
	return strbuf_normalize_path(normalized_path);
119
}
120

121
/*
122
 * See if the worktree root directory has shortnames enabled.
123
 * This will help us decide if we need to do an expensive shortname
124
 * to longname conversion on every notification event.
125
 *
126
 * We do not want to create a file to test this, so we assume that the
127
 * root directory contains a ".git" file or directory.  (Our caller
128
 * only calls us for the worktree root, so this should be fine.)
129
 *
130
 * Remember the spelling of the shortname for ".git" if it exists.
131
 */
132
static void check_for_shortnames(struct one_watch *watch)
133
{
134
	wchar_t buf_in[MAX_PATH + 1];
135
	wchar_t buf_out[MAX_PATH + 1];
136
	wchar_t *last;
137
	wchar_t *p;
138

139
	/* build L"<wt-root-path>/.git" */
140
	swprintf(buf_in, ARRAY_SIZE(buf_in) - 1, L"%ls.git",
141
		 watch->wpath_longname);
142

143
	if (!GetShortPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out)))
144
		return;
145

146
	/*
147
	 * Get the final filename component of the shortpath.
148
	 * We know that the path does not have a final slash.
149
	 */
150
	for (last = p = buf_out; *p; p++)
151
		if (*p == L'/' || *p == '\\')
152
			last = p + 1;
153

154
	if (!wcscmp(last, L".git"))
155
		return;
156

157
	watch->has_shortnames = 1;
158
	wcsncpy(watch->dotgit_shortname, last,
159
		ARRAY_SIZE(watch->dotgit_shortname));
160

161
	/*
162
	 * The shortname for ".git" is usually of the form "GIT~1", so
163
	 * we should be able to avoid shortname to longname mapping on
164
	 * every notification event if the source string does not
165
	 * contain a "~".
166
	 *
167
	 * However, the documentation for GetLongPathNameW() says
168
	 * that there are filesystems that don't follow that pattern
169
	 * and warns against this optimization.
170
	 *
171
	 * Lets test this.
172
	 */
173
	if (wcschr(watch->dotgit_shortname, L'~'))
174
		watch->has_tilde = 1;
175
}
176

177
enum get_relative_result {
178
	GRR_NO_CONVERSION_NEEDED,
179
	GRR_HAVE_CONVERSION,
180
	GRR_SHUTDOWN,
181
};
182

183
/*
184
 * Info notification paths are relative to the root of the watch.
185
 * If our CWD is still at the root, then we can use relative paths
186
 * to convert from shortnames to longnames.  If our process has a
187
 * different CWD, then we need to construct an absolute path, do
188
 * the conversion, and then return the root-relative portion.
189
 *
190
 * We use the longname form of the root as our basis and assume that
191
 * it already has a trailing slash.
192
 *
193
 * `wpath_len` is in WCHARS not bytes.
194
 */
195
static enum get_relative_result get_relative_longname(
196
	struct one_watch *watch,
197
	const wchar_t *wpath, DWORD wpath_len,
198
	wchar_t *wpath_longname, size_t bufsize_wpath_longname)
199
{
200
	wchar_t buf_in[2 * MAX_PATH + 1];
201
	wchar_t buf_out[MAX_PATH + 1];
202
	DWORD root_len;
203
	DWORD out_len;
204

205
	/*
206
	 * Build L"<wt-root-path>/<event-rel-path>"
207
	 * Note that the <event-rel-path> might not be null terminated
208
	 * so we avoid swprintf() constructions.
209
	 */
210
	root_len = watch->wpath_longname_len;
211
	if (root_len + wpath_len >= ARRAY_SIZE(buf_in)) {
212
		/*
213
		 * This should not happen.  We cannot append the observed
214
		 * relative path onto the end of the worktree root path
215
		 * without overflowing the buffer.  Just give up.
216
		 */
217
		return GRR_SHUTDOWN;
218
	}
219
	wcsncpy(buf_in, watch->wpath_longname, root_len);
220
	wcsncpy(buf_in + root_len, wpath, wpath_len);
221
	buf_in[root_len + wpath_len] = 0;
222

223
	/*
224
	 * We don't actually know if the source pathname is a
225
	 * shortname or a longname.  This Windows routine allows
226
	 * either to be given as input.
227
	 */
228
	out_len = GetLongPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out));
229
	if (!out_len) {
230
		/*
231
		 * The shortname to longname conversion can fail for
232
		 * various reasons, for example if the file has been
233
		 * deleted.  (That is, if we just received a
234
		 * delete-file notification event and the file is
235
		 * already gone, we can't ask the file system to
236
		 * lookup the longname for it.  Likewise, for moves
237
		 * and renames where we are given the old name.)
238
		 *
239
		 * Since deleting or moving a file or directory by its
240
		 * shortname is rather obscure, I'm going ignore the
241
		 * failure and ask the caller to report the original
242
		 * relative path.  This seems kinder than failing here
243
		 * and forcing a resync.  Besides, forcing a resync on
244
		 * every file/directory delete would effectively
245
		 * cripple monitoring.
246
		 *
247
		 * We might revisit this in the future.
248
		 */
249
		return GRR_NO_CONVERSION_NEEDED;
250
	}
251

252
	if (!wcscmp(buf_in, buf_out)) {
253
		/*
254
		 * The path does not have a shortname alias.
255
		 */
256
		return GRR_NO_CONVERSION_NEEDED;
257
	}
258

259
	if (wcsncmp(buf_in, buf_out, root_len)) {
260
		/*
261
		 * The spelling of the root directory portion of the computed
262
		 * longname has changed.  This should not happen.  Basically,
263
		 * it means that we don't know where (without recomputing the
264
		 * longname of just the root directory) to split out the
265
		 * relative path.  Since this should not happen, I'm just
266
		 * going to let this fail and force a shutdown (because all
267
		 * subsequent events are probably going to see the same
268
		 * mismatch).
269
		 */
270
		return GRR_SHUTDOWN;
271
	}
272

273
	if (out_len - root_len >= bufsize_wpath_longname) {
274
		/*
275
		 * This should not happen.  We cannot copy the root-relative
276
		 * portion of the path into the provided buffer without an
277
		 * overrun.  Just give up.
278
		 */
279
		return GRR_SHUTDOWN;
280
	}
281

282
	/* Return the worktree root-relative portion of the longname. */
283

284
	wcscpy(wpath_longname, buf_out + root_len);
285
	return GRR_HAVE_CONVERSION;
286
}
287

288
void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
289
{
290
	SetEvent(state->listen_data->hListener[LISTENER_SHUTDOWN]);
291
}
292

293
static struct one_watch *create_watch(const char *path)
294
{
295
	struct one_watch *watch = NULL;
296
	DWORD desired_access = FILE_LIST_DIRECTORY;
297
	DWORD share_mode =
298
		FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
299
	HANDLE hDir;
300
	DWORD len_longname;
301
	wchar_t wpath[MAX_PATH + 1];
302
	wchar_t wpath_longname[MAX_PATH + 1];
303

304
	if (xutftowcs_path(wpath, path) < 0) {
305
		error(_("could not convert to wide characters: '%s'"), path);
306
		return NULL;
307
	}
308

309
	hDir = CreateFileW(wpath,
310
			   desired_access, share_mode, NULL, OPEN_EXISTING,
311
			   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
312
			   NULL);
313
	if (hDir == INVALID_HANDLE_VALUE) {
314
		error(_("[GLE %ld] could not watch '%s'"),
315
		      GetLastError(), path);
316
		return NULL;
317
	}
318

319
	len_longname = GetLongPathNameW(wpath, wpath_longname,
320
					ARRAY_SIZE(wpath_longname));
321
	if (!len_longname) {
322
		error(_("[GLE %ld] could not get longname of '%s'"),
323
		      GetLastError(), path);
324
		CloseHandle(hDir);
325
		return NULL;
326
	}
327

328
	if (wpath_longname[len_longname - 1] != L'/' &&
329
	    wpath_longname[len_longname - 1] != L'\\') {
330
		wpath_longname[len_longname++] = L'/';
331
		wpath_longname[len_longname] = 0;
332
	}
333

334
	CALLOC_ARRAY(watch, 1);
335

336
	watch->buf_len = sizeof(watch->buffer); /* assume full MAX_RDCW_BUF */
337

338
	strbuf_init(&watch->path, 0);
339
	strbuf_addstr(&watch->path, path);
340

341
	wcscpy(watch->wpath_longname, wpath_longname);
342
	watch->wpath_longname_len = len_longname;
343

344
	watch->hDir = hDir;
345
	watch->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
346

347
	return watch;
348
}
349

350
static void destroy_watch(struct one_watch *watch)
351
{
352
	if (!watch)
353
		return;
354

355
	strbuf_release(&watch->path);
356
	if (watch->hDir != INVALID_HANDLE_VALUE)
357
		CloseHandle(watch->hDir);
358
	if (watch->hEvent != INVALID_HANDLE_VALUE)
359
		CloseHandle(watch->hEvent);
360

361
	free(watch);
362
}
363

364
static int start_rdcw_watch(struct one_watch *watch)
365
{
366
	DWORD dwNotifyFilter =
367
		FILE_NOTIFY_CHANGE_FILE_NAME |
368
		FILE_NOTIFY_CHANGE_DIR_NAME |
369
		FILE_NOTIFY_CHANGE_ATTRIBUTES |
370
		FILE_NOTIFY_CHANGE_SIZE |
371
		FILE_NOTIFY_CHANGE_LAST_WRITE |
372
		FILE_NOTIFY_CHANGE_CREATION;
373

374
	ResetEvent(watch->hEvent);
375

376
	memset(&watch->overlapped, 0, sizeof(watch->overlapped));
377
	watch->overlapped.hEvent = watch->hEvent;
378

379
	/*
380
	 * Queue an async call using Overlapped IO.  This returns immediately.
381
	 * Our event handle will be signalled when the real result is available.
382
	 *
383
	 * The return value here just means that we successfully queued it.
384
	 * We won't know if the Read...() actually produces data until later.
385
	 */
386
	watch->is_active = ReadDirectoryChangesW(
387
		watch->hDir, watch->buffer, watch->buf_len, TRUE,
388
		dwNotifyFilter, &watch->count, &watch->overlapped, NULL);
389

390
	if (watch->is_active)
391
		return 0;
392

393
	error(_("ReadDirectoryChangedW failed on '%s' [GLE %ld]"),
394
	      watch->path.buf, GetLastError());
395
	return -1;
396
}
397

398
static int recv_rdcw_watch(struct one_watch *watch)
399
{
400
	DWORD gle;
401

402
	watch->is_active = FALSE;
403

404
	/*
405
	 * The overlapped result is ready.  If the Read...() was successful
406
	 * we finally receive the actual result into our buffer.
407
	 */
408
	if (GetOverlappedResult(watch->hDir, &watch->overlapped, &watch->count,
409
				TRUE))
410
		return 0;
411

412
	gle = GetLastError();
413
	if (gle == ERROR_INVALID_PARAMETER &&
414
	    /*
415
	     * The kernel throws an invalid parameter error when our
416
	     * buffer is too big and we are pointed at a remote
417
	     * directory (and possibly for other reasons).  Quietly
418
	     * set it down and try again.
419
	     *
420
	     * See note about MAX_RDCW_BUF at the top.
421
	     */
422
	    watch->buf_len > MAX_RDCW_BUF_FALLBACK) {
423
		watch->buf_len = MAX_RDCW_BUF_FALLBACK;
424
		return -2;
425
	}
426

427
	/*
428
	 * GetOverlappedResult() fails if the watched directory is
429
	 * deleted while we were waiting for an overlapped IO to
430
	 * complete.  The documentation did not list specific errors,
431
	 * but I observed ERROR_ACCESS_DENIED (0x05) errors during
432
	 * testing.
433
	 *
434
	 * Note that we only get notificaiton events for events
435
	 * *within* the directory, not *on* the directory itself.
436
	 * (These might be properies of the parent directory, for
437
	 * example).
438
	 *
439
	 * NEEDSWORK: We might try to check for the deleted directory
440
	 * case and return a better error message, but I'm not sure it
441
	 * is worth it.
442
	 *
443
	 * Shutdown if we get any error.
444
	 */
445

446
	error(_("GetOverlappedResult failed on '%s' [GLE %ld]"),
447
	      watch->path.buf, gle);
448
	return -1;
449
}
450

451
static void cancel_rdcw_watch(struct one_watch *watch)
452
{
453
	DWORD count;
454

455
	if (!watch || !watch->is_active)
456
		return;
457

458
	/*
459
	 * The calls to ReadDirectoryChangesW() and GetOverlappedResult()
460
	 * form a "pair" (my term) where we queue an IO and promise to
461
	 * hang around and wait for the kernel to give us the result.
462
	 *
463
	 * If for some reason after we queue the IO, we have to quit
464
	 * or otherwise not stick around for the second half, we must
465
	 * tell the kernel to abort the IO.  This prevents the kernel
466
	 * from writing to our buffer and/or signalling our event
467
	 * after we free them.
468
	 *
469
	 * (Ask me how much fun it was to track that one down).
470
	 */
471
	CancelIoEx(watch->hDir, &watch->overlapped);
472
	GetOverlappedResult(watch->hDir, &watch->overlapped, &count, TRUE);
473
	watch->is_active = FALSE;
474
}
475

476
/*
477
 * Process a single relative pathname event.
478
 * Return 1 if we should shutdown.
479
 */
480
static int process_1_worktree_event(
481
	struct string_list *cookie_list,
482
	struct fsmonitor_batch **batch,
483
	const struct strbuf *path,
484
	enum fsmonitor_path_type t,
485
	DWORD info_action)
486
{
487
	const char *slash;
488

489
	switch (t) {
490
	case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
491
		/* special case cookie files within .git */
492

493
		/* Use just the filename of the cookie file. */
494
		slash = find_last_dir_sep(path->buf);
495
		string_list_append(cookie_list,
496
				   slash ? slash + 1 : path->buf);
497
		break;
498

499
	case IS_INSIDE_DOT_GIT:
500
		/* ignore everything inside of "<worktree>/.git/" */
501
		break;
502

503
	case IS_DOT_GIT:
504
		/* "<worktree>/.git" was deleted (or renamed away) */
505
		if ((info_action == FILE_ACTION_REMOVED) ||
506
		    (info_action == FILE_ACTION_RENAMED_OLD_NAME)) {
507
			trace2_data_string("fsmonitor", NULL,
508
					   "fsm-listen/dotgit",
509
					   "removed");
510
			return 1;
511
		}
512
		break;
513

514
	case IS_WORKDIR_PATH:
515
		/* queue normal pathname */
516
		if (!*batch)
517
			*batch = fsmonitor_batch__new();
518
		fsmonitor_batch__add_path(*batch, path->buf);
519
		break;
520

521
	case IS_GITDIR:
522
	case IS_INSIDE_GITDIR:
523
	case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
524
	default:
525
		BUG("unexpected path classification '%d' for '%s'",
526
		    t, path->buf);
527
	}
528

529
	return 0;
530
}
531

532
/*
533
 * Process filesystem events that happen anywhere (recursively) under the
534
 * <worktree> root directory.  For a normal working directory, this includes
535
 * both version controlled files and the contents of the .git/ directory.
536
 *
537
 * If <worktree>/.git is a file, then we only see events for the file
538
 * itself.
539
 */
540
static int process_worktree_events(struct fsmonitor_daemon_state *state)
541
{
542
	struct fsm_listen_data *data = state->listen_data;
543
	struct one_watch *watch = data->watch_worktree;
544
	struct strbuf path = STRBUF_INIT;
545
	struct string_list cookie_list = STRING_LIST_INIT_DUP;
546
	struct fsmonitor_batch *batch = NULL;
547
	const char *p = watch->buffer;
548
	wchar_t wpath_longname[MAX_PATH + 1];
549

550
	/*
551
	 * If the kernel gets more events than will fit in the kernel
552
	 * buffer associated with our RDCW handle, it drops them and
553
	 * returns a count of zero.
554
	 *
555
	 * Yes, the call returns WITHOUT error and with length zero.
556
	 * This is the documented behavior.  (My testing has confirmed
557
	 * that it also sets the last error to ERROR_NOTIFY_ENUM_DIR,
558
	 * but we do not rely on that since the function did not
559
	 * return an error and it is not documented.)
560
	 *
561
	 * (The "overflow" case is not ambiguous with the "no data" case
562
	 * because we did an INFINITE wait.)
563
	 *
564
	 * This means we have a gap in coverage.  Tell the daemon layer
565
	 * to resync.
566
	 */
567
	if (!watch->count) {
568
		trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel",
569
				   "overflow");
570
		fsmonitor_force_resync(state);
571
		return LISTENER_HAVE_DATA_WORKTREE;
572
	}
573

574
	/*
575
	 * On Windows, `info` contains an "array" of paths that are
576
	 * relative to the root of whichever directory handle received
577
	 * the event.
578
	 */
579
	for (;;) {
580
		FILE_NOTIFY_INFORMATION *info = (void *)p;
581
		wchar_t *wpath = info->FileName;
582
		DWORD wpath_len = info->FileNameLength / sizeof(WCHAR);
583
		enum fsmonitor_path_type t;
584
		enum get_relative_result grr;
585

586
		if (watch->has_shortnames) {
587
			if (!wcscmp(wpath, watch->dotgit_shortname)) {
588
				/*
589
				 * This event exactly matches the
590
				 * spelling of the shortname of
591
				 * ".git", so we can skip some steps.
592
				 *
593
				 * (This case is odd because the user
594
				 * can "rm -rf GIT~1" and we cannot
595
				 * use the filesystem to map it back
596
				 * to ".git".)
597
				 */
598
				strbuf_reset(&path);
599
				strbuf_addstr(&path, ".git");
600
				t = IS_DOT_GIT;
601
				goto process_it;
602
			}
603

604
			if (watch->has_tilde && !wcschr(wpath, L'~')) {
605
				/*
606
				 * Shortnames on this filesystem have tildes
607
				 * and the notification path does not have
608
				 * one, so we assume that it is a longname.
609
				 */
610
				goto normalize_it;
611
			}
612

613
			grr = get_relative_longname(watch, wpath, wpath_len,
614
						    wpath_longname,
615
						    ARRAY_SIZE(wpath_longname));
616
			switch (grr) {
617
			case GRR_NO_CONVERSION_NEEDED: /* use info buffer as is */
618
				break;
619
			case GRR_HAVE_CONVERSION:
620
				wpath = wpath_longname;
621
				wpath_len = wcslen(wpath);
622
				break;
623
			default:
624
			case GRR_SHUTDOWN:
625
				goto force_shutdown;
626
			}
627
		}
628

629
normalize_it:
630
		if (normalize_path_in_utf8(wpath, wpath_len, &path) == -1)
631
			goto skip_this_path;
632

633
		t = fsmonitor_classify_path_workdir_relative(path.buf);
634

635
process_it:
636
		if (process_1_worktree_event(&cookie_list, &batch, &path, t,
637
					     info->Action))
638
			goto force_shutdown;
639

640
skip_this_path:
641
		if (!info->NextEntryOffset)
642
			break;
643
		p += info->NextEntryOffset;
644
	}
645

646
	fsmonitor_publish(state, batch, &cookie_list);
647
	batch = NULL;
648
	string_list_clear(&cookie_list, 0);
649
	strbuf_release(&path);
650
	return LISTENER_HAVE_DATA_WORKTREE;
651

652
force_shutdown:
653
	fsmonitor_batch__free_list(batch);
654
	string_list_clear(&cookie_list, 0);
655
	strbuf_release(&path);
656
	return LISTENER_SHUTDOWN;
657
}
658

659
/*
660
 * Process filesystem events that happened anywhere (recursively) under the
661
 * external <gitdir> (such as non-primary worktrees or submodules).
662
 * We only care about cookie files that our client threads created here.
663
 *
664
 * Note that we DO NOT get filesystem events on the external <gitdir>
665
 * itself (it is not inside something that we are watching).  In particular,
666
 * we do not get an event if the external <gitdir> is deleted.
667
 *
668
 * Also, we do not care about shortnames within the external <gitdir>, since
669
 * we never send these paths to clients.
670
 */
671
static int process_gitdir_events(struct fsmonitor_daemon_state *state)
672
{
673
	struct fsm_listen_data *data = state->listen_data;
674
	struct one_watch *watch = data->watch_gitdir;
675
	struct strbuf path = STRBUF_INIT;
676
	struct string_list cookie_list = STRING_LIST_INIT_DUP;
677
	const char *p = watch->buffer;
678

679
	if (!watch->count) {
680
		trace2_data_string("fsmonitor", NULL, "fsm-listen/kernel",
681
				   "overflow");
682
		fsmonitor_force_resync(state);
683
		return LISTENER_HAVE_DATA_GITDIR;
684
	}
685

686
	for (;;) {
687
		FILE_NOTIFY_INFORMATION *info = (void *)p;
688
		const char *slash;
689
		enum fsmonitor_path_type t;
690

691
		if (normalize_path_in_utf8(
692
			    info->FileName,
693
			    info->FileNameLength / sizeof(WCHAR),
694
			    &path) == -1)
695
			goto skip_this_path;
696

697
		t = fsmonitor_classify_path_gitdir_relative(path.buf);
698

699
		switch (t) {
700
		case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
701
			/* special case cookie files within gitdir */
702

703
			/* Use just the filename of the cookie file. */
704
			slash = find_last_dir_sep(path.buf);
705
			string_list_append(&cookie_list,
706
					   slash ? slash + 1 : path.buf);
707
			break;
708

709
		case IS_INSIDE_GITDIR:
710
			goto skip_this_path;
711

712
		default:
713
			BUG("unexpected path classification '%d' for '%s'",
714
			    t, path.buf);
715
		}
716

717
skip_this_path:
718
		if (!info->NextEntryOffset)
719
			break;
720
		p += info->NextEntryOffset;
721
	}
722

723
	fsmonitor_publish(state, NULL, &cookie_list);
724
	string_list_clear(&cookie_list, 0);
725
	strbuf_release(&path);
726
	return LISTENER_HAVE_DATA_GITDIR;
727
}
728

729
void fsm_listen__loop(struct fsmonitor_daemon_state *state)
730
{
731
	struct fsm_listen_data *data = state->listen_data;
732
	DWORD dwWait;
733
	int result;
734

735
	state->listen_error_code = 0;
736

737
	if (start_rdcw_watch(data->watch_worktree) == -1)
738
		goto force_error_stop;
739

740
	if (data->watch_gitdir &&
741
	    start_rdcw_watch(data->watch_gitdir) == -1)
742
		goto force_error_stop;
743

744
	for (;;) {
745
		dwWait = WaitForMultipleObjects(data->nr_listener_handles,
746
						data->hListener,
747
						FALSE, INFINITE);
748

749
		if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_WORKTREE) {
750
			result = recv_rdcw_watch(data->watch_worktree);
751
			if (result == -1) {
752
				/* hard error */
753
				goto force_error_stop;
754
			}
755
			if (result == -2) {
756
				/* retryable error */
757
				if (start_rdcw_watch(data->watch_worktree) == -1)
758
					goto force_error_stop;
759
				continue;
760
			}
761

762
			/* have data */
763
			if (process_worktree_events(state) == LISTENER_SHUTDOWN)
764
				goto force_shutdown;
765
			if (start_rdcw_watch(data->watch_worktree) == -1)
766
				goto force_error_stop;
767
			continue;
768
		}
769

770
		if (dwWait == WAIT_OBJECT_0 + LISTENER_HAVE_DATA_GITDIR) {
771
			result = recv_rdcw_watch(data->watch_gitdir);
772
			if (result == -1) {
773
				/* hard error */
774
				goto force_error_stop;
775
			}
776
			if (result == -2) {
777
				/* retryable error */
778
				if (start_rdcw_watch(data->watch_gitdir) == -1)
779
					goto force_error_stop;
780
				continue;
781
			}
782

783
			/* have data */
784
			if (process_gitdir_events(state) == LISTENER_SHUTDOWN)
785
				goto force_shutdown;
786
			if (start_rdcw_watch(data->watch_gitdir) == -1)
787
				goto force_error_stop;
788
			continue;
789
		}
790

791
		if (dwWait == WAIT_OBJECT_0 + LISTENER_SHUTDOWN)
792
			goto clean_shutdown;
793

794
		error(_("could not read directory changes [GLE %ld]"),
795
		      GetLastError());
796
		goto force_error_stop;
797
	}
798

799
force_error_stop:
800
	state->listen_error_code = -1;
801

802
force_shutdown:
803
	/*
804
	 * Tell the IPC thead pool to stop (which completes the await
805
	 * in the main thread (which will also signal this thread (if
806
	 * we are still alive))).
807
	 */
808
	ipc_server_stop_async(state->ipc_server_data);
809

810
clean_shutdown:
811
	cancel_rdcw_watch(data->watch_worktree);
812
	cancel_rdcw_watch(data->watch_gitdir);
813
}
814

815
int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
816
{
817
	struct fsm_listen_data *data;
818

819
	CALLOC_ARRAY(data, 1);
820

821
	data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
822

823
	data->watch_worktree = create_watch(state->path_worktree_watch.buf);
824
	if (!data->watch_worktree)
825
		goto failed;
826

827
	check_for_shortnames(data->watch_worktree);
828

829
	if (state->nr_paths_watching > 1) {
830
		data->watch_gitdir = create_watch(state->path_gitdir_watch.buf);
831
		if (!data->watch_gitdir)
832
			goto failed;
833
	}
834

835
	data->hListener[LISTENER_SHUTDOWN] = data->hEventShutdown;
836
	data->nr_listener_handles++;
837

838
	data->hListener[LISTENER_HAVE_DATA_WORKTREE] =
839
		data->watch_worktree->hEvent;
840
	data->nr_listener_handles++;
841

842
	if (data->watch_gitdir) {
843
		data->hListener[LISTENER_HAVE_DATA_GITDIR] =
844
			data->watch_gitdir->hEvent;
845
		data->nr_listener_handles++;
846
	}
847

848
	state->listen_data = data;
849
	return 0;
850

851
failed:
852
	CloseHandle(data->hEventShutdown);
853
	destroy_watch(data->watch_worktree);
854
	destroy_watch(data->watch_gitdir);
855

856
	return -1;
857
}
858

859
void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
860
{
861
	struct fsm_listen_data *data;
862

863
	if (!state || !state->listen_data)
864
		return;
865

866
	data = state->listen_data;
867

868
	CloseHandle(data->hEventShutdown);
869
	destroy_watch(data->watch_worktree);
870
	destroy_watch(data->watch_gitdir);
871

872
	FREE_AND_NULL(state->listen_data);
873
}
874

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.