git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v4 3/6] fsmonitor: implement filesystem change listener for Linux

From
Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Date
Dec 12, 2022, 10:42 UTC
Message-ID
<221212.86h6y06gca.gmgdl@evledraar.gmail.com>
In-Reply-To
<80282efef5779728022a317388da6790e5db44e2.1669230044.git.gitgitgadget@gmail.com>
On Wed, Nov 23 2022, Eric DeCosta via GitGitGadget wrote:
Show 16 quoted lines
> From: Eric DeCosta <edecosta@mathworks.com>
> [...]
> +}
> +
> +/*
> + * Remove the inotify watch, the watch descriptor to path mapping
> + * and the reverse mapping.
> + */
> +static void remove_watch(struct watch_entry *w,
> +	struct fsm_listen_data *data)
> +{
> +	struct watch_entry k1, k2, *w1, *w2;
> +
> +	/* remove watch, ignore error if kernel already did it */
> +	if (inotify_rm_watch(data->fd_inotify, w->wd) && errno != EINVAL)
> +		error_errno("inotify_rm_watch() failed");
If we error_errno(), shouldn't this function have a return value?
Show 13 quoted lines
> +
> +	hashmap_entry_init(&k1.ent, memhash(&w->wd, sizeof(int)));
> +	w1 = hashmap_remove_entry(&data->watches, &k1, ent, NULL);
> +	if (!w1)
> +		BUG("Double remove of watch for '%s'", w->dir);
> +
> +	if (w1->cookie)
> +		BUG("Removing watch for '%s' which has a pending rename", w1->dir);
> +
> +	hashmap_entry_init(&k2.ent, memhash(w->dir, strlen(w->dir)));
> +	w2 = hashmap_remove_entry(&data->revwatches, &k2, ent, NULL);
> +	if (!w2)
> +		BUG("Double remove of reverse watch for '%s'", w->dir);

For the BUG() additions: Start with lower-case in messages, see CodingGuidelines. I.e. "double remove of ..." etc. Ditto below..

Show 25 quoted lines
> [...]
> +/*
> + * Handle directory renames
> + *
> + * Once a IN_MOVED_TO event is received, lookup the rename tracking information
> + * via the event cookie and use this information to update the watch.
> + */
> +static void rename_dir(uint32_t cookie, const char *path,
> +	struct fsm_listen_data *data)
> +{
> +	struct rename_entry rek, *re;
> +	struct watch_entry k, *w;
> +
> +	/* lookup a pending rename to match */
> +	rek.cookie = cookie;
> +	hashmap_entry_init(&rek.ent, memhash(&rek.cookie, sizeof(uint32_t)));
> +	re = hashmap_get_entry(&data->renames, &rek, ent, NULL);
> +	if (re) {
> +		k.dir = re->dir;
> +		hashmap_entry_init(&k.ent, memhash(k.dir, strlen(k.dir)));
> +		w = hashmap_get_entry(&data->revwatches, &k, ent, NULL);
> +		if (w) {
> +			w->cookie = 0; /* rename handled */
> +			remove_watch(w, data);
> +			add_watch(path, data);
Elsewhere you check the add_watch() return value, but not here?
Show 27 quoted lines
> +		} else {
> +			BUG("No matching watch");
> +		}
> +	} else {
> +		BUG("No matching cookie");
> +	}
> +}
> +
> +/*
> + * Recursively add watches to every directory under path
> + */
> +static int register_inotify(const char *path,
> +	struct fsmonitor_daemon_state *state,
> +	struct fsmonitor_batch *batch)
> +{
> +	DIR *dir;
> +	const char *rel;
> +	struct strbuf current = STRBUF_INIT;
> +	struct dirent *de;
> +	struct stat fs;
> +	int ret = -1;
> +
> +	dir = opendir(path);
> +	if (!dir)
> +		return error_errno("opendir('%s') failed", path);
> +
> +	while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {
ditto drop the "!= NULL"
Show 34 quoted lines
> +	struct strbuf msg = STRBUF_INIT;
> +
> +	if (mask & IN_ACCESS)
> +		strbuf_addstr(&msg, "IN_ACCESS|");
> +	if (mask & IN_MODIFY)
> +		strbuf_addstr(&msg, "IN_MODIFY|");
> +	if (mask & IN_ATTRIB)
> +		strbuf_addstr(&msg, "IN_ATTRIB|");
> +	if (mask & IN_CLOSE_WRITE)
> +		strbuf_addstr(&msg, "IN_CLOSE_WRITE|");
> +	if (mask & IN_CLOSE_NOWRITE)
> +		strbuf_addstr(&msg, "IN_CLOSE_NOWRITE|");
> +	if (mask & IN_OPEN)
> +		strbuf_addstr(&msg, "IN_OPEN|");
> +	if (mask & IN_MOVED_FROM)
> +		strbuf_addstr(&msg, "IN_MOVED_FROM|");
> +	if (mask & IN_MOVED_TO)
> +		strbuf_addstr(&msg, "IN_MOVED_TO|");
> +	if (mask & IN_CREATE)
> +		strbuf_addstr(&msg, "IN_CREATE|");
> +	if (mask & IN_DELETE)
> +		strbuf_addstr(&msg, "IN_DELETE|");
> +	if (mask & IN_DELETE_SELF)
> +		strbuf_addstr(&msg, "IN_DELETE_SELF|");
> +	if (mask & IN_MOVE_SELF)
> +		strbuf_addstr(&msg, "IN_MOVE_SELF|");
> +	if (mask & IN_UNMOUNT)
> +		strbuf_addstr(&msg, "IN_UNMOUNT|");
> +	if (mask & IN_Q_OVERFLOW)
> +		strbuf_addstr(&msg, "IN_Q_OVERFLOW|");
> +	if (mask & IN_IGNORED)
> +		strbuf_addstr(&msg, "IN_IGNORED|");
> +	if (mask & IN_ISDIR)
> +		strbuf_addstr(&msg, "IN_ISDIR|");
Shouldn't the very last addition to mask omit the "|"?
I think it would be worth just making this a NULL-delimited "int, const char *" array like:
	{
		{ IN_ACCESS, "IN_ACCESS" },
		...
		NULL,
	};
Then looping over it, or maybe not...
Show 20 quoted lines
> +	trace_printf_key(&trace_fsmonitor, "inotify_event: '%s', mask=%#8.8x %s",
> +				path, mask, msg.buf);
> +
> +	strbuf_release(&msg);
> +}
> +
> +int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
> +{
> +	int fd;
> +	int ret = 0;
> +	struct fsm_listen_data *data;
> +
> +	CALLOC_ARRAY(data, 1);
> +	state->listen_data = data;
> +	state->listen_error_code = -1;
> +	data->shutdown = SHUTDOWN_ERROR;
> +
> +	fd = inotify_init1(O_NONBLOCK);
> +	if (fd < 0)
> +		return error_errno("inotify_init1() failed");
Here you leak the "data" you just allocated on error.
Show 8 quoted lines
> +
> +	data->fd_inotify = fd;
> +
> +	hashmap_init(&data->watches, watch_entry_cmp, NULL, 0);
> +	hashmap_init(&data->renames, rename_entry_cmp, NULL, 0);
> +	hashmap_init(&data->revwatches, revwatches_entry_cmp, NULL, 0);
> +
> +	if (add_watch(state->path_worktree_watch.buf, data))

I.e. we should only avoid free()-ing it if we can successfully hand it over to add_watch(), shouldn't we?n

> +		ret = -1;
> +	else if (register_inotify(state->path_worktree_watch.buf, state, NULL))
> +		ret = -1;
We add {}'s to all if/else if branches if one needs it, see CodingGuidelines.
Show 5 quoted lines
> +	else if (state->nr_paths_watching > 1) {
> +		if (add_watch(state->path_gitdir_watch.buf, data))
> +			ret = -1;
> +		else if (register_inotify(state->path_gitdir_watch.buf, state, NULL))
> +			ret = -1;
Can't this be:
	else if (state->nr_paths_watching > 1 &&
		 (add_watch(...) || register_inotify(...)))
		ret = -1;
> +	switch (fsmonitor_classify_path_absolute(state, path)) {
> +		case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:

Don't indent the "case" label here more than the "switch", see CodingGuidelines.

> +static void handle_events(struct fsmonitor_daemon_state *state)
> +{
> +	 /* See https://man7.org/linux/man-pages/man7/inotify.7.html */
Extra " " here after the "\t".
Show 14 quoted lines
> +	char buf[4096]
> +		__attribute__ ((aligned(__alignof__(struct inotify_event))));
> +
> +	struct hashmap watches = state->listen_data->watches;
> +	struct fsmonitor_batch *batch = NULL;
> +	struct string_list cookie_list = STRING_LIST_INIT_DUP;
> +	struct watch_entry k, *w;
> +	struct strbuf path;
> +	const struct inotify_event *event;
> +	int fd = state->listen_data->fd_inotify;
> +	ssize_t len;
> +	char *ptr, *p;
> +
> +	strbuf_init(&path, PATH_MAX);
Just use "struct strbuf path = STRBUF_INIT" instead? I.e...
Show 47 quoted lines
> +
> +	for(;;) {
> +		len = read(fd, buf, sizeof(buf));
> +		if (len == -1 && errno != EAGAIN) {
> +			error_errno(_("reading inotify message stream failed"));
> +			state->listen_data->shutdown = SHUTDOWN_ERROR;
> +			goto done;
> +		}
> +
> +		/* nothing to read */
> +		if (len <= 0)
> +			goto done;
> +
> +		/* Loop over all events in the buffer. */
> +		for (ptr = buf; ptr < buf + len;
> +			 ptr += sizeof(struct inotify_event) + event->len) {
> +
> +			event = (const struct inotify_event *) ptr;
> +
> +			if (em_ignore(event->mask))
> +				continue;
> +
> +			/* File system was unmounted or event queue overflowed */
> +			if (em_force_shutdown(event->mask)) {
> +				if (trace_pass_fl(&trace_fsmonitor))
> +					log_mask_set("Forcing shutdown", event->mask);
> +				state->listen_data->shutdown = SHUTDOWN_FORCE;
> +				goto done;
> +			}
> +
> +			hashmap_entry_init(&k.ent, memhash(&event->wd, sizeof(int)));
> +			k.wd = event->wd;
> +
> +			w = hashmap_get_entry(&watches, &k, ent, NULL);
> +			if (!w) /* should never happen */
> +				BUG("No watch for '%s'", event->name);
> +
> +			/* directory watch was removed */
> +			if (em_remove_watch(event->mask)) {
> +				remove_watch(w, state->listen_data);
> +				continue;
> +			}
> +
> +			strbuf_reset(&path);
> +			strbuf_add(&path, w->dir, strlen(w->dir));
> +			strbuf_addch(&path, '/');
> +			strbuf_addstr(&path, event->name);

... we may not even get to this, so we may pointlessly pre-grow it, if we're considering the micro-optimization.

But if we do need it it'll quickly get up to size in the loop, which is probably smaller than PATH_MAX, and paths can exceed PATH_MAX....

Show 14 quoted lines
> +	for(;;) {
> +		switch (state->listen_data->shutdown) {
> +			case SHUTDOWN_CONTINUE:
> +				poll_num = poll(fds, 1, 1);
> +				if (poll_num == -1) {
> +					if (errno == EINTR)
> +						continue;
> +					error_errno(_("polling inotify message stream failed"));
> +					state->listen_data->shutdown = SHUTDOWN_ERROR;
> +					continue;
> +				}
> +
> +				if ((time(NULL) - checked) >= interval) {
> +					checked = time(NULL);

As this is linux-specific code, shouldn't we use the linux-specific API to get the guaranteed atomically growing time here, arther than time()?

Maybe not, but I wonder if this has funny side-effects with ntpd adjusting the time concurrently...

Previous: Eric DeCosta via GitGitGadgetNext: Eric DeCosta via GitGitGadget
Message 77 of 89 in “fsmonitor: Implement fsmonitor for Linux”
  1. 00/12 fsmonitor: Implement fsmonitor for LinuxEric DeCosta via GitGitGadget, Oct 9, 2022
  2. 02/12 fsmonitor: relocate socket file if .git directory is remoteEric DeCosta via GitGitGadget, Oct 9, 2022
  3. 04/12 fsmonitor: deal with synthetic firmlinks on macOSEric DeCosta via GitGitGadget, Oct 9, 2022
  4. 03/12 fsmonitor: avoid socket location check if using hookEric DeCosta via GitGitGadget, Oct 9, 2022
  5. 05/12 fsmonitor: check for compatability before communicating with fsmonitorEric DeCosta via GitGitGadget, Oct 9, 2022
  6. 01/12 fsmonitor: refactor filesystem checks to common interfaceEric DeCosta via GitGitGadget, Oct 9, 2022
  7. Ævar Arnfjörð BjarmasonOct 18, 2022
  8. 06/12 fsmonitor: add documentation for allowRemote and socketDir optionsEric DeCosta via GitGitGadget, Oct 9, 2022
  9. 07/12 fsmonitor: prepare to share code between Mac OS and LinuxEric DeCosta via GitGitGadget, Oct 9, 2022
  10. Junio C HamanoOct 9, 2022
  11. Jeff HostetlerOct 10, 2022
  12. 08/12 fsmonitor: determine if filesystem is local or remoteEric DeCosta via GitGitGadget, Oct 9, 2022
  13. Ævar Arnfjörð BjarmasonOct 10, 2022
  14. Eric DeCostaOct 14, 2022
  15. 09/12 fsmonitor: implement filesystem change listener for LinuxEric DeCosta via GitGitGadget, Oct 9, 2022
  16. 10/12 fsmonitor: enable fsmonitor for LinuxEric DeCosta via GitGitGadget, Oct 9, 2022
  17. 11/12 fsmonitor: test updatesEric DeCosta via GitGitGadget, Oct 9, 2022
  18. Ævar Arnfjörð BjarmasonOct 18, 2022
  19. 12/12 fsmonitor: update doc for LinuxEric DeCosta via GitGitGadget, Oct 9, 2022
  20. Ævar Arnfjörð BjarmasonOct 18, 2022
  21. Junio C HamanoOct 9, 2022
  22. Eric SunshineOct 10, 2022
  23. Junio C HamanoOct 10, 2022
  24. 00/12 fsmonitor: Implement fsmonitor for LinuxEric DeCosta via GitGitGadget, Oct 14, 2022
  25. 01/12 fsmonitor: refactor filesystem checks to common interfaceEric DeCosta via GitGitGadget, Oct 14, 2022
  26. 02/12 fsmonitor: relocate socket file if .git directory is remoteEric DeCosta via GitGitGadget, Oct 14, 2022
  27. Ævar Arnfjörð BjarmasonOct 18, 2022
  28. 05/12 fsmonitor: check for compatability before communicating with fsmonitorEric DeCosta via GitGitGadget, Oct 14, 2022
  29. 03/12 fsmonitor: avoid socket location check if using hookEric DeCosta via GitGitGadget, Oct 14, 2022
  30. 04/12 fsmonitor: deal with synthetic firmlinks on macOSEric DeCosta via GitGitGadget, Oct 14, 2022
  31. 06/12 fsmonitor: add documentation for allowRemote and socketDir optionsEric DeCosta via GitGitGadget, Oct 14, 2022
  32. 07/12 fsmonitor: prepare to share code between Mac OS and LinuxEric DeCosta via GitGitGadget, Oct 14, 2022
  33. Junio C HamanoOct 14, 2022
  34. Eric DeCostaOct 17, 2022
  35. Junio C HamanoOct 18, 2022
  36. 10/12 fsmonitor: enable fsmonitor for LinuxEric DeCosta via GitGitGadget, Oct 14, 2022
  37. 11/12 fsmonitor: test updatesEric DeCosta via GitGitGadget, Oct 14, 2022
  38. 09/12 fsmonitor: implement filesystem change listener for LinuxEric DeCosta via GitGitGadget, Oct 14, 2022
  39. Ævar Arnfjörð BjarmasonOct 18, 2022
  40. 08/12 fsmonitor: determine if filesystem is local or remoteEric DeCosta via GitGitGadget, Oct 14, 2022
  41. 12/12 fsmonitor: update doc for LinuxEric DeCosta via GitGitGadget, Oct 14, 2022
  42. Junio C HamanoOct 14, 2022
  43. Eric DeCostaOct 17, 2022
  44. Junio C HamanoOct 17, 2022
  45. Johannes SchindelinOct 18, 2022
  46. Glen ChooOct 17, 2022
  47. Junio C HamanoOct 18, 2022
  48. Glen ChooOct 18, 2022
  49. Junio C HamanoOct 18, 2022
  50. Ævar Arnfjörð BjarmasonOct 19, 2022
  51. Eric SunshineOct 19, 2022
  52. Junio C HamanoOct 19, 2022
  53. Ævar Arnfjörð BjarmasonOct 19, 2022
  54. Eric SunshineOct 19, 2022
  55. Ævar Arnfjörð BjarmasonOct 19, 2022
  56. Junio C HamanoOct 19, 2022
  57. Junio C HamanoOct 20, 2022
  58. Eric SunshineOct 20, 2022
  59. Junio C HamanoOct 20, 2022
  60. Eric SunshineOct 20, 2022
  61. Junio C HamanoOct 20, 2022
  62. Junio C HamanoOct 20, 2022
  63. 0/6 fsmonitor: Implement fsmonitor for LinuxEric DeCosta via GitGitGadget, Nov 16, 2022
  64. 1/6 fsmonitor: prepare to share code between Mac OS and LinuxEric DeCosta via GitGitGadget, Nov 16, 2022
  65. 2/6 fsmonitor: determine if filesystem is local or remoteEric DeCosta via GitGitGadget, Nov 16, 2022
  66. 3/6 fsmonitor: implement filesystem change listener for LinuxEric DeCosta via GitGitGadget, Nov 16, 2022
  67. 4/6 fsmonitor: enable fsmonitor for LinuxEric DeCosta via GitGitGadget, Nov 16, 2022
  68. 5/6 fsmonitor: test updatesEric DeCosta via GitGitGadget, Nov 16, 2022
  69. 6/6 fsmonitor: update doc for LinuxEric DeCosta via GitGitGadget, Nov 16, 2022
  70. Taylor BlauNov 16, 2022
  71. 0/6 fsmonitor: Implement fsmonitor for LinuxEric DeCosta via GitGitGadget, Nov 23, 2022
  72. 1/6 fsmonitor: prepare to share code between Mac OS and LinuxEric DeCosta via GitGitGadget, Nov 23, 2022
  73. 2/6 fsmonitor: determine if filesystem is local or remoteEric DeCosta via GitGitGadget, Nov 23, 2022
  74. Junio C HamanoNov 25, 2022
  75. Ævar Arnfjörð BjarmasonDec 12, 2022
  76. 3/6 fsmonitor: implement filesystem change listener for LinuxEric DeCosta via GitGitGadget, Nov 23, 2022
  77. Ævar Arnfjörð BjarmasonDec 12, 2022
  78. 4/6 fsmonitor: enable fsmonitor for LinuxEric DeCosta via GitGitGadget, Nov 23, 2022
  79. 5/6 fsmonitor: test updatesEric DeCosta via GitGitGadget, Nov 23, 2022
  80. 6/6 fsmonitor: update doc for LinuxEric DeCosta via GitGitGadget, Nov 23, 2022
  81. 0/6 fsmonitor: Implement fsmonitor for LinuxEric DeCosta via GitGitGadget, Dec 12, 2022
  82. 1/6 fsmonitor: prepare to share code between Mac OS and LinuxEric DeCosta via GitGitGadget, Dec 12, 2022
  83. 2/6 fsmonitor: determine if filesystem is local or remoteEric DeCosta via GitGitGadget, Dec 12, 2022
  84. 4/6 fsmonitor: enable fsmonitor for LinuxEric DeCosta via GitGitGadget, Dec 12, 2022
  85. 3/6 fsmonitor: implement filesystem change listener for LinuxEric DeCosta via GitGitGadget, Dec 12, 2022
  86. 5/6 fsmonitor: test updatesEric DeCosta via GitGitGadget, Dec 12, 2022
  87. 6/6 fsmonitor: update doc for LinuxEric DeCosta via GitGitGadget, Dec 12, 2022
  88. Junio C HamanoApr 12, 2023
  89. Ævar Arnfjörð BjarmasonOct 18, 2022

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.