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
This commit is contained in:
sef 2019-09-23 04:28:07 +00:00
parent cb473cf663
commit f0e5ce5f10
8 changed files with 138 additions and 10 deletions

View File

@ -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

View File

@ -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 *);

View File

@ -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

View File

@ -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);
}

View File

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

View File

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

View File

@ -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.
*

View File

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