diff --git a/man/man5/zfs-module-parameters.5 b/man/man5/zfs-module-parameters.5 index 2ceb655196..27b5346e26 100644 --- a/man/man5/zfs-module-parameters.5 +++ b/man/man5/zfs-module-parameters.5 @@ -955,6 +955,21 @@ Seconds to expire .zfs/snapshot Default value: \fB300\fR. .RE +.sp +.ne 2 +.na +\fBzfs_admin_snapshot\fR (int) +.ad +.RS 12n +Allow the creation, removal, or renaming of entries in the .zfs/snapshot +directory to cause the creation, destruction, or renaming of snapshots. +When enabled this functionality works both locally and over NFS exports +which have the 'no_root_squash' option set. This functionality is disabled +by default. +.sp +Use \fB1\fR for yes and \fB0\fR for no (default). +.RE + .sp .ne 2 .na diff --git a/module/zfs/zfs_ctldir.c b/module/zfs/zfs_ctldir.c index f0aff7b454..e54b0c16c1 100644 --- a/module/zfs/zfs_ctldir.c +++ b/module/zfs/zfs_ctldir.c @@ -107,6 +107,7 @@ static kmutex_t zfs_snapshot_lock; * Control Directory Tunables (.zfs) */ int zfs_expire_snapshot = ZFSCTL_EXPIRE_SNAPSHOT; +int zfs_admin_snapshot = 0; /* * Dedicated task queue for unmounting snapshots. @@ -585,7 +586,45 @@ zfsctl_root(znode_t *zp) igrab(ZTOZSB(zp)->z_ctldir); return (ZTOZSB(zp)->z_ctldir); } +/* + * Generate a long fid which includes the root object and objset of a + * snapshot but not the generation number. For the root object the + * generation number is ignored when zero to avoid needing to open + * the dataset when generating fids for the snapshot names. + */ +static int +zfsctl_snapdir_fid(struct inode *ip, fid_t *fidp) +{ + zfs_sb_t *zsb = ITOZSB(ip); + zfid_short_t *zfid = (zfid_short_t *)fidp; + zfid_long_t *zlfid = (zfid_long_t *)fidp; + uint32_t gen = 0; + uint64_t object; + uint64_t objsetid; + int i; + object = zsb->z_root; + objsetid = ZFSCTL_INO_SNAPDIRS - ip->i_ino; + zfid->zf_len = LONG_FID_LEN; + + for (i = 0; i < sizeof (zfid->zf_object); i++) + zfid->zf_object[i] = (uint8_t)(object >> (8 * i)); + + for (i = 0; i < sizeof (zfid->zf_gen); i++) + zfid->zf_gen[i] = (uint8_t)(gen >> (8 * i)); + + for (i = 0; i < sizeof (zlfid->zf_setid); i++) + zlfid->zf_setid[i] = (uint8_t)(objsetid >> (8 * i)); + + for (i = 0; i < sizeof (zlfid->zf_setgen); i++) + zlfid->zf_setgen[i] = 0; + + return (0); +} + +/* + * Generate an appropriate fid for an entry in the .zfs directory. + */ int zfsctl_fid(struct inode *ip, fid_t *fidp) { @@ -603,6 +642,11 @@ zfsctl_fid(struct inode *ip, fid_t *fidp) return (SET_ERROR(ENOSPC)); } + if (zfsctl_is_snapdir(ip)) { + ZFS_EXIT(zsb); + return (zfsctl_snapdir_fid(ip, fidp)); + } + zfid = (zfid_short_t *)fidp; zfid->zf_len = SHORT_FID_LEN; @@ -671,6 +715,48 @@ out: return (error); } +/* + * Returns full path in full_path: "/pool/dataset/.zfs/snapshot/snap_name/" + */ +static int +zfsctl_snapshot_path_objset(zfs_sb_t *zsb, uint64_t objsetid, + int path_len, char *full_path) +{ + objset_t *os = zsb->z_os; + fstrans_cookie_t cookie; + char *snapname; + boolean_t case_conflict; + uint64_t id, pos = 0; + int error = 0; + + if (zsb->z_mntopts->z_mntpoint == NULL) + return (ENOENT); + + cookie = spl_fstrans_mark(); + snapname = kmem_alloc(MAXNAMELEN, KM_SLEEP); + + while (error == 0) { + dsl_pool_config_enter(dmu_objset_pool(os), FTAG); + error = dmu_snapshot_list_next(zsb->z_os, MAXNAMELEN, + snapname, &id, &pos, &case_conflict); + dsl_pool_config_exit(dmu_objset_pool(os), FTAG); + if (error) + goto out; + + if (id == objsetid) + break; + } + + memset(full_path, 0, path_len); + snprintf(full_path, path_len - 1, "%s/.zfs/snapshot/%s", + zsb->z_mntopts->z_mntpoint, snapname); +out: + kmem_free(snapname, MAXNAMELEN); + spl_fstrans_unmark(cookie); + + return (error); +} + /* * Special case the handling of "..". */ @@ -747,6 +833,9 @@ zfsctl_snapdir_rename(struct inode *sdip, char *snm, char *to, *from, *real, *fsname; int error; + if (!zfs_admin_snapshot) + return (EACCES); + ZFS_ENTER(zsb); to = kmem_alloc(MAXNAMELEN, KM_SLEEP); @@ -819,6 +908,9 @@ zfsctl_snapdir_remove(struct inode *dip, char *name, cred_t *cr, int flags) char *snapname, *real; int error; + if (!zfs_admin_snapshot) + return (EACCES); + ZFS_ENTER(zsb); snapname = kmem_alloc(MAXNAMELEN, KM_SLEEP); @@ -864,6 +956,9 @@ zfsctl_snapdir_mkdir(struct inode *dip, char *dirname, vattr_t *vap, char *dsname; int error; + if (!zfs_admin_snapshot) + return (EACCES); + dsname = kmem_alloc(MAXNAMELEN, KM_SLEEP); if (zfs_component_namecheck(dirname, NULL, NULL) != 0) { @@ -1013,6 +1108,7 @@ zfsctl_snapshot_mount(struct path *path, int flags) */ zpl_follow_down_one(path); snap_zsb = ITOZSB(path->dentry->d_inode); + snap_zsb->z_parent = zsb; dentry = path->dentry; path->mnt->mnt_flags |= MNT_SHRINKABLE; zpl_follow_up(path); @@ -1060,6 +1156,31 @@ zfsctl_lookup_objset(struct super_block *sb, uint64_t objsetid, zfs_sb_t **zsbp) } mutex_exit(&zfs_snapshot_lock); + /* + * Automount the snapshot given the objset id by constructing the + * full mount point and performing a traversal. + */ + if (error == ENOENT) { + struct path path; + char *mnt; + + mnt = kmem_alloc(MAXPATHLEN, KM_SLEEP); + error = zfsctl_snapshot_path_objset(sb->s_fs_info, objsetid, + MAXPATHLEN, mnt); + if (error) { + kmem_free(mnt, MAXPATHLEN); + return (SET_ERROR(error)); + } + + error = kern_path(mnt, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path); + if (error == 0) { + *zsbp = ITOZSB(path.dentry->d_inode); + path_put(&path); + } + + kmem_free(mnt, MAXPATHLEN); + } + return (error); } @@ -1127,5 +1248,8 @@ zfsctl_fini(void) mutex_destroy(&zfs_snapshot_lock); } +module_param(zfs_admin_snapshot, int, 0644); +MODULE_PARM_DESC(zfs_admin_snapshot, "Enable mkdir/rmdir/mv in .zfs/snapshot"); + module_param(zfs_expire_snapshot, int, 0644); MODULE_PARM_DESC(zfs_expire_snapshot, "Seconds to expire .zfs/snapshot"); diff --git a/module/zfs/zfs_vfsops.c b/module/zfs/zfs_vfsops.c index 9ee7b2e8ad..f105d9aeda 100644 --- a/module/zfs/zfs_vfsops.c +++ b/module/zfs/zfs_vfsops.c @@ -1600,6 +1600,8 @@ zfs_vget(struct super_block *sb, struct inode **ipp, fid_t *fidp) zp_gen = zp_gen & gen_mask; if (zp_gen == 0) zp_gen = 1; + if ((fid_gen == 0) && (zsb->z_root == object)) + fid_gen = zp_gen; if (zp->z_unlinked || zp_gen != fid_gen) { dprintf("znode gen (%llu) != fid gen (%llu)\n", zp_gen, fid_gen);