summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--man/man5/zfs-module-parameters.515
-rw-r--r--module/zfs/zfs_ctldir.c124
-rw-r--r--module/zfs/zfs_vfsops.c2
3 files changed, 141 insertions, 0 deletions
diff --git a/man/man5/zfs-module-parameters.5 b/man/man5/zfs-module-parameters.5
index 2ceb65519..27b5346e2 100644
--- a/man/man5/zfs-module-parameters.5
+++ b/man/man5/zfs-module-parameters.5
@@ -958,6 +958,21 @@ Default value: \fB300\fR.
.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
\fBzfs_flags\fR (int)
.ad
.RS 12n
diff --git a/module/zfs/zfs_ctldir.c b/module/zfs/zfs_ctldir.c
index f0aff7b45..e54b0c16c 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;
@@ -672,6 +716,48 @@ out:
}
/*
+ * 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 "..".
*/
int
@@ -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 9ee7b2e8a..f105d9aed 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);