git

Форк
0
/
fsm-listen-darwin.c 
540 строк · 15.4 Кб
1
#ifndef __clang__
2
#include <dispatch/dispatch.h>
3
#include "fsm-darwin-gcc.h"
4
#else
5
#include <CoreFoundation/CoreFoundation.h>
6
#include <CoreServices/CoreServices.h>
7

8
#ifndef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER
9
/*
10
 * This enum value was added in 10.13 to:
11
 *
12
 * /Applications/Xcode.app/Contents/Developer/Platforms/ \
13
 *    MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/ \
14
 *    Library/Frameworks/CoreServices.framework/Frameworks/ \
15
 *    FSEvents.framework/Versions/Current/Headers/FSEvents.h
16
 *
17
 * If we're compiling against an older SDK, this symbol won't be
18
 * present.  Silently define it here so that we don't have to ifdef
19
 * the logging or masking below.  This should be harmless since older
20
 * versions of macOS won't ever emit this FS event anyway.
21
 */
22
#define kFSEventStreamEventFlagItemCloned         0x00400000
23
#endif
24
#endif
25

26
#include "git-compat-util.h"
27
#include "fsmonitor-ll.h"
28
#include "fsm-listen.h"
29
#include "fsmonitor--daemon.h"
30
#include "fsmonitor-path-utils.h"
31
#include "gettext.h"
32
#include "simple-ipc.h"
33
#include "string-list.h"
34
#include "trace.h"
35

36
struct fsm_listen_data
37
{
38
	CFStringRef cfsr_worktree_path;
39
	CFStringRef cfsr_gitdir_path;
40

41
	CFArrayRef cfar_paths_to_watch;
42
	int nr_paths_watching;
43

44
	FSEventStreamRef stream;
45

46
	dispatch_queue_t dq;
47
	pthread_cond_t dq_finished;
48
	pthread_mutex_t dq_lock;
49

50
	enum shutdown_style {
51
		SHUTDOWN_EVENT = 0,
52
		FORCE_SHUTDOWN,
53
		FORCE_ERROR_STOP,
54
	} shutdown_style;
55

56
	unsigned int stream_scheduled:1;
57
	unsigned int stream_started:1;
58
};
59

60
static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)
61
{
62
	struct strbuf msg = STRBUF_INIT;
63

64
	if (flag & kFSEventStreamEventFlagMustScanSubDirs)
65
		strbuf_addstr(&msg, "MustScanSubDirs|");
66
	if (flag & kFSEventStreamEventFlagUserDropped)
67
		strbuf_addstr(&msg, "UserDropped|");
68
	if (flag & kFSEventStreamEventFlagKernelDropped)
69
		strbuf_addstr(&msg, "KernelDropped|");
70
	if (flag & kFSEventStreamEventFlagEventIdsWrapped)
71
		strbuf_addstr(&msg, "EventIdsWrapped|");
72
	if (flag & kFSEventStreamEventFlagHistoryDone)
73
		strbuf_addstr(&msg, "HistoryDone|");
74
	if (flag & kFSEventStreamEventFlagRootChanged)
75
		strbuf_addstr(&msg, "RootChanged|");
76
	if (flag & kFSEventStreamEventFlagMount)
77
		strbuf_addstr(&msg, "Mount|");
78
	if (flag & kFSEventStreamEventFlagUnmount)
79
		strbuf_addstr(&msg, "Unmount|");
80
	if (flag & kFSEventStreamEventFlagItemChangeOwner)
81
		strbuf_addstr(&msg, "ItemChangeOwner|");
82
	if (flag & kFSEventStreamEventFlagItemCreated)
83
		strbuf_addstr(&msg, "ItemCreated|");
84
	if (flag & kFSEventStreamEventFlagItemFinderInfoMod)
85
		strbuf_addstr(&msg, "ItemFinderInfoMod|");
86
	if (flag & kFSEventStreamEventFlagItemInodeMetaMod)
87
		strbuf_addstr(&msg, "ItemInodeMetaMod|");
88
	if (flag & kFSEventStreamEventFlagItemIsDir)
89
		strbuf_addstr(&msg, "ItemIsDir|");
90
	if (flag & kFSEventStreamEventFlagItemIsFile)
91
		strbuf_addstr(&msg, "ItemIsFile|");
92
	if (flag & kFSEventStreamEventFlagItemIsHardlink)
93
		strbuf_addstr(&msg, "ItemIsHardlink|");
94
	if (flag & kFSEventStreamEventFlagItemIsLastHardlink)
95
		strbuf_addstr(&msg, "ItemIsLastHardlink|");
96
	if (flag & kFSEventStreamEventFlagItemIsSymlink)
97
		strbuf_addstr(&msg, "ItemIsSymlink|");
98
	if (flag & kFSEventStreamEventFlagItemModified)
99
		strbuf_addstr(&msg, "ItemModified|");
100
	if (flag & kFSEventStreamEventFlagItemRemoved)
101
		strbuf_addstr(&msg, "ItemRemoved|");
102
	if (flag & kFSEventStreamEventFlagItemRenamed)
103
		strbuf_addstr(&msg, "ItemRenamed|");
104
	if (flag & kFSEventStreamEventFlagItemXattrMod)
105
		strbuf_addstr(&msg, "ItemXattrMod|");
106
	if (flag & kFSEventStreamEventFlagOwnEvent)
107
		strbuf_addstr(&msg, "OwnEvent|");
108
	if (flag & kFSEventStreamEventFlagItemCloned)
109
		strbuf_addstr(&msg, "ItemCloned|");
110

111
	trace_printf_key(&trace_fsmonitor, "fsevent: '%s', flags=0x%x %s",
112
			 path, flag, msg.buf);
113

114
	strbuf_release(&msg);
115
}
116

117
static int ef_is_root_changed(const FSEventStreamEventFlags ef)
118
{
119
	return (ef & kFSEventStreamEventFlagRootChanged);
120
}
121

122
static int ef_is_root_delete(const FSEventStreamEventFlags ef)
123
{
124
	return (ef & kFSEventStreamEventFlagItemIsDir &&
125
		ef & kFSEventStreamEventFlagItemRemoved);
126
}
127

128
static int ef_is_root_renamed(const FSEventStreamEventFlags ef)
129
{
130
	return (ef & kFSEventStreamEventFlagItemIsDir &&
131
		ef & kFSEventStreamEventFlagItemRenamed);
132
}
133

134
static int ef_is_dropped(const FSEventStreamEventFlags ef)
135
{
136
	return (ef & kFSEventStreamEventFlagMustScanSubDirs ||
137
		ef & kFSEventStreamEventFlagKernelDropped ||
138
		ef & kFSEventStreamEventFlagUserDropped);
139
}
140

141
/*
142
 * If an `xattr` change is the only reason we received this event,
143
 * then silently ignore it.  Git doesn't care about xattr's.  We
144
 * have to be careful here because the kernel can combine multiple
145
 * events for a single path.  And because events always have certain
146
 * bits set, such as `ItemIsFile` or `ItemIsDir`.
147
 *
148
 * Return 1 if we should ignore it.
149
 */
150
static int ef_ignore_xattr(const FSEventStreamEventFlags ef)
151
{
152
	static const FSEventStreamEventFlags mask =
153
		kFSEventStreamEventFlagItemChangeOwner |
154
		kFSEventStreamEventFlagItemCreated |
155
		kFSEventStreamEventFlagItemFinderInfoMod |
156
		kFSEventStreamEventFlagItemInodeMetaMod |
157
		kFSEventStreamEventFlagItemModified |
158
		kFSEventStreamEventFlagItemRemoved |
159
		kFSEventStreamEventFlagItemRenamed |
160
		kFSEventStreamEventFlagItemXattrMod |
161
		kFSEventStreamEventFlagItemCloned;
162

163
	return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod);
164
}
165

166
/*
167
 * On MacOS we have to adjust for Unicode composition insensitivity
168
 * (where NFC and NFD spellings are not respected).  The different
169
 * spellings are essentially aliases regardless of how the path is
170
 * actually stored on the disk.
171
 *
172
 * This is related to "core.precomposeUnicode" (which wants to try
173
 * to hide NFD completely and treat everything as NFC).  Here, we
174
 * don't know what the value the client has (or will have) for this
175
 * config setting when they make a query, so assume the worst and
176
 * emit both when the OS gives us an NFD path.
177
 */
178
static void my_add_path(struct fsmonitor_batch *batch, const char *path)
179
{
180
	char *composed;
181

182
	/* add the NFC or NFD path as received from the OS */
183
	fsmonitor_batch__add_path(batch, path);
184

185
	/* if NFD, also add the corresponding NFC spelling */
186
	composed = (char *)precompose_string_if_needed(path);
187
	if (!composed || composed == path)
188
		return;
189

190
	fsmonitor_batch__add_path(batch, composed);
191
	free(composed);
192
}
193

194

195
static void fsevent_callback(ConstFSEventStreamRef streamRef UNUSED,
196
			     void *ctx,
197
			     size_t num_of_events,
198
			     void *event_paths,
199
			     const FSEventStreamEventFlags event_flags[],
200
			     const FSEventStreamEventId event_ids[] UNUSED)
201
{
202
	struct fsmonitor_daemon_state *state = ctx;
203
	struct fsm_listen_data *data = state->listen_data;
204
	char **paths = (char **)event_paths;
205
	struct fsmonitor_batch *batch = NULL;
206
	struct string_list cookie_list = STRING_LIST_INIT_DUP;
207
	const char *path_k;
208
	const char *slash;
209
	char *resolved = NULL;
210
	struct strbuf tmp = STRBUF_INIT;
211
	int k;
212

213
	/*
214
	 * Build a list of all filesystem changes into a private/local
215
	 * list and without holding any locks.
216
	 */
217
	for (k = 0; k < num_of_events; k++) {
218
		/*
219
		 * On Mac, we receive an array of absolute paths.
220
		 */
221
		free(resolved);
222
		resolved = fsmonitor__resolve_alias(paths[k], &state->alias);
223
		if (resolved)
224
			path_k = resolved;
225
		else
226
			path_k = paths[k];
227

228
		/*
229
		 * If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.
230
		 * Please don't log them to Trace2.
231
		 *
232
		 * trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k);
233
		 */
234

235
		/*
236
		 * If event[k] is marked as dropped, we assume that we have
237
		 * lost sync with the filesystem and should flush our cached
238
		 * data.  We need to:
239
		 *
240
		 * [1] Abort/wake any client threads waiting for a cookie and
241
		 *     flush the cached state data (the current token), and
242
		 *     create a new token.
243
		 *
244
		 * [2] Discard the batch that we were locally building (since
245
		 *     they are conceptually relative to the just flushed
246
		 *     token).
247
		 */
248
		if (ef_is_dropped(event_flags[k])) {
249
			if (trace_pass_fl(&trace_fsmonitor))
250
				log_flags_set(path_k, event_flags[k]);
251

252
			fsmonitor_force_resync(state);
253
			fsmonitor_batch__free_list(batch);
254
			string_list_clear(&cookie_list, 0);
255
			batch = NULL;
256

257
			/*
258
			 * We assume that any events that we received
259
			 * in this callback after this dropped event
260
			 * may still be valid, so we continue rather
261
			 * than break.  (And just in case there is a
262
			 * delete of ".git" hiding in there.)
263
			 */
264
			continue;
265
		}
266

267
		if (ef_is_root_changed(event_flags[k])) {
268
			/*
269
			 * The spelling of the pathname of the root directory
270
			 * has changed.  This includes the name of the root
271
			 * directory itself or of any parent directory in the
272
			 * path.
273
			 *
274
			 * (There may be other conditions that throw this,
275
			 * but I couldn't find any information on it.)
276
			 *
277
			 * Force a shutdown now and avoid things getting
278
			 * out of sync.  The Unix domain socket is inside
279
			 * the .git directory and a spelling change will make
280
			 * it hard for clients to rendezvous with us.
281
			 */
282
			trace_printf_key(&trace_fsmonitor,
283
					 "event: root changed");
284
			goto force_shutdown;
285
		}
286

287
		if (ef_ignore_xattr(event_flags[k])) {
288
			trace_printf_key(&trace_fsmonitor,
289
					 "ignore-xattr: '%s', flags=0x%x",
290
					 path_k, event_flags[k]);
291
			continue;
292
		}
293

294
		switch (fsmonitor_classify_path_absolute(state, path_k)) {
295

296
		case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
297
		case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
298
			/* special case cookie files within .git or gitdir */
299

300
			/* Use just the filename of the cookie file. */
301
			slash = find_last_dir_sep(path_k);
302
			string_list_append(&cookie_list,
303
					   slash ? slash + 1 : path_k);
304
			break;
305

306
		case IS_INSIDE_DOT_GIT:
307
		case IS_INSIDE_GITDIR:
308
			/* ignore all other paths inside of .git or gitdir */
309
			break;
310

311
		case IS_DOT_GIT:
312
		case IS_GITDIR:
313
			/*
314
			 * If .git directory is deleted or renamed away,
315
			 * we have to quit.
316
			 */
317
			if (ef_is_root_delete(event_flags[k])) {
318
				trace_printf_key(&trace_fsmonitor,
319
						 "event: gitdir removed");
320
				goto force_shutdown;
321
			}
322
			if (ef_is_root_renamed(event_flags[k])) {
323
				trace_printf_key(&trace_fsmonitor,
324
						 "event: gitdir renamed");
325
				goto force_shutdown;
326
			}
327
			break;
328

329
		case IS_WORKDIR_PATH:
330
			/* try to queue normal pathnames */
331

332
			if (trace_pass_fl(&trace_fsmonitor))
333
				log_flags_set(path_k, event_flags[k]);
334

335
			/*
336
			 * Because of the implicit "binning" (the
337
			 * kernel calls us at a given frequency) and
338
			 * de-duping (the kernel is free to combine
339
			 * multiple events for a given pathname), an
340
			 * individual fsevent could be marked as both
341
			 * a file and directory.  Add it to the queue
342
			 * with both spellings so that the client will
343
			 * know how much to invalidate/refresh.
344
			 */
345

346
			if (event_flags[k] & (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemIsSymlink)) {
347
				const char *rel = path_k +
348
					state->path_worktree_watch.len + 1;
349

350
				if (!batch)
351
					batch = fsmonitor_batch__new();
352
				my_add_path(batch, rel);
353
			}
354

355
			if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) {
356
				const char *rel = path_k +
357
					state->path_worktree_watch.len + 1;
358

359
				strbuf_reset(&tmp);
360
				strbuf_addstr(&tmp, rel);
361
				strbuf_addch(&tmp, '/');
362

363
				if (!batch)
364
					batch = fsmonitor_batch__new();
365
				my_add_path(batch, tmp.buf);
366
			}
367

368
			break;
369

370
		case IS_OUTSIDE_CONE:
371
		default:
372
			trace_printf_key(&trace_fsmonitor,
373
					 "ignoring '%s'", path_k);
374
			break;
375
		}
376
	}
377

378
	free(resolved);
379
	fsmonitor_publish(state, batch, &cookie_list);
380
	string_list_clear(&cookie_list, 0);
381
	strbuf_release(&tmp);
382
	return;
383

384
force_shutdown:
385
	free(resolved);
386
	fsmonitor_batch__free_list(batch);
387
	string_list_clear(&cookie_list, 0);
388

389
	pthread_mutex_lock(&data->dq_lock);
390
	data->shutdown_style = FORCE_SHUTDOWN;
391
	pthread_cond_broadcast(&data->dq_finished);
392
	pthread_mutex_unlock(&data->dq_lock);
393

394
	strbuf_release(&tmp);
395
	return;
396
}
397

398
/*
399
 * In the call to `FSEventStreamCreate()` to setup our watch, the
400
 * `latency` argument determines the frequency of calls to our callback
401
 * with new FS events.  Too slow and events get dropped; too fast and
402
 * we burn CPU unnecessarily.  Since it is rather obscure, I don't
403
 * think this needs to be a config setting.  I've done extensive
404
 * testing on my systems and chosen the value below.  It gives good
405
 * results and I've not seen any dropped events.
406
 *
407
 * With a latency of 0.1, I was seeing lots of dropped events during
408
 * the "touch 100000" files test within t/perf/p7519, but with a
409
 * latency of 0.001 I did not see any dropped events.  So I'm going
410
 * to assume that this is the "correct" value.
411
 *
412
 * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
413
 */
414

415
int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
416
{
417
	FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagNoDefer |
418
		kFSEventStreamCreateFlagWatchRoot |
419
		kFSEventStreamCreateFlagFileEvents;
420
	FSEventStreamContext ctx = {
421
		0,
422
		state,
423
		NULL,
424
		NULL,
425
		NULL
426
	};
427
	struct fsm_listen_data *data;
428
	const void *dir_array[2];
429

430
	CALLOC_ARRAY(data, 1);
431
	state->listen_data = data;
432

433
	data->cfsr_worktree_path = CFStringCreateWithCString(
434
		NULL, state->path_worktree_watch.buf, kCFStringEncodingUTF8);
435
	dir_array[data->nr_paths_watching++] = data->cfsr_worktree_path;
436

437
	if (state->nr_paths_watching > 1) {
438
		data->cfsr_gitdir_path = CFStringCreateWithCString(
439
			NULL, state->path_gitdir_watch.buf,
440
			kCFStringEncodingUTF8);
441
		dir_array[data->nr_paths_watching++] = data->cfsr_gitdir_path;
442
	}
443

444
	data->cfar_paths_to_watch = CFArrayCreate(NULL, dir_array,
445
						  data->nr_paths_watching,
446
						  NULL);
447
	data->stream = FSEventStreamCreate(NULL, fsevent_callback, &ctx,
448
					   data->cfar_paths_to_watch,
449
					   kFSEventStreamEventIdSinceNow,
450
					   0.001, flags);
451
	if (!data->stream)
452
		goto failed;
453

454
	return 0;
455

456
failed:
457
	error(_("Unable to create FSEventStream."));
458

459
	FREE_AND_NULL(state->listen_data);
460
	return -1;
461
}
462

463
void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
464
{
465
	struct fsm_listen_data *data;
466

467
	if (!state || !state->listen_data)
468
		return;
469

470
	data = state->listen_data;
471

472
	if (data->stream) {
473
		if (data->stream_started)
474
			FSEventStreamStop(data->stream);
475
		if (data->stream_scheduled)
476
			FSEventStreamInvalidate(data->stream);
477
		FSEventStreamRelease(data->stream);
478
	}
479

480
	if (data->dq)
481
		dispatch_release(data->dq);
482
	pthread_cond_destroy(&data->dq_finished);
483
	pthread_mutex_destroy(&data->dq_lock);
484

485
	FREE_AND_NULL(state->listen_data);
486
}
487

488
void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
489
{
490
	struct fsm_listen_data *data;
491

492
	data = state->listen_data;
493

494
	pthread_mutex_lock(&data->dq_lock);
495
	data->shutdown_style = SHUTDOWN_EVENT;
496
	pthread_cond_broadcast(&data->dq_finished);
497
	pthread_mutex_unlock(&data->dq_lock);
498
}
499

500
void fsm_listen__loop(struct fsmonitor_daemon_state *state)
501
{
502
	struct fsm_listen_data *data;
503

504
	data = state->listen_data;
505

506
	pthread_mutex_init(&data->dq_lock, NULL);
507
	pthread_cond_init(&data->dq_finished, NULL);
508
	data->dq = dispatch_queue_create("FSMonitor", NULL);
509

510
	FSEventStreamSetDispatchQueue(data->stream, data->dq);
511
	data->stream_scheduled = 1;
512

513
	if (!FSEventStreamStart(data->stream)) {
514
		error(_("Failed to start the FSEventStream"));
515
		goto force_error_stop_without_loop;
516
	}
517
	data->stream_started = 1;
518

519
	pthread_mutex_lock(&data->dq_lock);
520
	pthread_cond_wait(&data->dq_finished, &data->dq_lock);
521
	pthread_mutex_unlock(&data->dq_lock);
522

523
	switch (data->shutdown_style) {
524
	case FORCE_ERROR_STOP:
525
		state->listen_error_code = -1;
526
		/* fall thru */
527
	case FORCE_SHUTDOWN:
528
		ipc_server_stop_async(state->ipc_server_data);
529
		/* fall thru */
530
	case SHUTDOWN_EVENT:
531
	default:
532
		break;
533
	}
534
	return;
535

536
force_error_stop_without_loop:
537
	state->listen_error_code = -1;
538
	ipc_server_stop_async(state->ipc_server_data);
539
	return;
540
}
541

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

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

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

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