Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fsmonitor: completing a stale patch that Implements fsmonitor for Linux #1667

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

maryis
Copy link

@maryis maryis commented Feb 5, 2024

The goal in the original pull is to deliver fsmonitor for Linux that works similar to fsmonitor for Windows and Mac OS.

The original stale patch : #1352

This patch set builds upon previous work for done for Windows and Mac OS to implement a fsmonitor back-end for Linux based on the Linux inotify API. inotify differs significantly from the equivalent Windows and Mac OS APIs in that a watch must be registered for every directory of interest (rather than a singular watch at the root of the directory tree) and special care must be taken to handle directory renames correctly.

More information about inotify: https://man7.org/linux/man-pages/man7/inotify.7.html

cc: Eric Sunshine [email protected], Ævar Arnfjörð Bjarmason [email protected], Glen Choo [email protected], Johannes Schindelin [email protected], Taylor Blau [email protected]
cc: Jean-Noël Avila [email protected]
cc: Patrick Steinhardt [email protected]

Copy link

Welcome to GitGitGadget

Hi @maryis, and welcome to GitGitGadget, the GitHub App to send patch series to the Git mailing list from GitHub Pull Requests.

Please make sure that your Pull Request has a good description, as it will be used as cover letter. You can CC potential reviewers by adding a footer to the PR description with the following syntax:

CC: Revi Ewer <[email protected]>, Ill Takalook <[email protected]>

Also, it is a good idea to review the commit messages one last time, as the Git project expects them in a quite specific form:

  • the lines should not exceed 76 columns,
  • the first line should be like a header and typically start with a prefix like "tests:" or "revisions:" to state which subsystem the change is about, and
  • the commit messages' body should be describing the "why?" of the change.
  • Finally, the commit messages should end in a Signed-off-by: line matching the commits' author.

It is in general a good idea to await the automated test ("Checks") in this Pull Request before contributing the patches, e.g. to avoid trivial issues such as unportable code.

Contributing the patches

Before you can contribute the patches, your GitHub username needs to be added to the list of permitted users. Any already-permitted user can do that, by adding a comment to your PR of the form /allow. A good way to find other contributors is to locate recent pull requests where someone has been /allowed:

Both the person who commented /allow and the PR author are able to /allow you.

An alternative is the channel #git-devel on the Libera Chat IRC network:

<newcontributor> I've just created my first PR, could someone please /allow me? https://github.com/gitgitgadget/git/pull/12345
<veteran> newcontributor: it is done
<newcontributor> thanks!

Once on the list of permitted usernames, you can contribute the patches to the Git mailing list by adding a PR comment /submit.

If you want to see what email(s) would be sent for a /submit request, add a PR comment /preview to have the email(s) sent to you. You must have a public GitHub email address for this. Note that any reviewers CC'd via the list in the PR description will not actually be sent emails.

After you submit, GitGitGadget will respond with another comment that contains the link to the cover letter mail in the Git mailing list archive. Please make sure to monitor the discussion in that thread and to address comments and suggestions (while the comments and suggestions will be mirrored into the PR by GitGitGadget, you will still want to reply via mail).

If you do not want to subscribe to the Git mailing list just to be able to respond to a mail, you can download the mbox from the Git mailing list archive (click the (raw) link), then import it into your mail program. If you use GMail, you can do this via:

curl -g --user "<EMailAddress>:<Password>" \
    --url "imaps://imap.gmail.com/INBOX" -T /path/to/raw.txt

To iterate on your change, i.e. send a revised patch or patch series, you will first want to (force-)push to the same branch. You probably also want to modify your Pull Request description (or title). It is a good idea to summarize the revision by adding something like this to the cover letter (read: by editing the first comment on the PR, i.e. the PR description):

Changes since v1:
- Fixed a typo in the commit message (found by ...)
- Added a code comment to ... as suggested by ...
...

To send a new iteration, just add another PR comment with the contents: /submit.

Need help?

New contributors who want advice are encouraged to join [email protected], where volunteers who regularly contribute to Git are willing to answer newbie questions, give advice, or otherwise provide mentoring to interested contributors. You must join in order to post or view messages, but anyone can join.

You may also be able to find help in real time in the developer IRC channel, #git-devel on Libera Chat. Remember that IRC does not support offline messaging, so if you send someone a private message and log out, they cannot respond to you. The scrollback of #git-devel is archived, though.

Copy link

There is a merge commit in this Pull Request:

45293c1c953f83e9ed39404e77f2d6e3cf006da5

Please rebase the branch and force-push.

1 similar comment
Copy link

There is a merge commit in this Pull Request:

45293c1c953f83e9ed39404e77f2d6e3cf006da5

Please rebase the branch and force-push.

@maryis maryis changed the title rerolling a stale patch after addressing comments : fsmonitor: Implement fsmonitor for Linux fsmonitor: completing a stale path that Implements fsmonitor for Linux Feb 5, 2024
@dscho
Copy link
Member

dscho commented Feb 5, 2024

/allow

@dscho
Copy link
Member

dscho commented Feb 5, 2024

Please rebase the branch and force-push.

@maryis please do...

Copy link

User maryis is now allowed to use GitGitGadget.

WARNING: maryis has no public email address set on GitHub;
GitGitGadget needs an email address to Cc: you on your contribution, so that you receive any feedback on the Git mailing list. Go to https://github.com/settings/profile to make your preferred email public to let GitGitGadget know which email address to use.

@maryis maryis changed the title fsmonitor: completing a stale path that Implements fsmonitor for Linux fsmonitor: completing a stale patch that Implements fsmonitor for Linux Feb 6, 2024
Copy link

There are issues in commit 791cb6c:
rebase
Commit checks stopped - the message is too short
Commit not signed off

@maryis maryis force-pushed the fsmonitor_linux branch 2 times, most recently from ac2c20f to 12f1b95 Compare February 6, 2024 21:39
Copy link

There are issues in commit 4878ace:
rebase
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit 4b9e4b9:
fsmonitor: update doc for Linux
Commit not signed off

Copy link

There are issues in commit b698cbd:
rebase done
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit 01fe79c:
resolve merge conflicts, address pull-1352 review comments
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit 12f1b95:
resolve rebase conflicts
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit f2d6291:
fsmonitor: rebase with master
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit 214f4d7:
addressed comments for patch 1352
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit f2d6291:
fsmonitor: rebase with master
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit 214f4d7:
addressed comments for patch 1352
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit b353f4f:
added explicit import to fsm-listen-linux file
Commit checks stopped - the message is too short
Commit not signed off

@maryis maryis force-pushed the fsmonitor_linux branch 2 times, most recently from 798b89e to 8af15de Compare February 10, 2024 17:16
Copy link

There are issues in commit 78f2ff1:
fsmonitor: rebase with master
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit b25893b:
addressed comments for patch 1352
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit 8af15de:
added explicit import to fsm-listen-linux file
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit 78f2ff1:
fsmonitor: rebase with master
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit dfe3aea:
fsmonitor:
The first line must be separated from the rest by an empty line

Copy link

There are issues in commit 78f2ff1:
fsmonitor: rebase with master
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit 78f2ff1:
fsmonitor: rebase with master
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit 70480a1:
fsmonitor: rebase with master
Commit checks stopped - the message is too short
Commit not signed off

Copy link

There are issues in commit c8367f5:
fsmonitor: rebase with master
Commit not signed off

edecosta-mw and others added 7 commits February 15, 2024 09:08
rebased with master, and resolved conflicts
Signed-off-by: Eric DeCosta <[email protected]>
Compare the given path to the mounted filesystems. Find the mount that is
the longest prefix of the path (if any) and determine if that mount is on a
local or remote filesystem.

Signed-off-by: Eric DeCosta <[email protected]>
Implement a filesystem change listener for Linux based on the inotify API:
https://man7.org/linux/man-pages/man7/inotify.7.html

inotify requires registering a watch on every directory in the worktree and
special handling of moves/renames.

Signed-off-by: Eric DeCosta <[email protected]>
Update build to enable fsmonitor for Linux.

Signed-off-by: Eric DeCosta <[email protected]>
t7527-builtin-fsmonitor was leaking fsmonitor--daemon processes in some
cases.

Accomodate slight difference in the number of events generated on Linux.

On lower-powered systems, spin a little to give the daemon time
to respond to and log filesystem events.

Signed-off-by: Eric DeCosta <[email protected]>
Update the documentation for Linux.

Signed-off-by: Eric DeCosta <[email protected]>
addressed comments on 1352, rebased, resolved conflicts

Signed-off-by: Marzieh Esipreh <[email protected]>
@maryis maryis marked this pull request as ready for review February 15, 2024 09:12
@maryis
Copy link
Author

maryis commented Feb 15, 2024

/submit

Copy link

Submitted as [email protected]

To fetch this version into FETCH_HEAD:

git fetch https://github.com/gitgitgadget/git/ pr-git-1667/maryis/fsmonitor_linux-v1

To fetch this version to local tag pr-git-1667/maryis/fsmonitor_linux-v1:

git fetch --no-tags https://github.com/gitgitgadget/git/ tag pr-git-1667/maryis/fsmonitor_linux-v1

@@ -2088,6 +2088,10 @@ ifdef HAVE_CLOCK_GETTIME
BASIC_CFLAGS += -DHAVE_CLOCK_GETTIME

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Jean-Noël Avila wrote (reply to this):

Hello,

Le 15/02/2024 à 11:29, Eric DeCosta via GitGitGadget a écrit :
> From: Eric DeCosta <[email protected]>
> > Compare the given path to the mounted filesystems. Find the mount that is
> the longest prefix of the path (if any) and determine if that mount is on a
> local or remote filesystem.
> > Signed-off-by: Eric DeCosta <[email protected]>
> ---
>   Makefile                                |   4 +
>   compat/fsmonitor/fsm-path-utils-linux.c | 195 ++++++++++++++++++++++++
>   compat/fsmonitor/fsm-path-utils-linux.h |  91 +++++++++++
>   config.mak.uname                        |  11 ++
>   4 files changed, 301 insertions(+)
>   create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c
>   create mode 100644 compat/fsmonitor/fsm-path-utils-linux.h
> > diff --git a/Makefile b/Makefile
> index 78e874099d9..0f36a0fd83a 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -2088,6 +2088,10 @@ ifdef HAVE_CLOCK_GETTIME
>   	BASIC_CFLAGS += -DHAVE_CLOCK_GETTIME
>   endif
>   > +ifdef HAVE_LINUX_MAGIC_H
> +	BASIC_CFLAGS += -DHAVE_LINUX_MAGIC_H
> +endif
> +
>   ifdef HAVE_CLOCK_MONOTONIC
>   	BASIC_CFLAGS += -DHAVE_CLOCK_MONOTONIC
>   endif
> diff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c
> new file mode 100644
> index 00000000000..c21d1349532
> --- /dev/null
> +++ b/compat/fsmonitor/fsm-path-utils-linux.c
> @@ -0,0 +1,195 @@
> +#include "git-compat-util.h"
> +#include "abspath.h"
> +#include "fsmonitor.h"
> +#include "fsmonitor-path-utils.h"
> +#include "fsm-path-utils-linux.h"
> +#include <errno.h>
> +#include <mntent.h>
> +#include <sys/mount.h>
> +#include <sys/vfs.h>
> +#include <sys/statvfs.h>
> +
> +static int is_remote_fs(const char *path)
> +{
> +	struct statfs fs;
> +
> +	if (statfs(path, &fs))
> +		return error_errno(_("statfs('%s') failed"), path);

For the sake of simplifying of the work of translators, would it be wise to change this to

+	if (statfs(path, &fs))
+		/* TRANSLATORS: %s('%s') is a libc function call */
+		return error_errno(_("%s('%s') failed"), "statfs", +				path);

and generalize this to all other messages?

Thanks,

JN

Copy link

User Jean-Noël Avila <[email protected]> has been added to the cc: list.

@@ -0,0 +1,24 @@
#include "cache.h"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Patrick Steinhardt wrote (reply to this):

On Thu, Feb 15, 2024 at 10:29:32AM +0000, Eric DeCosta via GitGitGadget wrote:
> From: Eric DeCosta <[email protected]>
> 
> rebased with master, and resolved conflicts

It would be a lot more useful if you adopted the original phrasing of
the commit:

```
fsmonitor: prepare to share code between Mac OS and Linux 

Linux and Mac OS can share some of the code originally developed for Mac OS.

Mac OS and Linux can share fsm-ipc-unix.c and fsm-settings-unix.c

Signed-off-by: Eric DeCosta <[email protected]>
```

Depending on whether or not you have made significant changes during the
rebase I'd also convert the trailers to:

Patch-originally-by: Eric DeCoste <[email protected]>
Signed-off-by: Marzieh Esipreh <[email protected]>

Furthermore, I think it would be useful if this commit was split up even
further than it already is. Having a preparatory patch that moves around
shareable code is a different topic than introducing the code skeleton
for Linux support, and as far as I can see the shared code does not end
up requiring anything from the new "*-linux.c" files.

Patrick

> Signed-off-by: Eric DeCosta <[email protected]>
> ---
>  compat/fsmonitor/fsm-health-linux.c    | 24 ++++++++++
>  compat/fsmonitor/fsm-ipc-darwin.c      | 57 +----------------------
>  compat/fsmonitor/fsm-ipc-linux.c       |  1 +
>  compat/fsmonitor/fsm-ipc-unix.c        | 53 +++++++++++++++++++++
>  compat/fsmonitor/fsm-settings-darwin.c | 64 +-------------------------
>  compat/fsmonitor/fsm-settings-linux.c  |  1 +
>  compat/fsmonitor/fsm-settings-unix.c   | 61 ++++++++++++++++++++++++
>  7 files changed, 142 insertions(+), 119 deletions(-)
>  create mode 100644 compat/fsmonitor/fsm-health-linux.c
>  create mode 100644 compat/fsmonitor/fsm-ipc-linux.c
>  create mode 100644 compat/fsmonitor/fsm-ipc-unix.c
>  create mode 100644 compat/fsmonitor/fsm-settings-linux.c
>  create mode 100644 compat/fsmonitor/fsm-settings-unix.c
> 
> diff --git a/compat/fsmonitor/fsm-health-linux.c b/compat/fsmonitor/fsm-health-linux.c
> new file mode 100644
> index 00000000000..b9f709e8548
> --- /dev/null
> +++ b/compat/fsmonitor/fsm-health-linux.c
> @@ -0,0 +1,24 @@
> +#include "cache.h"
> +#include "config.h"
> +#include "fsmonitor.h"
> +#include "fsm-health.h"
> +#include "fsmonitor--daemon.h"
> +
> +int fsm_health__ctor(struct fsmonitor_daemon_state *state)
> +{
> +	return 0;
> +}
> +
> +void fsm_health__dtor(struct fsmonitor_daemon_state *state)
> +{
> +	return;
> +}
> +
> +void fsm_health__loop(struct fsmonitor_daemon_state *state)
> +{
> +	return;
> +}
> +
> +void fsm_health__stop_async(struct fsmonitor_daemon_state *state)
> +{
> +}
> diff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-darwin.c
> index 6f3a95410cc..4c3c92081ee 100644
> --- a/compat/fsmonitor/fsm-ipc-darwin.c
> +++ b/compat/fsmonitor/fsm-ipc-darwin.c
> @@ -1,56 +1 @@
> -#include "git-compat-util.h"
> -#include "config.h"
> -#include "gettext.h"
> -#include "hex.h"
> -#include "path.h"
> -#include "repository.h"
> -#include "strbuf.h"
> -#include "fsmonitor-ll.h"
> -#include "fsmonitor-ipc.h"
> -#include "fsmonitor-path-utils.h"
> -
> -static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, "fsmonitor--daemon.ipc")
> -
> -const char *fsmonitor_ipc__get_path(struct repository *r)
> -{
> -	static const char *ipc_path = NULL;
> -	git_SHA_CTX sha1ctx;
> -	char *sock_dir = NULL;
> -	struct strbuf ipc_file = STRBUF_INIT;
> -	unsigned char hash[GIT_MAX_RAWSZ];
> -
> -	if (!r)
> -		BUG("No repository passed into fsmonitor_ipc__get_path");
> -
> -	if (ipc_path)
> -		return ipc_path;
> -
> -
> -	/* By default the socket file is created in the .git directory */
> -	if (fsmonitor__is_fs_remote(r->gitdir) < 1) {
> -		ipc_path = fsmonitor_ipc__get_default_path();
> -		return ipc_path;
> -	}
> -
> -	git_SHA1_Init(&sha1ctx);
> -	git_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));
> -	git_SHA1_Final(hash, &sha1ctx);
> -
> -	repo_config_get_string(r, "fsmonitor.socketdir", &sock_dir);
> -
> -	/* Create the socket file in either socketDir or $HOME */
> -	if (sock_dir && *sock_dir) {
> -		strbuf_addf(&ipc_file, "%s/.git-fsmonitor-%s",
> -					sock_dir, hash_to_hex(hash));
> -	} else {
> -		strbuf_addf(&ipc_file, "~/.git-fsmonitor-%s", hash_to_hex(hash));
> -	}
> -	free(sock_dir);
> -
> -	ipc_path = interpolate_path(ipc_file.buf, 1);
> -	if (!ipc_path)
> -		die(_("Invalid path: %s"), ipc_file.buf);
> -
> -	strbuf_release(&ipc_file);
> -	return ipc_path;
> -}
> +#include "fsm-ipc-unix.c"
> diff --git a/compat/fsmonitor/fsm-ipc-linux.c b/compat/fsmonitor/fsm-ipc-linux.c
> new file mode 100644
> index 00000000000..4c3c92081ee
> --- /dev/null
> +++ b/compat/fsmonitor/fsm-ipc-linux.c
> @@ -0,0 +1 @@
> +#include "fsm-ipc-unix.c"
> diff --git a/compat/fsmonitor/fsm-ipc-unix.c b/compat/fsmonitor/fsm-ipc-unix.c
> new file mode 100644
> index 00000000000..eb25123fa12
> --- /dev/null
> +++ b/compat/fsmonitor/fsm-ipc-unix.c
> @@ -0,0 +1,53 @@
> +#include "cache.h"
> +#include "config.h"
> +#include "hex.h"
> +#include "strbuf.h"
> +#include "fsmonitor.h"
> +#include "fsmonitor-ipc.h"
> +#include "fsmonitor-path-utils.h"
> +
> +static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, "fsmonitor--daemon.ipc")
> +
> +const char *fsmonitor_ipc__get_path(struct repository *r)
> +{
> +	static const char *ipc_path = NULL;
> +	git_SHA_CTX sha1ctx;
> +	char *sock_dir = NULL;
> +	struct strbuf ipc_file = STRBUF_INIT;
> +	unsigned char hash[GIT_MAX_RAWSZ];
> +
> +	if (!r)
> +		BUG("No repository passed into fsmonitor_ipc__get_path");
> +
> +	if (ipc_path)
> +		return ipc_path;
> +
> +
> +	/* By default the socket file is created in the .git directory */
> +	if (fsmonitor__is_fs_remote(r->gitdir) < 1) {
> +		ipc_path = fsmonitor_ipc__get_default_path();
> +		return ipc_path;
> +	}
> +
> +	git_SHA1_Init(&sha1ctx);
> +	git_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));
> +	git_SHA1_Final(hash, &sha1ctx);
> +
> +	repo_config_get_string(r, "fsmonitor.socketdir", &sock_dir);
> +
> +	/* Create the socket file in either socketDir or $HOME */
> +	if (sock_dir && *sock_dir) {
> +		strbuf_addf(&ipc_file, "%s/.git-fsmonitor-%s",
> +					sock_dir, hash_to_hex(hash));
> +	} else {
> +		strbuf_addf(&ipc_file, "~/.git-fsmonitor-%s", hash_to_hex(hash));
> +	}
> +	free(sock_dir);
> +
> +	ipc_path = interpolate_path(ipc_file.buf, 1);
> +	if (!ipc_path)
> +		die(_("Invalid path: %s"), ipc_file.buf);
> +
> +	strbuf_release(&ipc_file);
> +	return ipc_path;
> +}
> diff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-darwin.c
> index a3825906351..14baf9f0603 100644
> --- a/compat/fsmonitor/fsm-settings-darwin.c
> +++ b/compat/fsmonitor/fsm-settings-darwin.c
> @@ -1,63 +1 @@
> -#include "git-compat-util.h"
> -#include "config.h"
> -#include "fsmonitor-ll.h"
> -#include "fsmonitor-ipc.h"
> -#include "fsmonitor-settings.h"
> -#include "fsmonitor-path-utils.h"
> -
> - /*
> - * For the builtin FSMonitor, we create the Unix domain socket for the
> - * IPC in the .git directory.  If the working directory is remote,
> - * then the socket will be created on the remote file system.  This
> - * can fail if the remote file system does not support UDS file types
> - * (e.g. smbfs to a Windows server) or if the remote kernel does not
> - * allow a non-local process to bind() the socket.  (These problems
> - * could be fixed by moving the UDS out of the .git directory and to a
> - * well-known local directory on the client machine, but care should
> - * be taken to ensure that $HOME is actually local and not a managed
> - * file share.)
> - *
> - * FAT32 and NTFS working directories are problematic too.
> - *
> - * The builtin FSMonitor uses a Unix domain socket in the .git
> - * directory for IPC.  These Windows drive formats do not support
> - * Unix domain sockets, so mark them as incompatible for the daemon.
> - *
> - */
> -static enum fsmonitor_reason check_uds_volume(struct repository *r)
> -{
> -	struct fs_info fs;
> -	const char *ipc_path = fsmonitor_ipc__get_path(r);
> -	struct strbuf path = STRBUF_INIT;
> -	strbuf_add(&path, ipc_path, strlen(ipc_path));
> -
> -	if (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {
> -		strbuf_release(&path);
> -		return FSMONITOR_REASON_ERROR;
> -	}
> -
> -	strbuf_release(&path);
> -
> -	if (fs.is_remote ||
> -		!strcmp(fs.typename, "msdos") ||
> -		!strcmp(fs.typename, "ntfs")) {
> -		free(fs.typename);
> -		return FSMONITOR_REASON_NOSOCKETS;
> -	}
> -
> -	free(fs.typename);
> -	return FSMONITOR_REASON_OK;
> -}
> -
> -enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)
> -{
> -	enum fsmonitor_reason reason;
> -
> -	if (ipc) {
> -		reason = check_uds_volume(r);
> -		if (reason != FSMONITOR_REASON_OK)
> -			return reason;
> -	}
> -
> -	return FSMONITOR_REASON_OK;
> -}
> +#include "fsm-settings-unix.c"
> diff --git a/compat/fsmonitor/fsm-settings-linux.c b/compat/fsmonitor/fsm-settings-linux.c
> new file mode 100644
> index 00000000000..14baf9f0603
> --- /dev/null
> +++ b/compat/fsmonitor/fsm-settings-linux.c
> @@ -0,0 +1 @@
> +#include "fsm-settings-unix.c"
> diff --git a/compat/fsmonitor/fsm-settings-unix.c b/compat/fsmonitor/fsm-settings-unix.c
> new file mode 100644
> index 00000000000..d16dca89416
> --- /dev/null
> +++ b/compat/fsmonitor/fsm-settings-unix.c
> @@ -0,0 +1,61 @@
> +#include "fsmonitor.h"
> +#include "fsmonitor-ipc.h"
> +#include "fsmonitor-path-utils.h"
> +
> + /*
> + * For the builtin FSMonitor, we create the Unix domain socket for the
> + * IPC in the .git directory.  If the working directory is remote,
> + * then the socket will be created on the remote file system.  This
> + * can fail if the remote file system does not support UDS file types
> + * (e.g. smbfs to a Windows server) or if the remote kernel does not
> + * allow a non-local process to bind() the socket.  (These problems
> + * could be fixed by moving the UDS out of the .git directory and to a
> + * well-known local directory on the client machine, but care should
> + * be taken to ensure that $HOME is actually local and not a managed
> + * file share.)
> + *
> + * FAT32 and NTFS working directories are problematic too.
> + *
> + * The builtin FSMonitor uses a Unix domain socket in the .git
> + * directory for IPC.  These Windows drive formats do not support
> + * Unix domain sockets, so mark them as incompatible for the daemon.
> + *
> + */
> +static enum fsmonitor_reason check_uds_volume(struct repository *r)
> +{
> +	struct fs_info fs;
> +	const char *ipc_path = fsmonitor_ipc__get_path(r);
> +	struct strbuf path = STRBUF_INIT;
> +	strbuf_addstr(&path, ipc_path);
> +
> +	if (fsmonitor__get_fs_info(dirname(path.buf), &fs) == -1) {
> +		free(fs.typename);
> +		strbuf_release(&path);
> +		return FSMONITOR_REASON_ERROR;
> +	}
> +
> +	strbuf_release(&path);
> +
> +	if (fs.is_remote ||
> +		!strcmp(fs.typename, "msdos") ||
> +		!strcmp(fs.typename, "ntfs")) {
> +		free(fs.typename);
> +		return FSMONITOR_REASON_NOSOCKETS;
> +	}
> +
> +	free(fs.typename);
> +	return FSMONITOR_REASON_OK;
> +}
> +
> +enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)
> +{
> +	enum fsmonitor_reason reason;
> +
> +	if (ipc) {
> +		reason = check_uds_volume(r);
> +		if (reason != FSMONITOR_REASON_OK)
> +			return reason;
> +	}
> +
> +	return FSMONITOR_REASON_OK;
> +}
> -- 
> gitgitgadget
> 
> 

Copy link

User Patrick Steinhardt <[email protected]> has been added to the cc: list.

@@ -2088,6 +2088,10 @@ ifdef HAVE_CLOCK_GETTIME
BASIC_CFLAGS += -DHAVE_CLOCK_GETTIME

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Patrick Steinhardt wrote (reply to this):

On Thu, Feb 15, 2024 at 10:29:33AM +0000, Eric DeCosta via GitGitGadget wrote:
> From: Eric DeCosta <[email protected]>
> 
> Compare the given path to the mounted filesystems. Find the mount that is
> the longest prefix of the path (if any) and determine if that mount is on a
> local or remote filesystem.

It would be nice to motivate this change in the commit message. Right
now it only explains what the commit does, but it does not mention at
all why that would be a good idea in the first place. Explaining that
this is part of the interface that the existing fsmonitor infrastructure
expects to exist would help.

> Signed-off-by: Eric DeCosta <[email protected]>
> ---
>  Makefile                                |   4 +
>  compat/fsmonitor/fsm-path-utils-linux.c | 195 ++++++++++++++++++++++++
>  compat/fsmonitor/fsm-path-utils-linux.h |  91 +++++++++++
>  config.mak.uname                        |  11 ++
>  4 files changed, 301 insertions(+)
>  create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c
>  create mode 100644 compat/fsmonitor/fsm-path-utils-linux.h
> 
> diff --git a/Makefile b/Makefile
> index 78e874099d9..0f36a0fd83a 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -2088,6 +2088,10 @@ ifdef HAVE_CLOCK_GETTIME
>  	BASIC_CFLAGS += -DHAVE_CLOCK_GETTIME
>  endif
>  
> +ifdef HAVE_LINUX_MAGIC_H
> +	BASIC_CFLAGS += -DHAVE_LINUX_MAGIC_H
> +endif
> +
>  ifdef HAVE_CLOCK_MONOTONIC
>  	BASIC_CFLAGS += -DHAVE_CLOCK_MONOTONIC
>  endif
> diff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c
> new file mode 100644
> index 00000000000..c21d1349532
> --- /dev/null
> +++ b/compat/fsmonitor/fsm-path-utils-linux.c
> @@ -0,0 +1,195 @@
> +#include "git-compat-util.h"
> +#include "abspath.h"
> +#include "fsmonitor.h"
> +#include "fsmonitor-path-utils.h"
> +#include "fsm-path-utils-linux.h"
> +#include <errno.h>
> +#include <mntent.h>
> +#include <sys/mount.h>
> +#include <sys/vfs.h>
> +#include <sys/statvfs.h>
> +
> +static int is_remote_fs(const char *path)
> +{
> +	struct statfs fs;
> +
> +	if (statfs(path, &fs))
> +		return error_errno(_("statfs('%s') failed"), path);
> +
> +	switch (fs.f_type) {
> +	case ACFS_SUPER_MAGIC:
> +	case AFS_SUPER_MAGIC:
> +	case CEPH_SUPER_MAGIC:
> +	case CIFS_SUPER_MAGIC:
> +	case CODA_SUPER_MAGIC:
> +	case FHGFS_SUPER_MAGIC:
> +	case GFS_SUPER_MAGIC:
> +	case GPFS_SUPER_MAGIC:
> +	case IBRIX_SUPER_MAGIC:
> +	case KAFS_SUPER_MAGIC:
> +	case LUSTRE_SUPER_MAGIC:
> +	case NCP_SUPER_MAGIC:
> +	case NFS_SUPER_MAGIC:
> +	case NFSD_SUPER_MAGIC:
> +	case OCFS2_SUPER_MAGIC:
> +	case PANFS_SUPER_MAGIC:
> +	case SMB_SUPER_MAGIC:
> +	case SMB2_SUPER_MAGIC:
> +	case SNFS_SUPER_MAGIC:
> +	case VMHGFS_SUPER_MAGIC:
> +	case VXFS_SUPER_MAGIC:
> +		return 1;
> +	default:
> +		return 0;
> +	}
> +}

This list doesn't feel all that maintainable to me, but so be it if
there is no better interface available.

> +static int find_mount(const char *path, const struct statvfs *fs,
> +			struct mntent *entry)

I don't quite understand why `find_mount()` is required in the first
place. Why can't we statfs(2) the path directly? This syscall provides
the fsid and should be sufficient for us to fill in `struct fs_info`.

Explaining details like this in the commit message would help guide the
reader's expectations.

Patrick

> +{
> +	const char *const mounts = "/proc/mounts";
> +	char *rp = real_pathdup(path, 1);
> +	struct mntent *ment = NULL;
> +	struct statvfs mntfs;
> +	FILE *fp;
> +	int found = 0;
> +	int ret = 0;
> +	size_t dlen, plen, flen = 0;
> +
> +	entry->mnt_fsname = NULL;
> +	entry->mnt_dir = NULL;
> +	entry->mnt_type = NULL;
> +
> +	fp = setmntent(mounts, "r");
> 
> +	if (!fp) {
> +		free(rp);
> +		return error_errno(_("setmntent('%s') failed"), mounts);
> +	}
> +
> +	plen = strlen(rp);
> +
> +	/* read all the mount information and compare to path */
> +	while ((ment = getmntent(fp))) {
> +		if (statvfs(ment->mnt_dir, &mntfs)) {
> +			switch (errno) {
> +			case EPERM:
> +			case ESRCH:
> +			case EACCES:
> +				continue;
> +			default:
> +				error_errno(_("statvfs('%s') failed"), ment->mnt_dir);
> +				ret = -1;
> +				goto done;
> +			}
> +		}
> +
> +		/* is mount on the same filesystem and is a prefix of the path */
> +		if ((fs->f_fsid == mntfs.f_fsid) &&
> +			!strncmp(ment->mnt_dir, rp, strlen(ment->mnt_dir))) {
> +			dlen = strlen(ment->mnt_dir);
> +			if (dlen > plen)
> +				continue;
> +			/*
> +			 * look for the longest prefix (including root)
> +			 */
> +			if (dlen > flen &&
> +				((dlen == 1 && ment->mnt_dir[0] == '/') ||
> +				 (!rp[dlen] || rp[dlen] == '/'))) {
> +				flen = dlen;
> +				found = 1;
> +
> +				/*
> +				 * https://man7.org/linux/man-pages/man3/getmntent.3.html
> +				 *
> +				 * The pointer points to a static area of memory which is
> +				 * overwritten by subsequent calls to getmntent().
> +				 */
> +				free(entry->mnt_fsname);
> +				free(entry->mnt_dir);
> +				free(entry->mnt_type);
> +				entry->mnt_fsname = xstrdup(ment->mnt_fsname);
> +				entry->mnt_dir = xstrdup(ment->mnt_dir);
> +				entry->mnt_type = xstrdup(ment->mnt_type);
> +			}
> +		}
> +	}
> +
> +done:
> +	free(rp);
> +	endmntent(fp);
> +
> +	if (!found)
> +		return -1;
> +
> +	return ret;
> +}
> +
> +int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)
> +{
> +	int ret = 0;
> +	struct mntent entry;
> +	struct statvfs fs;
> +
> +	fs_info->is_remote = -1;
> +	fs_info->typename = NULL;
> +
> +	if (statvfs(path, &fs))
> +		return error_errno(_("statvfs('%s') failed"), path);
> +
> +	if (find_mount(path, &fs, &entry) < 0) {
> +		ret = -1;
> +		goto done;
> +	}
> +
> +	trace_printf_key(&trace_fsmonitor,
> +			 "statvfs('%s') [flags 0x%08lx] '%s' '%s'",
> +			 path, fs.f_flag, entry.mnt_type, entry.mnt_fsname);
> +
> +	fs_info->is_remote = is_remote_fs(entry.mnt_dir);
> +	fs_info->typename = xstrdup(entry.mnt_fsname);
> +
> +	if (fs_info->is_remote < 0)
> +		ret = -1;
> +
> +	trace_printf_key(&trace_fsmonitor,
> +				"'%s' is_remote: %d",
> +				path, fs_info->is_remote);
> +
> +done:
> +	free(entry.mnt_fsname);
> +	free(entry.mnt_dir);
> +	free(entry.mnt_type);
> +	return ret;
> +}
> +
> +int fsmonitor__is_fs_remote(const char *path)
> +{
> +	int ret = 0;
> +	struct fs_info fs;
> +
> +	if (fsmonitor__get_fs_info(path, &fs))
> +		ret = -1;
> +	else
> +		ret = fs.is_remote;
> +
> +	free(fs.typename);
> +
> +	return ret;
> +}
> +
> +/*
> + * No-op for now.
> + */
> +int fsmonitor__get_alias(const char *path, struct alias_info *info)
> +{
> +	return 0;
> +}
> +
> +/*
> + * No-op for now.
> + */
> +char *fsmonitor__resolve_alias(const char *path,
> +		const struct alias_info *info)
> +{
> +	return NULL;
> +}
> diff --git a/compat/fsmonitor/fsm-path-utils-linux.h b/compat/fsmonitor/fsm-path-utils-linux.h
> new file mode 100644
> index 00000000000..49bdb3c4728
> --- /dev/null
> +++ b/compat/fsmonitor/fsm-path-utils-linux.h
> @@ -0,0 +1,91 @@
> +#ifndef FSM_PATH_UTILS_LINUX_H
> +#define FSM_PATH_UTILS_LINUX_H
> +#endif
> +
> +#ifdef HAVE_LINUX_MAGIC_H
> +#include <linux/magic.h>
> +#endif
> +
> +#ifndef ACFS_SUPER_MAGIC
> +#define ACFS_SUPER_MAGIC 0x61636673
> +#endif
> +
> +#ifndef AFS_SUPER_MAGIC
> +#define AFS_SUPER_MAGIC 0x5346414f
> +#endif
> +
> +#ifndef CEPH_SUPER_MAGIC
> +#define CEPH_SUPER_MAGIC 0x00c36400
> +#endif
> +
> +#ifndef CIFS_SUPER_MAGIC
> +#define CIFS_SUPER_MAGIC 0xff534d42
> +#endif
> +
> +#ifndef CODA_SUPER_MAGIC
> +#define CODA_SUPER_MAGIC 0x73757245
> +#endif
> +
> +#ifndef FHGFS_SUPER_MAGIC
> +#define FHGFS_SUPER_MAGIC 0x19830326
> +#endif
> +
> +#ifndef GFS_SUPER_MAGIC
> +#define GFS_SUPER_MAGIC 0x1161970
> +#endif
> +
> +#ifndef GPFS_SUPER_MAGIC
> +#define GPFS_SUPER_MAGIC 0x47504653
> +#endif
> +
> +#ifndef IBRIX_SUPER_MAGIC
> +#define IBRIX_SUPER_MAGIC 0x013111a8
> +#endif
> +
> +#ifndef KAFS_SUPER_MAGIC
> +#define KAFS_SUPER_MAGIC 0x6b414653
> +#endif
> +
> +#ifndef LUSTRE_SUPER_MAGIC
> +#define LUSTRE_SUPER_MAGIC 0x0bd00bd0
> +#endif
> +
> +#ifndef NCP_SUPER_MAGIC
> +#define NCP_SUPER_MAGIC 0x564c
> +#endif
> +
> +#ifndef NFS_SUPER_MAGIC
> +#define NFS_SUPER_MAGIC 0x6969
> +#endif
> +
> +#ifndef NFSD_SUPER_MAGIC
> +#define NFSD_SUPER_MAGIC 0x6e667364
> +#endif
> +
> +#ifndef OCFS2_SUPER_MAGIC
> +#define OCFS2_SUPER_MAGIC 0x7461636f
> +#endif
> +
> +#ifndef PANFS_SUPER_MAGIC
> +#define PANFS_SUPER_MAGIC 0xaad7aaea
> +#endif
> +
> +#ifndef SMB_SUPER_MAGIC
> +#define SMB_SUPER_MAGIC 0x517b
> +#endif
> +
> +#ifndef SMB2_SUPER_MAGIC
> +#define SMB2_SUPER_MAGIC 0xfe534d42
> +#endif
> +
> +#ifndef SNFS_SUPER_MAGIC
> +#define SNFS_SUPER_MAGIC 0xbeefdead
> +#endif
> +
> +#ifndef VMHGFS_SUPER_MAGIC
> +#define VMHGFS_SUPER_MAGIC 0xbacbacbc
> +#endif
> +
> +#ifndef VXFS_SUPER_MAGIC
> +#define VXFS_SUPER_MAGIC 0xa501fcf5
> +#endif
> diff --git a/config.mak.uname b/config.mak.uname
> index dacc95172dc..80d7e2a2e68 100644
> --- a/config.mak.uname
> +++ b/config.mak.uname
> @@ -68,6 +68,17 @@ ifeq ($(uname_S),Linux)
>  	ifneq ($(findstring .el7.,$(uname_R)),)
>  		BASIC_CFLAGS += -std=c99
>  	endif
> +	ifeq ($(shell test -f /usr/include/linux/magic.h && echo y),y)
> +		HAVE_LINUX_MAGIC_H = YesPlease
> +	endif
> +	# The builtin FSMonitor on Linux builds upon Simple-IPC.  Both require
> +	# Unix domain sockets and PThreads.
> +	ifndef NO_PTHREADS
> +	ifndef NO_UNIX_SOCKETS
> +	FSMONITOR_DAEMON_BACKEND = linux
> +	FSMONITOR_OS_SETTINGS = linux
> +	endif
> +	endif
>  endif
>  ifeq ($(uname_S),GNU/kFreeBSD)
>  	HAVE_ALLOCA_H = YesPlease
> -- 
> gitgitgadget
> 
> 

@@ -0,0 +1,676 @@
#include "cache.h"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Patrick Steinhardt wrote (reply to this):

On Thu, Feb 15, 2024 at 10:29:34AM +0000, Eric DeCosta via GitGitGadget wrote:
> From: Eric DeCosta <[email protected]>
> 
> Implement a filesystem change listener for Linux based on the inotify API:
> https://man7.org/linux/man-pages/man7/inotify.7.html
> 
> inotify requires registering a watch on every directory in the worktree and
> special handling of moves/renames.

I assume that fsmonitor is especially important in the context of repos
with large trees, and to the best of my knowledge inotify(7) does not
scale well when installing many watches. I thus have to wonder whether
fanotify(7) would be a better match to implement this nowadays, and what
the considerations were to pick one over the other.

> Signed-off-by: Eric DeCosta <[email protected]>
> ---
>  compat/fsmonitor/fsm-listen-linux.c | 676 ++++++++++++++++++++++++++++
>  1 file changed, 676 insertions(+)
>  create mode 100644 compat/fsmonitor/fsm-listen-linux.c
> 
> diff --git a/compat/fsmonitor/fsm-listen-linux.c b/compat/fsmonitor/fsm-listen-linux.c
> new file mode 100644
> index 00000000000..e8548e4e009
> --- /dev/null
> +++ b/compat/fsmonitor/fsm-listen-linux.c
> @@ -0,0 +1,676 @@
> +#include "cache.h"
> +#include "fsmonitor.h"
> +#include "fsm-listen.h"
> +#include "fsmonitor--daemon.h"
> +#include <dirent.h>
> +#include <fcntl.h>
> +#include <sys/inotify.h>
> +#include <sys/stat.h>
> +
> +/*
> + * Safe value to bitwise OR with rest of mask for
> + * kernels that do not support IN_MASK_CREATE
> + */
> +#ifndef IN_MASK_CREATE
> +#define IN_MASK_CREATE 0x00000000
> +#endif
> +
> +enum shutdown_reason {
> +	SHUTDOWN_CONTINUE = 0,
> +	SHUTDOWN_STOP,
> +	SHUTDOWN_ERROR,
> +	SHUTDOWN_FORCE
> +};
> +
> +struct watch_entry {
> +	struct hashmap_entry ent;
> +	int wd;
> +	uint32_t cookie;
> +	const char *dir;
> +};
> +
> +struct rename_entry {
> +	struct hashmap_entry ent;
> +	time_t whence;
> +	uint32_t cookie;
> +	const char *dir;
> +};
> +
> +struct fsm_listen_data {
> +	int fd_inotify;
> +	enum shutdown_reason shutdown;
> +	struct hashmap watches;
> +	struct hashmap renames;
> +	struct hashmap revwatches;
> +};
> +
> +static int watch_entry_cmp(const void *cmp_data,
> +			  const struct hashmap_entry *eptr,
> +			  const struct hashmap_entry *entry_or_key,
> +			  const void *keydata)
> +{
> +	const struct watch_entry *e1, *e2;
> +
> +	e1 = container_of(eptr, const struct watch_entry, ent);
> +	e2 = container_of(eptr, const struct watch_entry, ent);
> +	return e1->wd != e2->wd;
> +}
> +
> +static int revwatches_entry_cmp(const void *cmp_data,
> +			  const struct hashmap_entry *eptr,
> +			  const struct hashmap_entry *entry_or_key,
> +			  const void *keydata)
> +{
> +	const struct watch_entry *e1, *e2;
> +
> +	e1 = container_of(eptr, const struct watch_entry, ent);
> +	e2 = container_of(eptr, const struct watch_entry, ent);
> +	return strcmp(e1->dir, e2->dir);
> +}
> +
> +static int rename_entry_cmp(const void *cmp_data,
> +			  const struct hashmap_entry *eptr,
> +			  const struct hashmap_entry *entry_or_key,
> +			  const void *keydata)
> +{
> +	const struct rename_entry *e1, *e2;
> +
> +	e1 = container_of(eptr, const struct rename_entry, ent);
> +	e2 = container_of(eptr, const struct rename_entry, ent);
> +	return e1->cookie != e2->cookie;
> +}
> +
> +/*
> + * Register an inotify watch, add watch descriptor to path mapping
> + * and the reverse mapping.
> + */
> +static int add_watch(const char *path, struct fsm_listen_data *data)
> +{
> +	const char *interned = strintern(path);
> +	struct watch_entry *w1, *w2;
> +
> +	/* add the inotify watch, don't allow watches to be modified */
> +	int wd = inotify_add_watch(data->fd_inotify, interned,
> +				(IN_ALL_EVENTS | IN_ONLYDIR | IN_MASK_CREATE)
> +				^ IN_ACCESS ^ IN_CLOSE ^ IN_OPEN);
> +	if (wd < 0)
> +		return error_errno("inotify_add_watch('%s') failed", interned);
> +
> +	/* add watch descriptor -> directory mapping */
> +	CALLOC_ARRAY(w1, 1);
> +	w1->wd = wd;
> +	w1->dir = interned;
> +	hashmap_entry_init(&w1->ent, memhash(&w1->wd, sizeof(int)));
> +	hashmap_add(&data->watches, &w1->ent);
> +
> +	/* add directory -> watch descriptor mapping */
> +	CALLOC_ARRAY(w2, 1);
> +	w2->wd = wd;
> +	w2->dir = interned;
> +	hashmap_entry_init(&w2->ent, memhash(w2->dir, strlen(w2->dir)));
> +	hashmap_add(&data->revwatches, &w2->ent);
> +
> +	return 0;
> +}
> +
> +/*
> + * 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");
> +
> +	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);
> +
> +	/* w1->dir and w2->dir are interned strings, we don't own them */
> +	free(w1);
> +	free(w2);
> +}
> +
> +/*
> + * Check for stale directory renames.
> + *
> + * https://man7.org/linux/man-pages/man7/inotify.7.html
> + *
> + * Allow for some small timeout to account for the fact that insertion of the
> + * IN_MOVED_FROM+IN_MOVED_TO event pair is not atomic, and the possibility that
> + * there may not be any IN_MOVED_TO event.
> + *
> + * If the IN_MOVED_TO event is not received within the timeout then events have
> + * been missed and the monitor is in an inconsistent state with respect to the
> + * filesystem.
> + */
> +static int check_stale_dir_renames(struct hashmap *renames, time_t max_age)
> +{
> +	struct rename_entry *re;
> +	struct hashmap_iter iter;
> +
> +	hashmap_for_each_entry(renames, &iter, re, ent) {
> +		if (re->whence <= max_age)
> +			return -1;
> +	}
> +	return 0;
> +}
> +
> +/*
> + * Track pending renames.
> + *
> + * Tracking is done via a event cookie to watch descriptor mapping.
> + *
> + * A rename is not complete until matching a IN_MOVED_TO event is received
> + * for a corresponding IN_MOVED_FROM event.
> + */
> +static void add_dir_rename(uint32_t cookie, const char *path,
> +	struct fsm_listen_data *data)

Nit: `add_dir_rename()` and the below `rename_dir()` sound as if we
actually perform the rename ourselves. How about `track_dir_rename()`
and `finalize_dir_rename()`?

> +{
> +	struct watch_entry k, *w;
> +	struct rename_entry *re;
> +
> +	/* lookup the watch descriptor for the given path */
> +	hashmap_entry_init(&k.ent, memhash(path, strlen(path)));
> +	w = hashmap_get_entry(&data->revwatches, &k, ent, NULL);
> +	if (!w) /* should never happen */
> +		BUG("No watch for '%s'", path);

Error message should start with lower-case character.

> +	w->cookie = cookie;
> +
> +	/* add the pending rename to match against later */
> +	CALLOC_ARRAY(re, 1);
> +	re->dir = w->dir;
> +	re->cookie = w->cookie;
> +	re->whence = time(NULL);
> +	hashmap_entry_init(&re->ent, memhash(&re->cookie, sizeof(uint32_t)));
> +	hashmap_add(&data->renames, &re->ent);
> +}
> +
> +/*
> + * 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);
> +		} else {
> +			BUG("No matching watch");
> +		}
> +	} else {
> +		BUG("No matching cookie");

The above two bugs should start with a lower-case letter.

> +	}
> +}
> +
> +/*
> + * 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) {
> +		strbuf_reset(&current);
> +		strbuf_addf(&current, "%s/%s", path, de->d_name);
> +		if (lstat(current.buf, &fs)) {
> +			error_errno("lstat('%s') failed", current.buf);

Missing `_()` translation marker.

> +			goto failed;
> +		}
> +
> +		/* recurse into directory */
> +		if (S_ISDIR(fs.st_mode)) {
> +			if (add_watch(current.buf, state->listen_data))
> +				goto failed;
> +			if (register_inotify(current.buf, state, batch))
> +				goto failed;
> +		} else if (batch) {
> +			rel = current.buf + state->path_worktree_watch.len + 1;
> +			trace_printf_key(&trace_fsmonitor, "explicitly adding '%s'", rel);
> +			fsmonitor_batch__add_path(batch, rel);
> +		}
> +	}
> +	ret = 0;
> +
> +failed:
> +	strbuf_release(&current);
> +	if (closedir(dir) < 0)
> +		return error_errno("closedir('%s') failed", path);

Missing `_()` translation marker.

> +	return ret;
> +}
> +
> +static int em_rename_dir_from(u_int32_t mask)
> +{
> +	return ((mask & IN_ISDIR) && (mask & IN_MOVED_FROM));
> +}
> +
> +static int em_rename_dir_to(u_int32_t mask)
> +{
> +	return ((mask & IN_ISDIR) && (mask & IN_MOVED_TO));
> +}
> +
> +static int em_remove_watch(u_int32_t mask)
> +{
> +	return (mask & IN_DELETE_SELF);
> +}
> +
> +static int em_dir_renamed(u_int32_t mask)
> +{
> +	return ((mask & IN_ISDIR) && (mask & IN_MOVE));
> +}
> +
> +static int em_dir_created(u_int32_t mask)
> +{
> +	return ((mask & IN_ISDIR) && (mask & IN_CREATE));
> +}
> +
> +static int em_dir_deleted(uint32_t mask)
> +{
> +	return ((mask & IN_ISDIR) && (mask & IN_DELETE));
> +}
> +
> +static int em_force_shutdown(u_int32_t mask)
> +{
> +	return (mask & IN_UNMOUNT) || (mask & IN_Q_OVERFLOW);
> +}
> +
> +static int em_ignore(u_int32_t mask)
> +{
> +	return (mask & IN_IGNORED) || (mask & IN_MOVE_SELF);
> +}
> +
> +static void log_mask_set(const char *path, u_int32_t mask)
> +{
> +	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|");

Doesn't this end up with one trailing '|' in `msg`? You could use
`strbuf_strip_suffix(msg, "|")` to drop it.

> +	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");
> +
> +	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))
> +		ret = -1;
> +	else if (register_inotify(state->path_worktree_watch.buf, state, NULL))
> +		ret = -1;
> +	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;
> +	}

Style: if one of the branches requires braces then all of them should.

> +
> +	if (!ret) {
> +		state->listen_error_code = 0;
> +		data->shutdown = SHUTDOWN_CONTINUE;
> +	}
> +
> +	return ret;
> +}
> +
> +void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
> +{
> +	struct fsm_listen_data *data;
> +	struct hashmap_iter iter;
> +	struct watch_entry *w;
> +	int fd;
> +
> +	if (!state || !state->listen_data)
> +		return;
> +
> +	data = state->listen_data;
> +	fd = data->fd_inotify;
> +
> +	hashmap_for_each_entry(&data->watches, &iter, w, ent) {
> +		w->cookie = 0; /* ignore any pending renames */
> +		remove_watch(w, data);
> +	}
> +	hashmap_clear(&data->watches);
> +
> +	hashmap_clear(&data->revwatches); /* remove_watch freed the entries */
> +
> +	hashmap_clear_and_free(&data->renames, struct rename_entry, ent);
> +
> +	FREE_AND_NULL(state->listen_data);

The empty lines between all these cleanups  can probably be removed.

> +
> +	if (fd && (close(fd) < 0))
> +		error_errno(_("closing inotify file descriptor failed"));
> +}
> +
> +void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
> +{
> +	if (!state->listen_data->shutdown)
> +		state->listen_data->shutdown = SHUTDOWN_STOP;
> +}
> +
> +/*
> + * Process a single inotify event and queue for publication.
> + */
> +static int process_event(const char *path,
> +	const struct inotify_event *event,
> +	struct fsmonitor_batch *batch,
> +	struct string_list *cookie_list,
> +	struct fsmonitor_daemon_state *state)
> +{
> +	const char *rel;
> +	const char *last_sep;
> +
> +	switch (fsmonitor_classify_path_absolute(state, path)) {
> +		case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
> +		case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
> +			/* Use just the filename of the cookie file. */
> +			last_sep = find_last_dir_sep(path);
> +			string_list_append(cookie_list,
> +					last_sep ? last_sep + 1 : path);
> +			break;
> +		case IS_INSIDE_DOT_GIT:
> +		case IS_INSIDE_GITDIR:
> +			break;
> +		case IS_DOT_GIT:
> +		case IS_GITDIR:
> +			/*
> +			* If .git directory is deleted or renamed away,
> +			* we have to quit.
> +			*/
> +			if (em_dir_deleted(event->mask)) {
> +				trace_printf_key(&trace_fsmonitor,
> +						"event: gitdir removed");
> +				state->listen_data->shutdown = SHUTDOWN_FORCE;
> +				goto done;
> +			}
> +
> +			if (em_dir_renamed(event->mask)) {
> +				trace_printf_key(&trace_fsmonitor,
> +						"event: gitdir renamed");
> +				state->listen_data->shutdown = SHUTDOWN_FORCE;
> +				goto done;
> +			}
> +			break;
> +		case IS_WORKDIR_PATH:
> +			/* normal events in the working directory */
> +			if (trace_pass_fl(&trace_fsmonitor))
> +				log_mask_set(path, event->mask);
> +
> +			rel = path + state->path_worktree_watch.len + 1;
> +			fsmonitor_batch__add_path(batch, rel);
> +
> +			if (em_dir_deleted(event->mask))
> +				break;
> +
> +			/* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */
> +			if (em_rename_dir_from(event->mask))
> +				add_dir_rename(event->cookie, path, state->listen_data);
> +
> +			/* received IN_MOVE_TO, update watch to reflect new path */
> +			if (em_rename_dir_to(event->mask)) {
> +				rename_dir(event->cookie, path, state->listen_data);
> +				if (register_inotify(path, state, batch)) {
> +					state->listen_data->shutdown = SHUTDOWN_ERROR;
> +					goto done;
> +				}
> +			}
> +
> +			if (em_dir_created(event->mask)) {
> +				if (add_watch(path, state->listen_data)) {
> +					state->listen_data->shutdown = SHUTDOWN_ERROR;
> +					goto done;
> +				}
> +				if (register_inotify(path, state, batch)) {
> +					state->listen_data->shutdown = SHUTDOWN_ERROR;
> +					goto done;
> +				}
> +			}
> +			break;
> +		case IS_OUTSIDE_CONE:
> +		default:
> +			trace_printf_key(&trace_fsmonitor,
> +					"ignoring '%s'", path);
> +			break;
> +	}
> +	return 0;
> +done:
> +	return -1;
> +}
> +
> +/*
> + * Read the inotify event stream and pre-process events before further
> + * processing and eventual publishing.
> + */
> +static void handle_events(struct fsmonitor_daemon_state *state)
> +{
> +	 /* See https://man7.org/linux/man-pages/man7/inotify.7.html */
> +	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;

I think many of the variables could be moved into deeper scopes. Makes
it easier to assess what is being used where.

> +
> +	strbuf_init(&path, PATH_MAX);
> +
> +	for(;;) {
> +		len = read(fd, buf, sizeof(buf));
> +		if (len == -1 && errno != EAGAIN) {

Wouldn't we also have to handle EINTR here?

> +			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) {

Nit: there's an additional whitespace here.

> +
> +			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);

Error messages should start with a lower-case letter.

> +
> +			/* 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);

The above three lines can be simplified to:

```
strbuf_addf("%s/%s", w->dir, event->name);
```

> +
> +			p = fsmonitor__resolve_alias(path.buf, &state->alias);
> +			if (!p)
> +				p = strbuf_detach(&path, NULL);
> +
> +			if (!batch)
> +				batch = fsmonitor_batch__new();
> +
> +			if (process_event(p, event, batch, &cookie_list, state)) {
> +				free(p);
> +				goto done;
> +			}
> +			free(p);
> +		}
> +		strbuf_reset(&path);
> +		fsmonitor_publish(state, batch, &cookie_list);
> +		string_list_clear(&cookie_list, 0);
> +		batch = NULL;
> +	}
> +done:
> +	strbuf_release(&path);
> +	fsmonitor_batch__free_list(batch);
> +	string_list_clear(&cookie_list, 0);
> +}
> +
> +/*
> + * Non-blocking read of the inotify events stream. The inotify fd is polled
> + * frequently to help minimize the number of queue overflows.
> + */
> +void fsm_listen__loop(struct fsmonitor_daemon_state *state)
> +{
> +	int poll_num;
> +	const int interval = 1000;

You could rename the variable to `interval_ms` to clarify its unit.

> +	time_t checked = time(NULL);
> +	struct pollfd fds[1];
> +	fds[0].fd = state->listen_data->fd_inotify;
> +	fds[0].events = POLLIN;
> +
> +	for(;;) {

Nit: `while (1)`

> +		switch (state->listen_data->shutdown) {
> +			case SHUTDOWN_CONTINUE:
> +				poll_num = poll(fds, 1, 1);

Why do you pick a timeout of 1 millisecond here? I'd have expected us to
instruct poll(3p) to either not block at all (0) or to block for
`interval`.

> +				if (poll_num == -1) {
> +					if (errno == EINTR)
> +						continue;

Wouldn't we also have to handle EAGAIN here?

Patrick

> +					error_errno(_("polling inotify message stream failed"));
> +					state->listen_data->shutdown = SHUTDOWN_ERROR;
> +					continue;
> +				}
> +
> +				if ((time(NULL) - checked) >= interval) {
> +					checked = time(NULL);
> +					if (check_stale_dir_renames(&state->listen_data->renames,
> +						checked - interval)) {
> +						trace_printf_key(&trace_fsmonitor,
> +							"Missed IN_MOVED_TO events, forcing shutdown");
> +						state->listen_data->shutdown = SHUTDOWN_FORCE;
> +						continue;
> +					}
> +				}
> +
> +				if (poll_num > 0 && (fds[0].revents & POLLIN))
> +					handle_events(state);
> +
> +				continue;
> +			case SHUTDOWN_ERROR:
> +				state->listen_error_code = -1;
> +				ipc_server_stop_async(state->ipc_server_data);
> +				break;
> +			case SHUTDOWN_FORCE:
> +				state->listen_error_code = 0;
> +				ipc_server_stop_async(state->ipc_server_data);
> +				break;
> +			case SHUTDOWN_STOP:
> +			default:
> +				state->listen_error_code = 0;
> +				break;
> +		}
> +		return;
> +	}
> +}
> -- 
> gitgitgadget
> 
> 

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regarding fanotify this is the original authors thought that shared with me:

I did start to explore an implementation based on fanotify, but did not pursue it too deeply as it is a privileged API that would require that the fsmonitor daemon be started as root.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maryis please follow the advice how to reply to the mail. PR comments won't be seen by the Git reviewers.

@@ -0,0 +1,24 @@
#include "git-compat-util.h"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Patrick Steinhardt wrote (reply to this):

On Thu, Feb 15, 2024 at 10:29:38AM +0000, marzi.esipreh via GitGitGadget wrote:
> From: "marzi.esipreh" <[email protected]>
> 
> addressed comments on 1352, rebased, resolved conflicts

Please squash these changes into the preceding commits whereever
required.

Patrick

> 
> Signed-off-by: Marzieh Esipreh <[email protected]>
> ---
>  compat/fsmonitor/fsm-health-linux.c     |   2 +-
>  compat/fsmonitor/fsm-ipc-unix.c         |   6 +-
>  compat/fsmonitor/fsm-listen-linux.c     | 170 ++++++++++++------------
>  compat/fsmonitor/fsm-path-utils-linux.c |   1 +
>  compat/fsmonitor/fsm-settings-unix.c    |   3 +
>  5 files changed, 95 insertions(+), 87 deletions(-)
> 
> diff --git a/compat/fsmonitor/fsm-health-linux.c b/compat/fsmonitor/fsm-health-linux.c
> index b9f709e8548..4c291f8a066 100644
> --- a/compat/fsmonitor/fsm-health-linux.c
> +++ b/compat/fsmonitor/fsm-health-linux.c
> @@ -1,4 +1,4 @@
> -#include "cache.h"
> +#include "git-compat-util.h"
>  #include "config.h"
>  #include "fsmonitor.h"
>  #include "fsm-health.h"
> diff --git a/compat/fsmonitor/fsm-ipc-unix.c b/compat/fsmonitor/fsm-ipc-unix.c
> index eb25123fa12..70afddfd298 100644
> --- a/compat/fsmonitor/fsm-ipc-unix.c
> +++ b/compat/fsmonitor/fsm-ipc-unix.c
> @@ -1,10 +1,12 @@
> -#include "cache.h"
> +#include "git-compat-util.h"
>  #include "config.h"
>  #include "hex.h"
>  #include "strbuf.h"
>  #include "fsmonitor.h"
>  #include "fsmonitor-ipc.h"
>  #include "fsmonitor-path-utils.h"
> +#include "gettext.h"
> +#include "path.h"
>  
>  static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, "fsmonitor--daemon.ipc")
>  
> @@ -17,7 +19,7 @@ const char *fsmonitor_ipc__get_path(struct repository *r)
>  	unsigned char hash[GIT_MAX_RAWSZ];
>  
>  	if (!r)
> -		BUG("No repository passed into fsmonitor_ipc__get_path");
> +		BUG("no repository passed into fsmonitor_ipc__get_path");
>  
>  	if (ipc_path)
>  		return ipc_path;
> diff --git a/compat/fsmonitor/fsm-listen-linux.c b/compat/fsmonitor/fsm-listen-linux.c
> index e8548e4e009..84d8fb28d5d 100644
> --- a/compat/fsmonitor/fsm-listen-linux.c
> +++ b/compat/fsmonitor/fsm-listen-linux.c
> @@ -1,7 +1,10 @@
> -#include "cache.h"
> +#include "git-compat-util.h"
> +#include "config.h"
>  #include "fsmonitor.h"
>  #include "fsm-listen.h"
>  #include "fsmonitor--daemon.h"
> +#include "gettext.h"
> +#include "simple-ipc.h"
>  #include <dirent.h>
>  #include <fcntl.h>
>  #include <sys/inotify.h>
> @@ -129,15 +132,15 @@ static void remove_watch(struct watch_entry *w,
>  	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);
> +		BUG("double remove of watch for '%s'", w->dir);
>  
>  	if (w1->cookie)
> -		BUG("Removing watch for '%s' which has a pending rename", w1->dir);
> +		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);
> +		BUG("double remove of reverse watch for '%s'", w->dir);
>  
>  	/* w1->dir and w2->dir are interned strings, we don't own them */
>  	free(w1);
> @@ -187,7 +190,7 @@ static void add_dir_rename(uint32_t cookie, const char *path,
>  	hashmap_entry_init(&k.ent, memhash(path, strlen(path)));
>  	w = hashmap_get_entry(&data->revwatches, &k, ent, NULL);
>  	if (!w) /* should never happen */
> -		BUG("No watch for '%s'", path);
> +		BUG("no watch for '%s'", path);
>  	w->cookie = cookie;
>  
>  	/* add the pending rename to match against later */
> @@ -224,10 +227,10 @@ static void rename_dir(uint32_t cookie, const char *path,
>  			remove_watch(w, data);
>  			add_watch(path, data);
>  		} else {
> -			BUG("No matching watch");
> +			BUG("no matching watch");
>  		}
>  	} else {
> -		BUG("No matching cookie");
> +		BUG("no matching cookie");
>  	}
>  }
>  
> @@ -249,7 +252,7 @@ static int register_inotify(const char *path,
>  	if (!dir)
>  		return error_errno("opendir('%s') failed", path);
>  
> -	while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {
> +	while ((de = readdir_skip_dot_and_dotdot(dir))) {
>  		strbuf_reset(&current);
>  		strbuf_addf(&current, "%s/%s", path, de->d_name);
>  		if (lstat(current.buf, &fs)) {
> @@ -353,7 +356,7 @@ static void log_mask_set(const char *path, u_int32_t mask)
>  	if (mask & IN_IGNORED)
>  		strbuf_addstr(&msg, "IN_IGNORED|");
>  	if (mask & IN_ISDIR)
> -		strbuf_addstr(&msg, "IN_ISDIR|");
> +		strbuf_addstr(&msg, "IN_ISDIR");
>  
>  	trace_printf_key(&trace_fsmonitor, "inotify_event: '%s', mask=%#8.8x %s",
>  				path, mask, msg.buf);
> @@ -373,8 +376,10 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
>  	data->shutdown = SHUTDOWN_ERROR;
>  
>  	fd = inotify_init1(O_NONBLOCK);
> -	if (fd < 0)
> +	if (fd < 0) {
> +		FREE_AND_NULL(data);
>  		return error_errno("inotify_init1() failed");
> +	}
>  
>  	data->fd_inotify = fd;
>  
> @@ -386,12 +391,10 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
>  		ret = -1;
>  	else if (register_inotify(state->path_worktree_watch.buf, state, NULL))
>  		ret = -1;
> -	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;
> -	}
> +	else if (state->nr_paths_watching > 1 &&
> +		 (add_watch(state->path_gitdir_watch.buf, data) ||
> +		  register_inotify(state->path_gitdir_watch.buf, state, NULL)))
> +		ret = -1;
>  
>  	if (!ret) {
>  		state->listen_error_code = 0;
> @@ -449,80 +452,80 @@ static int process_event(const char *path,
>  	const char *last_sep;
>  
>  	switch (fsmonitor_classify_path_absolute(state, path)) {
> -		case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
> -		case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
> -			/* Use just the filename of the cookie file. */
> -			last_sep = find_last_dir_sep(path);
> -			string_list_append(cookie_list,
> -					last_sep ? last_sep + 1 : path);
> -			break;
> -		case IS_INSIDE_DOT_GIT:
> -		case IS_INSIDE_GITDIR:
> -			break;
> -		case IS_DOT_GIT:
> -		case IS_GITDIR:
> -			/*
> -			* If .git directory is deleted or renamed away,
> -			* we have to quit.
> -			*/
> -			if (em_dir_deleted(event->mask)) {
> -				trace_printf_key(&trace_fsmonitor,
> -						"event: gitdir removed");
> -				state->listen_data->shutdown = SHUTDOWN_FORCE;
> -				goto done;
> -			}
> +	case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
> +	case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
> +		/* Use just the filename of the cookie file. */
> +		last_sep = find_last_dir_sep(path);
> +		string_list_append(cookie_list,
> +				last_sep ? last_sep + 1 : path);
> +		break;
> +	case IS_INSIDE_DOT_GIT:
> +	case IS_INSIDE_GITDIR:
> +		break;
> +	case IS_DOT_GIT:
> +	case IS_GITDIR:
> +		/*
> +		* If .git directory is deleted or renamed away,
> +		* we have to quit.
> +		*/
> +		if (em_dir_deleted(event->mask)) {
> +			trace_printf_key(&trace_fsmonitor,
> +					"event: gitdir removed");
> +			state->listen_data->shutdown = SHUTDOWN_FORCE;
> +			goto done;
> +		}
>  
> -			if (em_dir_renamed(event->mask)) {
> -				trace_printf_key(&trace_fsmonitor,
> -						"event: gitdir renamed");
> -				state->listen_data->shutdown = SHUTDOWN_FORCE;
> -				goto done;
> -			}
> -			break;
> -		case IS_WORKDIR_PATH:
> -			/* normal events in the working directory */
> -			if (trace_pass_fl(&trace_fsmonitor))
> -				log_mask_set(path, event->mask);
> +		if (em_dir_renamed(event->mask)) {
> +			trace_printf_key(&trace_fsmonitor,
> +					"event: gitdir renamed");
> +			state->listen_data->shutdown = SHUTDOWN_FORCE;
> +			goto done;
> +		}
> +		break;
> +	case IS_WORKDIR_PATH:
> +		/* normal events in the working directory */
> +		if (trace_pass_fl(&trace_fsmonitor))
> +			log_mask_set(path, event->mask);
>  
> -			rel = path + state->path_worktree_watch.len + 1;
> -			fsmonitor_batch__add_path(batch, rel);
> +		rel = path + state->path_worktree_watch.len + 1;
> +		fsmonitor_batch__add_path(batch, rel);
>  
> -			if (em_dir_deleted(event->mask))
> -				break;
> +		if (em_dir_deleted(event->mask))
> +			break;
>  
> -			/* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */
> -			if (em_rename_dir_from(event->mask))
> -				add_dir_rename(event->cookie, path, state->listen_data);
> +		/* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */
> +		if (em_rename_dir_from(event->mask))
> +			add_dir_rename(event->cookie, path, state->listen_data);
>  
> -			/* received IN_MOVE_TO, update watch to reflect new path */
> -			if (em_rename_dir_to(event->mask)) {
> -				rename_dir(event->cookie, path, state->listen_data);
> -				if (register_inotify(path, state, batch)) {
> -					state->listen_data->shutdown = SHUTDOWN_ERROR;
> -					goto done;
> -				}
> +		/* received IN_MOVE_TO, update watch to reflect new path */
> +		if (em_rename_dir_to(event->mask)) {
> +			rename_dir(event->cookie, path, state->listen_data);
> +			if (register_inotify(path, state, batch)) {
> +				state->listen_data->shutdown = SHUTDOWN_ERROR;
> +				goto done;
>  			}
> +		}
>  
> -			if (em_dir_created(event->mask)) {
> -				if (add_watch(path, state->listen_data)) {
> -					state->listen_data->shutdown = SHUTDOWN_ERROR;
> -					goto done;
> -				}
> -				if (register_inotify(path, state, batch)) {
> -					state->listen_data->shutdown = SHUTDOWN_ERROR;
> -					goto done;
> -				}
> +		if (em_dir_created(event->mask)) {
> +			if (add_watch(path, state->listen_data)) {
> +				state->listen_data->shutdown = SHUTDOWN_ERROR;
> +				goto done;
>  			}
> -			break;
> -		case IS_OUTSIDE_CONE:
> -		default:
> -			trace_printf_key(&trace_fsmonitor,
> -					"ignoring '%s'", path);
> -			break;
> +			if (register_inotify(path, state, batch)) {
> +				state->listen_data->shutdown = SHUTDOWN_ERROR;
> +				goto done;
> +			}
> +		}
> +		break;
> +	case IS_OUTSIDE_CONE:
> +	default:
> +		trace_printf_key(&trace_fsmonitor,
> +				"ignoring '%s'", path);
> +		break;
>  	}
>  	return 0;
> -done:
> -	return -1;
> +	done:
> +		return -1;
>  }
>  
>  /*
> @@ -531,7 +534,7 @@ static int process_event(const char *path,
>   */
>  static void handle_events(struct fsmonitor_daemon_state *state)
>  {
> -	 /* See https://man7.org/linux/man-pages/man7/inotify.7.html */
> +	/* See https://man7.org/linux/man-pages/man7/inotify.7.html */
>  	char buf[4096]
>  		__attribute__ ((aligned(__alignof__(struct inotify_event))));
>  
> @@ -539,13 +542,12 @@ static void handle_events(struct fsmonitor_daemon_state *state)
>  	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);
> +	struct strbuf path = STRBUF_INIT;
>  
>  	for(;;) {
>  		len = read(fd, buf, sizeof(buf));
> @@ -581,7 +583,7 @@ static void handle_events(struct fsmonitor_daemon_state *state)
>  
>  			w = hashmap_get_entry(&watches, &k, ent, NULL);
>  			if (!w) /* should never happen */
> -				BUG("No watch for '%s'", event->name);
> +				BUG("no watch for '%s'", event->name);
>  
>  			/* directory watch was removed */
>  			if (em_remove_watch(event->mask)) {
> diff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c
> index c21d1349532..0e3b33ffa48 100644
> --- a/compat/fsmonitor/fsm-path-utils-linux.c
> +++ b/compat/fsmonitor/fsm-path-utils-linux.c
> @@ -3,6 +3,7 @@
>  #include "fsmonitor.h"
>  #include "fsmonitor-path-utils.h"
>  #include "fsm-path-utils-linux.h"
> +#include "gettext.h"
>  #include <errno.h>
>  #include <mntent.h>
>  #include <sys/mount.h>
> diff --git a/compat/fsmonitor/fsm-settings-unix.c b/compat/fsmonitor/fsm-settings-unix.c
> index d16dca89416..c9b75aa44fe 100644
> --- a/compat/fsmonitor/fsm-settings-unix.c
> +++ b/compat/fsmonitor/fsm-settings-unix.c
> @@ -1,6 +1,9 @@
> +#include "git-compat-util.h"
> +#include "config.h"
>  #include "fsmonitor.h"
>  #include "fsmonitor-ipc.h"
>  #include "fsmonitor-path-utils.h"
> +#include <stdint.h>
>  
>   /*
>   * For the builtin FSMonitor, we create the Unix domain socket for the
> -- 
> gitgitgadget
> 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
4 participants