From ba7a55d93465725fd860066d95d4e910c3c027c2 Mon Sep 17 00:00:00 2001 From: Sean Eric Fagan Date: Mon, 23 Sep 2019 04:28:07 +0000 Subject: [PATCH] Add two options to allow mount to avoid covering up existing mount points. The two options are * nocover/cover: Prevent/allow mounting over an existing root mountpoint. E.g., "mount -t ufs -o nocover /dev/sd1a /usr/local" will fail if /usr/local is already a mountpoint. * emptydir/noemptydir: Prevent/allow mounting on a non-empty directory. E.g., "mount -t ufs -o emptydir /dev/sd1a /usr" will fail. Neither of these options is intended to be a default, for historical and compatibility reasons. Reviewed by: allanjude, kib Differential Revision: https://reviews.freebsd.org/D21458 --- lib/libc/sys/mount.2 | 16 +++++++++- sbin/mount/mntopts.h | 7 +++-- sbin/mount/mount.8 | 7 ++++- sbin/mount/mount.c | 4 +++ sys/kern/vfs_mount.c | 39 ++++++++++++++++++++---- sys/kern/vfs_subr.c | 70 ++++++++++++++++++++++++++++++++++++++++++++ sys/sys/mount.h | 4 ++- sys/sys/vnode.h | 1 + 8 files changed, 138 insertions(+), 10 deletions(-) diff --git a/lib/libc/sys/mount.2 b/lib/libc/sys/mount.2 index 2d296e6f5b1..6f5a199be9a 100644 --- a/lib/libc/sys/mount.2 +++ b/lib/libc/sys/mount.2 @@ -28,7 +28,7 @@ .\" @(#)mount.2 8.3 (Berkeley) 5/24/95 .\" $FreeBSD$ .\" -.Dd December 1, 2017 +.Dd August 28, 2019 .Dt MOUNT 2 .Os .Sh NAME @@ -157,6 +157,10 @@ mount even if some files are open for writing. Disable read clustering. .It Dv MNT_NOCLUSTERW Disable write clustering. +.It Dv MNT_NOCOVER +Do not mount over the root of another mount point. +.It Dv MNT_EMPTYDIR +Require an empty directory for the mount point directory. .El .Pp The flag @@ -260,6 +264,11 @@ is not a directory. .It Bq Er EBUSY Another process currently holds a reference to .Fa dir . +.It Bq Er EBUSY +The +.Dv MNT_NOCOVER +option was given, and the requested mount point +is already the root of another mount point. .It Bq Er EFAULT The .Fa dir @@ -280,6 +289,11 @@ The .Fa fspec argument is not a block device. +.It Bq Er ENOTEMPTY +The +.Dv MNT_EMPTYDIR +option was specified, and the requested mount point +is not an empty directory. .It Bq Er ENXIO The major device number of .Fa fspec diff --git a/sbin/mount/mntopts.h b/sbin/mount/mntopts.h index 924ead25389..183d6d9e501 100644 --- a/sbin/mount/mntopts.h +++ b/sbin/mount/mntopts.h @@ -65,7 +65,8 @@ struct mntopt { #define MOPT_UPDATE { "update", 0, MNT_UPDATE, 0 } #define MOPT_RO { "ro", 0, MNT_RDONLY, 0 } #define MOPT_RW { "rw", 1, MNT_RDONLY, 0 } - +#define MOPT_NOCOVER { "cover", 1, MNT_NOCOVER, 0 } +#define MOPT_EMPTYDIR { "emptydir", 0, MNT_EMPTYDIR, 0 } /* This is parsed by mount(8), but is ignored by specific mount_*(8)s. */ #define MOPT_AUTO { "auto", 0, 0, 0 } @@ -95,7 +96,9 @@ struct mntopt { MOPT_ACLS, \ MOPT_NFS4ACLS, \ MOPT_AUTOMOUNTED, \ - MOPT_UNTRUSTED + MOPT_UNTRUSTED, \ + MOPT_NOCOVER, \ + MOPT_EMPTYDIR void getmntopts(const char *, const struct mntopt *, int *, int *); void rmslashes(char *, char *); diff --git a/sbin/mount/mount.8 b/sbin/mount/mount.8 index 7ff94fb7c16..3aee1bb8615 100644 --- a/sbin/mount/mount.8 +++ b/sbin/mount/mount.8 @@ -28,7 +28,7 @@ .\" @(#)mount.8 8.8 (Berkeley) 6/16/94 .\" $FreeBSD$ .\" -.Dd March 22, 2017 +.Dd August 28, 2019 .Dt MOUNT 8 .Os .Sh NAME @@ -162,6 +162,8 @@ When used with the .Fl u flag, this is the same as specifying the options currently in effect for the mounted file system. +.It Cm emptydir +Require that the mount point directory be empty. .It Cm force The same as .Fl f ; @@ -237,6 +239,9 @@ flag. Disable read clustering. .It Cm noclusterw Disable write clustering. +.It Cm nocover +Do not mount if the requested mount point is already +the root of a mount point. .It Cm noexec Do not allow execution of any binaries on the mounted file system. This option is useful for a server that has file systems containing diff --git a/sbin/mount/mount.c b/sbin/mount/mount.c index 102e87009b0..938a220b41c 100644 --- a/sbin/mount/mount.c +++ b/sbin/mount/mount.c @@ -119,6 +119,8 @@ static struct opt { { MNT_AUTOMOUNTED, "automounted" }, { MNT_VERIFIED, "verified" }, { MNT_UNTRUSTED, "untrusted" }, + { MNT_NOCOVER, "nocover" }, + { MNT_EMPTYDIR, "emptydir" }, { 0, NULL } }; @@ -975,6 +977,8 @@ flags2opts(int flags) if (flags & MNT_ACLS) res = catopt(res, "acls"); if (flags & MNT_NFS4ACLS) res = catopt(res, "nfsv4acls"); if (flags & MNT_UNTRUSTED) res = catopt(res, "untrusted"); + if (flags & MNT_NOCOVER) res = catopt(res, "nocover"); + if (flags & MNT_EMPTYDIR) res = catopt(res, "emptydir"); return (res); } diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c index 76c483c5640..633fc27a6c9 100644 --- a/sys/kern/vfs_mount.c +++ b/sys/kern/vfs_mount.c @@ -668,19 +668,21 @@ vfs_donmount(struct thread *td, uint64_t fsflags, struct uio *fsoptions) * when we want to update the root filesystem. */ TAILQ_FOREACH_SAFE(opt, optlist, link, tmp_opt) { + int do_freeopt = 0; + if (strcmp(opt->name, "update") == 0) { fsflags |= MNT_UPDATE; - vfs_freeopt(optlist, opt); + do_freeopt = 1; } else if (strcmp(opt->name, "async") == 0) fsflags |= MNT_ASYNC; else if (strcmp(opt->name, "force") == 0) { fsflags |= MNT_FORCE; - vfs_freeopt(optlist, opt); + do_freeopt = 1; } else if (strcmp(opt->name, "reload") == 0) { fsflags |= MNT_RELOAD; - vfs_freeopt(optlist, opt); + do_freeopt = 1; } else if (strcmp(opt->name, "multilabel") == 0) fsflags |= MNT_MULTILABEL; @@ -741,7 +743,7 @@ vfs_donmount(struct thread *td, uint64_t fsflags, struct uio *fsoptions) autoro = false; } else if (strcmp(opt->name, "autoro") == 0) { - vfs_freeopt(optlist, opt); + do_freeopt = 1; autoro = true; } else if (strcmp(opt->name, "suiddir") == 0) @@ -752,8 +754,22 @@ vfs_donmount(struct thread *td, uint64_t fsflags, struct uio *fsoptions) fsflags |= MNT_UNION; else if (strcmp(opt->name, "automounted") == 0) { fsflags |= MNT_AUTOMOUNTED; - vfs_freeopt(optlist, opt); + do_freeopt = 1; + } else if (strcmp(opt->name, "nocover") == 0) { + fsflags |= MNT_NOCOVER; + do_freeopt = 1; + } else if (strcmp(opt->name, "cover") == 0) { + fsflags &= ~MNT_NOCOVER; + do_freeopt = 1; + } else if (strcmp(opt->name, "emptydir") == 0) { + fsflags |= MNT_EMPTYDIR; + do_freeopt = 1; + } else if (strcmp(opt->name, "noemptydir") == 0) { + fsflags &= ~MNT_EMPTYDIR; + do_freeopt = 1; } + if (do_freeopt) + vfs_freeopt(optlist, opt); } /* @@ -889,6 +905,14 @@ vfs_domount_first( ASSERT_VOP_ELOCKED(vp, __func__); KASSERT((fsflags & MNT_UPDATE) == 0, ("MNT_UPDATE shouldn't be here")); + if ((fsflags & MNT_EMPTYDIR) != 0) { + error = vfs_emptydir(vp); + if (error != 0) { + vput(vp); + return (error); + } + } + /* * If the jail of the calling thread lacks permission for this type of * file system, deny immediately. @@ -1229,6 +1253,11 @@ vfs_domount( NDFREE(&nd, NDF_ONLY_PNBUF); vp = nd.ni_vp; if ((fsflags & MNT_UPDATE) == 0) { + if ((vp->v_vflag & VV_ROOT) != 0 && + (fsflags & MNT_NOCOVER) != 0) { + vput(vp); + return (EBUSY); + } pathbuf = malloc(MNAMELEN, M_TEMP, M_WAITOK); strcpy(pathbuf, fspath); error = vn_path_to_global_path(td, vp, pathbuf, MNAMELEN); diff --git a/sys/kern/vfs_subr.c b/sys/kern/vfs_subr.c index 3bdc4d1e3f6..7d8ea63c7a6 100644 --- a/sys/kern/vfs_subr.c +++ b/sys/kern/vfs_subr.c @@ -5535,6 +5535,76 @@ filt_vfsvnode(struct knote *kn, long hint) return (res); } +/* + * Returns whether the directory is empty or not. + * If it is empty, the return value is 0; otherwise + * the return value is an error value (which may + * be ENOTEMPTY). + */ +int +vfs_emptydir(struct vnode *vp) +{ + struct uio uio; + struct iovec iov; + struct dirent *dirent, *dp, *endp; + int error, eof; + + error = 0; + eof = 0; + + ASSERT_VOP_LOCKED(vp, "vfs_emptydir"); + + dirent = malloc(sizeof(struct dirent), M_TEMP, M_WAITOK); + iov.iov_base = dirent; + iov.iov_len = sizeof(struct dirent); + + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_offset = 0; + uio.uio_resid = sizeof(struct dirent); + uio.uio_segflg = UIO_SYSSPACE; + uio.uio_rw = UIO_READ; + uio.uio_td = curthread; + + while (eof == 0 && error == 0) { + error = VOP_READDIR(vp, &uio, curthread->td_ucred, &eof, + NULL, NULL); + if (error != 0) + break; + endp = (void *)((uint8_t *)dirent + + sizeof(struct dirent) - uio.uio_resid); + for (dp = dirent; dp < endp; + dp = (void *)((uint8_t *)dp + GENERIC_DIRSIZ(dp))) { + if (dp->d_type == DT_WHT) + continue; + if (dp->d_namlen == 0) + continue; + if (dp->d_type != DT_DIR && + dp->d_type != DT_UNKNOWN) { + error = ENOTEMPTY; + break; + } + if (dp->d_namlen > 2) { + error = ENOTEMPTY; + break; + } + if (dp->d_namlen == 1 && + dp->d_name[0] != '.') { + error = ENOTEMPTY; + break; + } + if (dp->d_namlen == 2 && + dp->d_name[1] != '.') { + error = ENOTEMPTY; + break; + } + uio.uio_resid = sizeof(struct dirent); + } + } + free(dirent, M_TEMP); + return (error); +} + int vfs_read_dirent(struct vop_readdir_args *ap, struct dirent *dp, off_t off) { diff --git a/sys/sys/mount.h b/sys/sys/mount.h index 562fa191fe2..4b60055ca6d 100644 --- a/sys/sys/mount.h +++ b/sys/sys/mount.h @@ -373,9 +373,11 @@ void __mnt_vnode_markerfree_active(struct vnode **mvp, struct mount *); #define MNT_SNAPSHOT 0x0000000001000000ULL /* snapshot the filesystem */ #define MNT_NONBUSY 0x0000000004000000ULL /* check vnode use counts. */ #define MNT_BYFSID 0x0000000008000000ULL /* specify filesystem by ID. */ +#define MNT_NOCOVER 0x0000001000000000ULL /* Do not cover a mount point */ +#define MNT_EMPTYDIR 0x0000002000000000ULL /* Only mount on empty dir */ #define MNT_CMDFLAGS (MNT_UPDATE | MNT_DELEXPORT | MNT_RELOAD | \ MNT_FORCE | MNT_SNAPSHOT | MNT_NONBUSY | \ - MNT_BYFSID) + MNT_BYFSID | MNT_NOCOVER | MNT_EMPTYDIR) /* * Internal filesystem control flags stored in mnt_kern_flag. * diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h index 18134ecca81..967b1c066c9 100644 --- a/sys/sys/vnode.h +++ b/sys/sys/vnode.h @@ -930,6 +930,7 @@ int vfs_kqfilter(struct vop_kqfilter_args *); void vfs_mark_atime(struct vnode *vp, struct ucred *cred); struct dirent; int vfs_read_dirent(struct vop_readdir_args *ap, struct dirent *dp, off_t off); +int vfs_emptydir(struct vnode *vp); int vfs_unixify_accmode(accmode_t *accmode);