git: a3f714c4ff8c - main - Add support for mounting single files in nullfs

From: Doug Rabson <dfr_at_FreeBSD.org>
Date: Mon, 19 Dec 2022 16:50:30 UTC
The branch main has been updated by dfr:

URL: https://cgit.FreeBSD.org/src/commit/?id=a3f714c4ff8cf3754520f330abe783aa6a06dcdb

commit a3f714c4ff8cf3754520f330abe783aa6a06dcdb
Author:     Doug Rabson <dfr@FreeBSD.org>
AuthorDate: 2022-11-07 16:56:09 +0000
Commit:     Doug Rabson <dfr@FreeBSD.org>
CommitDate: 2022-12-19 16:44:54 +0000

    Add support for mounting single files in nullfs
    
    My main use-case for this is to support mounting config files and secrets
    into OCI containers. My current workaround copies the files into the
    container which is messy and risks secrets leaking into container images
    if the cleanup fails.
    
    Reviewed by:    mjg, kib
    Tested by:      pho
    Differential Revision: https://reviews.freebsd.org/D37478
---
 sbin/mount/getmntopts.c          | 14 ++++++++++++++
 sbin/mount/mntopts.h             |  1 +
 sbin/mount/mount.c               | 23 ++++++++++++++++++++---
 sbin/mount_nullfs/mount_nullfs.8 | 11 +++++++++++
 sbin/mount_nullfs/mount_nullfs.c | 23 +++++++++++++++++++++--
 5 files changed, 67 insertions(+), 5 deletions(-)

diff --git a/sbin/mount/getmntopts.c b/sbin/mount/getmntopts.c
index fb739c6406ae..0ee6d99ed8b9 100644
--- a/sbin/mount/getmntopts.c
+++ b/sbin/mount/getmntopts.c
@@ -139,6 +139,20 @@ checkpath(const char *path, char *resolved)
 	return (0);
 }
 
+int
+checkpath_allow_file(const char *path, char *resolved)
+{
+	struct stat sb;
+
+	if (realpath(path, resolved) == NULL || stat(resolved, &sb) != 0)
+		return (1);
+	if (!S_ISDIR(sb.st_mode) && !S_ISREG(sb.st_mode)) {
+		errno = ENOTDIR;
+		return (1);
+	}
+	return (0);
+}
+
 void
 build_iovec(struct iovec **iov, int *iovlen, const char *name, void *val,
 	    size_t len)
diff --git a/sbin/mount/mntopts.h b/sbin/mount/mntopts.h
index 183d6d9e501d..1d8b80069355 100644
--- a/sbin/mount/mntopts.h
+++ b/sbin/mount/mntopts.h
@@ -103,6 +103,7 @@ struct mntopt {
 void getmntopts(const char *, const struct mntopt *, int *, int *);
 void rmslashes(char *, char *);
 int checkpath(const char *, char resolved_path[]);
+int checkpath_allow_file(const char *, char resolved_path[]);
 extern int getmnt_silent;
 void build_iovec(struct iovec **iov, int *iovlen, const char *name, void *val, size_t len);
 void build_iovec_argf(struct iovec **iov, int *iovlen, const char *name, const char *fmt, ...);
diff --git a/sbin/mount/mount.c b/sbin/mount/mount.c
index 6c986907bcda..7ac5cd965a8f 100644
--- a/sbin/mount/mount.c
+++ b/sbin/mount/mount.c
@@ -89,6 +89,7 @@ struct statfs *getmntpt(const char *);
 int	hasopt(const char *, const char *);
 int	ismounted(struct fstab *, struct statfs *, int);
 int	isremountable(const char *);
+int	allow_file_mount(const char *);
 void	mangle(char *, struct cpa *);
 char   *update_options(char *, char *, int);
 int	mountfs(const char *, const char *, const char *,
@@ -502,6 +503,15 @@ isremountable(const char *vfsname)
 	return (0);
 }
 
+int
+allow_file_mount(const char *vfsname)
+{
+
+	if (strcmp(vfsname, "nullfs") == 0)
+		return (1);
+	return (0);
+}
+
 int
 hasopt(const char *mntopts, const char *option)
 {
@@ -548,9 +558,16 @@ mountfs(const char *vfstype, const char *spec, const char *name, int flags,
 	static struct cpa mnt_argv;
 
 	/* resolve the mountpoint with realpath(3) */
-	if (checkpath(name, mntpath) != 0) {
-		xo_warn("%s", mntpath);
-		return (1);
+	if (allow_file_mount(vfstype)) {
+		if (checkpath_allow_file(name, mntpath) != 0) {
+			xo_warn("%s", mntpath);
+			return (1);
+		}
+	} else {
+		if (checkpath(name, mntpath) != 0) {
+			xo_warn("%s", mntpath);
+			return (1);
+		}
 	}
 	name = mntpath;
 
diff --git a/sbin/mount_nullfs/mount_nullfs.8 b/sbin/mount_nullfs/mount_nullfs.8
index 756b13a7ffdf..68c252c69d61 100644
--- a/sbin/mount_nullfs/mount_nullfs.8
+++ b/sbin/mount_nullfs/mount_nullfs.8
@@ -64,6 +64,17 @@ but in other respects it is indistinguishable from the original.
 .Pp
 The
 .Nm
+utility supports mounting both directories and single files.
+Both
+.Ar target
+and
+.Ar mount_point
+must be the same type.
+Mounting directories to files or files to
+directories is not supported.
+.Pp
+The
+.Nm
 file system differs from a traditional
 loopback file system in two respects: it is implemented using
 a stackable layers techniques, and its
diff --git a/sbin/mount_nullfs/mount_nullfs.c b/sbin/mount_nullfs/mount_nullfs.c
index 77ec0991ea9b..55d7ac982f70 100644
--- a/sbin/mount_nullfs/mount_nullfs.c
+++ b/sbin/mount_nullfs/mount_nullfs.c
@@ -48,6 +48,7 @@ static const char rcsid[] =
 
 #include <sys/param.h>
 #include <sys/mount.h>
+#include <sys/stat.h>
 #include <sys/uio.h>
 
 #include <err.h>
@@ -61,6 +62,14 @@ static const char rcsid[] =
 
 static void	usage(void) __dead2;
 
+static int
+stat_realpath(const char *path, char *resolved, struct stat *sbp)
+{
+	if (realpath(path, resolved) == NULL || stat(resolved, sbp) != 0)
+		return (1);
+	return (0);
+}
+
 int
 main(int argc, char *argv[])
 {
@@ -71,6 +80,8 @@ main(int argc, char *argv[])
 	char errmsg[255];
 	int ch, iovlen;
 	char nullfs[] = "nullfs";
+	struct stat target_stat;
+	struct stat mountpoint_stat;
 
 	iov = NULL;
 	iovlen = 0;
@@ -98,10 +109,18 @@ main(int argc, char *argv[])
 		usage();
 
 	/* resolve target and mountpoint with realpath(3) */
-	if (checkpath(argv[0], target) != 0)
+	if (stat_realpath(argv[0], target, &target_stat) != 0)
 		err(EX_USAGE, "%s", target);
-	if (checkpath(argv[1], mountpoint) != 0)
+	if (stat_realpath(argv[1], mountpoint, &mountpoint_stat) != 0)
 		err(EX_USAGE, "%s", mountpoint);
+	if (!S_ISDIR(target_stat.st_mode) && !S_ISREG(target_stat.st_mode))
+		errx(EX_USAGE, "%s: must be either a file or directory",
+		    target);
+	if ((target_stat.st_mode & S_IFMT) !=
+	    (mountpoint_stat.st_mode & S_IFMT))
+		errx(EX_USAGE,
+		    "%s: must be same type as %s (file or directory)",
+		    mountpoint, target);
 
 	build_iovec(&iov, &iovlen, "fstype", nullfs, (size_t)-1);
 	build_iovec(&iov, &iovlen, "fspath", mountpoint, (size_t)-1);