]> granicus.if.org Git - zfs/commitdiff
Support accessing .zfs/snapshot via NFS
authorBrian Behlendorf <behlendorf1@llnl.gov>
Fri, 28 Aug 2015 21:54:32 +0000 (14:54 -0700)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Fri, 4 Sep 2015 20:23:53 +0000 (13:23 -0700)
This patch is based on the previous work done by @andrey-ve and
@yshui.  It triggers the automount by using kern_path() to traverse
to the known snapshout mount point.  Once the snapshot is mounted
NFS can access the contents of the snapshot.

Allowing NFS clients to access to the .zfs/snapshot directory would
normally mean that a root user on a client mounting an export with
'no_root_squash' would be able to use mkdir/rmdir/mv to manipulate
snapshots on the server.  To prevent configuration mistakes a
zfs_admin_snapshot module option was added which disables the
mkdir/rmdir/mv functionally.  System administators desiring this
functionally must explicitly enable it.

Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #2797
Closes #1655
Closes #616

man/man5/zfs-module-parameters.5
module/zfs/zfs_ctldir.c
module/zfs/zfs_vfsops.c

index 2ceb6551961c01a9f579a263317921abdda18256..27b5346e264ca8d729c0d1f94f69fc36a535016f 100644 (file)
@@ -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
index f0aff7b4549f5b55034f99d6048df6feea5f0ab6..e54b0c16c16cddd5f52d777c45d099f49adbf2e4 100644 (file)
@@ -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");
index 9ee7b2e8ad9beebd991cde679a9547051598345e..f105d9aeda123d5b789e8785ff5e5cf77da07e45 100644 (file)
@@ -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);