diff --git a/sbin/mount/getmntopts.c b/sbin/mount/getmntopts.c --- a/sbin/mount/getmntopts.c +++ b/sbin/mount/getmntopts.c @@ -139,6 +139,20 @@ 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 --- a/sbin/mount/mntopts.h +++ b/sbin/mount/mntopts.h @@ -103,6 +103,7 @@ 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 --- a/sbin/mount/mount.c +++ b/sbin/mount/mount.c @@ -89,6 +89,7 @@ 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 @@ 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 @@ 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 --- a/sbin/mount_nullfs/mount_nullfs.8 +++ b/sbin/mount_nullfs/mount_nullfs.8 @@ -64,6 +64,17 @@ .Pp The .Nm +utility supports mounting both directories and single files. +Both +.Ar target +and +.Ar mount_point +must be the same type. +Mounting directoriess 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 --- a/sbin/mount_nullfs/mount_nullfs.c +++ b/sbin/mount_nullfs/mount_nullfs.c @@ -48,6 +48,7 @@ #include #include +#include #include #include @@ -61,6 +62,14 @@ 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 @@ 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 @@ 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); diff --git a/sys/fs/nullfs/null_vfsops.c b/sys/fs/nullfs/null_vfsops.c --- a/sys/fs/nullfs/null_vfsops.c +++ b/sys/fs/nullfs/null_vfsops.c @@ -156,6 +156,17 @@ } } + /* + * Lower vnode must be the same type as the covered vnode - we + * don't allow mounting directories to files or vice versa. + */ + if ((lowerrootvp->v_type != VDIR && lowerrootvp->v_type != VREG) || + lowerrootvp->v_type != mp->mnt_vnodecovered->v_type) { + NULLFSDEBUG("nullfs_mount: target must be same type as fspath"); + vput(lowerrootvp); + return (EINVAL); + } + xmp = (struct null_mount *) malloc(sizeof(struct null_mount), M_NULLFSMNT, M_WAITOK | M_ZERO); @@ -492,4 +503,4 @@ .vfs_unlink_lowervp = nullfs_unlink_lowervp, }; -VFS_SET(null_vfsops, nullfs, VFCF_LOOPBACK | VFCF_JAIL); +VFS_SET(null_vfsops, nullfs, VFCF_LOOPBACK | VFCF_JAIL | VFCF_FILEMOUNT); diff --git a/sys/kern/vfs_cache.c b/sys/kern/vfs_cache.c --- a/sys/kern/vfs_cache.c +++ b/sys/kern/vfs_cache.c @@ -3810,6 +3810,71 @@ return (error); } +/* + * This is similar to vn_path_to_global_path but allows for regular + * files which may not be present in the cache. + * + * Requires a locked, referenced vnode. + * Vnode is re-locked on success or ENODEV, otherwise unlocked. + */ +int +vn_path_to_global_path_hardlink(struct thread *td, struct vnode *vp, + struct vnode *dvp, char *path, u_int pathlen, const char *leaf_name, + size_t leaf_length) +{ + struct nameidata nd; + struct vnode *vp1; + char *rpath, *fbuf; + size_t len; + int error; + + ASSERT_VOP_ELOCKED(vp, __func__); + + /* + * Construct global filesystem path from dvp, vp and leaf + * name. + */ + VOP_UNLOCK(vp); + error = vn_fullpath_hardlink(vp, dvp, leaf_name, leaf_length, + &rpath, &fbuf, &len); + + if (error != 0) { + vrele(vp); + goto out; + } + + if (strlen(rpath) >= pathlen) { + vrele(vp); + error = ENAMETOOLONG; + goto out; + } + + /* + * Re-lookup the vnode by path to detect a possible rename. + * As a side effect, the vnode is relocked. + * If vnode was renamed, return ENOENT. + */ + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | AUDITVNODE1, UIO_SYSSPACE, path); + error = namei(&nd); + if (error != 0) { + vrele(vp); + goto out; + } + NDFREE_PNBUF(&nd); + vp1 = nd.ni_vp; + vrele(vp); + if (vp1 == vp) + strcpy(path, rpath); + else { + vput(vp1); + error = ENOENT; + } + +out: + free(fbuf, M_TEMP); + return (error); +} + #ifdef DDB static void db_print_vpath(struct vnode *vp) @@ -5350,7 +5415,7 @@ vp = fpl->tvp; vp_seqc = fpl->tvp_seqc; - VNPASS(vp->v_type == VDIR || vp->v_type == VBAD, vp); + VNPASS(vp->v_type == VDIR || vp->v_type == VREG || vp->v_type == VBAD, vp); mp = atomic_load_ptr(&vp->v_mountedhere); if (__predict_false(mp == NULL)) { return (0); @@ -5407,7 +5472,7 @@ vp = fpl->tvp; vp_seqc = fpl->tvp_seqc; - VNPASS(vp->v_type == VDIR || vp->v_type == VBAD, vp); + VNPASS(vp->v_type == VDIR || vp->v_type == VREG || vp->v_type == VBAD, vp); mp = atomic_load_ptr(&vp->v_mountedhere); if (__predict_false(mp == NULL)) { return (0); diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c --- a/sys/kern/vfs_mount.c +++ b/sys/kern/vfs_mount.c @@ -1103,8 +1103,13 @@ error = priv_check_cred(td->td_ucred, PRIV_VFS_ADMIN); if (error == 0) error = vinvalbuf(vp, V_SAVE, 0, 0); - if (error == 0 && vp->v_type != VDIR) - error = ENOTDIR; + if (vfsp->vfc_flags & VFCF_FILEMOUNT) { + if (error == 0 && vp->v_type != VDIR && vp->v_type != VREG) + error = EINVAL; + } else { + if (error == 0 && vp->v_type != VDIR) + error = ENOTDIR; + } if (error == 0 && (fsflags & MNT_EMPTYDIR) != 0) error = vfs_emptydir(vp); if (error == 0) { @@ -1533,22 +1538,27 @@ /* * Get vnode to be covered or mount point's vnode in case of MNT_UPDATE. */ - NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | AUDITVNODE1, UIO_SYSSPACE, + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | AUDITVNODE1 | WANTPARENT, UIO_SYSSPACE, fspath); error = namei(&nd); if (error != 0) return (error); - NDFREE_PNBUF(&nd); vp = nd.ni_vp; if ((fsflags & MNT_UPDATE) == 0) { if ((vp->v_vflag & VV_ROOT) != 0 && (fsflags & MNT_NOCOVER) != 0) { vput(vp); - return (EBUSY); + error = EBUSY; + goto out; } pathbuf = malloc(MNAMELEN, M_TEMP, M_WAITOK); strcpy(pathbuf, fspath); - error = vn_path_to_global_path(td, vp, pathbuf, MNAMELEN); + if (vp->v_type == VDIR) + error = vn_path_to_global_path(td, vp, pathbuf, MNAMELEN); + else + error = vn_path_to_global_path_hardlink(td, vp, + nd.ni_dvp, pathbuf, MNAMELEN, + nd.ni_cnd.cn_nameptr, nd.ni_cnd.cn_namelen); if (error == 0) { error = vfs_domount_first(td, vfsp, pathbuf, vp, fsflags, optlist); @@ -1557,6 +1567,10 @@ } else error = vfs_domount_update(td, vp, fsflags, optlist); +out: + NDFREE_PNBUF(&nd); + vrele(nd.ni_dvp); + return (error); } diff --git a/sys/sys/mount.h b/sys/sys/mount.h --- a/sys/sys/mount.h +++ b/sys/sys/mount.h @@ -678,6 +678,7 @@ #define VFCF_DELEGADMIN 0x00800000 /* supports delegated administration */ #define VFCF_SBDRY 0x01000000 /* Stop at Boundary: defer stop requests to kernel->user (AST) transition */ +#define VFCF_FILEMOUNT 0x02000000 /* allow mounting files */ typedef uint32_t fsctlop_t; diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h --- a/sys/sys/vnode.h +++ b/sys/sys/vnode.h @@ -714,6 +714,9 @@ int vn_commname(struct vnode *vn, char *buf, u_int buflen); int vn_path_to_global_path(struct thread *td, struct vnode *vp, char *path, u_int pathlen); +int vn_path_to_global_path_hardlink(struct thread *td, struct vnode *vp, + struct vnode *dvp, char *path, u_int pathlen, const char *leaf_name, + size_t leaf_length); int vaccess(enum vtype type, mode_t file_mode, uid_t file_uid, gid_t file_gid, accmode_t accmode, struct ucred *cred); int vaccess_vexec_smr(mode_t file_mode, uid_t file_uid, gid_t file_gid,