Subject: Re: [PATCH v4 2/6] fsmonitor: determine if filesystem is local or remote

From: Ævar Arnfjörð Bjarmason <[email-masked]>
Date: Mon, 12 Dec 2022 11:24:27 +0100

On Wed, Nov 23 2022, Eric DeCosta via GitGitGadget wrote:

> From: Eric DeCosta <[email-masked]>
>
> 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-masked]>
> ---
>  compat/fsmonitor/fsm-path-utils-linux.c | 186 ++++++++++++++++++++++++
>  1 file changed, 186 insertions(+)
>  create mode 100644 compat/fsmonitor/fsm-path-utils-linux.c
>
> diff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c
> new file mode 100644
> index 00000000000..d3281422ebc
> --- /dev/null
> +++ b/compat/fsmonitor/fsm-path-utils-linux.c
> @@ -0,0 +1,186 @@
> +#include "fsmonitor.h"
> +#include "fsmonitor-path-utils.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)) {
> +		error_errno(_("statfs('%s') failed"), path);
> +		return -1;
> +	}

Nit: Drop the braces and do:

	if (statfs(...) == -1)
		return error_errno(...)

> +	switch (fs.f_type) {
> +		case 0x61636673:  /* ACFS */
> +		case 0x5346414F:  /* AFS */
> +		case 0x00C36400:  /* CEPH */
> +		case 0xFF534D42:  /* CIFS */
> +		case 0x73757245:  /* CODA */
> +		case 0x19830326:  /* FHGFS */
> +		case 0x1161970:   /* GFS */
> +		case 0x47504653:  /* GPFS */
> +		case 0x013111A8:  /* IBRIX */
> +		case 0x6B414653:  /* KAFS */
> +		case 0x0BD00BD0:  /* LUSTRE */
> +		case 0x564C:      /* NCP */
> +		case 0x6969:      /* NFS */
> +		case 0x6E667364:  /* NFSD */
> +		case 0x7461636f:  /* OCFS2 */
> +		case 0xAAD7AAEA:  /* PANFS */
> +		case 0x517B:      /* SMB */
> +		case 0xBEEFDEAD:  /* SNFS */
> +		case 0xFE534D42:  /* SMB2 */
> +		case 0xBACBACBC:  /* VMHGFS */
> +		case 0xA501FCF5:  /* VXFS */

So, before we'd compare against the name, but to avoid the GPLv3
copy/pasting we're now comparing against the fs.f_type.

If we are hardcoding them, our usual convention is to lower-case
hexdigits, so 0xbacbacbc not 0xBACBACBC.

But at least my statfs() manpage documents the named defines in
linux/magic.h for most of these. Why not use those?

> +			return 1;
> +		default:
> +			break;

You could just "return 0" here, and...

> +	}
> +
> +	return 0;

...drop this "return 0".

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

Misindentation.

> +{
> +	const char *const mounts = "/proc/mounts";
> +	const char *rp = real_pathdup(path, 1);
> +	struct mntent *ment = NULL;
> +	struct statvfs mntfs;
> +	FILE *fp;
> +	int found = 0;
> +	int dlen, plen, flen = 0;
> +
> +	ent->mnt_fsname = NULL;
> +	ent->mnt_dir = NULL;
> +	ent->mnt_type = NULL;
> +
> +	fp = setmntent(mounts, "r");
> +	if (!fp) {
> +		error_errno(_("setmntent('%s') failed"), mounts);
> +		return -1;

Ditto "return error_errno()"

> +	}
> +
> +	plen = strlen(rp);

Let's make "plen", "dlen" and "flen" a "size_t", not "int"
> +
> +	/* read all the mount information and compare to path */
> +	while ((ment = getmntent(fp)) != NULL) {

Drop the "!= NULL"

> +		if (statvfs(ment->mnt_dir, &mntfs)) {
> +			switch (errno) {
> +			case EPERM:
> +			case ESRCH:
> +			case EACCES:
> +				continue;
> +			default:
> +				error_errno(_("statvfs('%s') failed"), ment->mnt_dir);
> +				endmntent(fp);

Shouldn't we check the endmntent() error too? Now, from the manpage the
interface is funny, and always returns 1.

But since this is linux-specific code it seems safe enough to go with it
& glibc assumptions and:

	errno = 0;
        endmntent(fp);
        if (errno)
        	return error_errno(....);

I.e. it'll just call fclose(), which might set errno() on failure.

Maybe it's not worth it...

> +	if (statvfs(path, &fs))
> +		return error_errno(_("statvfs('%s') failed"), path);

Here you do use that "return error_errno(...)" pattern...", yay!

> +
> +
> +	if (find_mount(path, &fs, &ment) < 0) {
> +		free(ment.mnt_fsname);
> +		free(ment.mnt_dir);
> +		free(ment.mnt_type);
> +		return -1;
> +	}
> +
> +	trace_printf_key(&trace_fsmonitor,
> +			 "statvfs('%s') [flags 0x%08lx] '%s' '%s'",
> +			 path, fs.f_flag, ment.mnt_type, ment.mnt_fsname);
> +
> +	fs_info->is_remote = is_remote_fs(ment.mnt_dir);
> +	fs_info->typename = ment.mnt_fsname;
> +	free(ment.mnt_dir);
> +	free(ment.mnt_type);

If you're going to \n\n-seperate this and the trace_printf_key() above I
think moving the second free() here to that "block" would make sense,
sinec here is the last time we use mnt_dir, but the last time we used
mnt_type was in the trace_printf_key().

But...

> +
> +	if (fs_info->is_remote < 0) {
> +		free(ment.mnt_fsname);

...aren't you NULL init-ing these, why not just for all of these:

	goto error;

Then....
> +		return -1;
> +	}
> +
> +	trace_printf_key(&trace_fsmonitor,
> +				"'%s' is_remote: %d",
> +				path, fs_info->is_remote);
> +
> +	return 0;

Have this be:

	int ret = -1; /* earlier */

	ret = 0;
cleanup:
	free(...);
	free(...);
	return ret;

> +}
> +
> +int fsmonitor__is_fs_remote(const char *path)
> +{
> +	struct fs_info fs;
> +
> +	if (fsmonitor__get_fs_info(path, &fs))
> +		return -1;
> +
> +	free(fs.typename);

This will segfault if you take the part through fsmonitor__get_fs_info()
where we don't have the fs.typename yet, i.e. if statfs() fails.

There's the trivial NULL-init way to work around it, but I think this
suggests a leaky abstraction. If we fail to get the fs info, then the
function itself should have free'd that, shouldn't it?

> +/*
> + * No-op for now.
> + */
> +char *fsmonitor__resolve_alias(const char *path,
> +	const struct alias_info *info)

Ditto misindentatione