Support accessing .zfs/snapshot via NFS
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
This commit is contained in:
parent
aa9b27080b
commit
0500e835af
|
@ -955,6 +955,21 @@ Seconds to expire .zfs/snapshot
|
||||||
Default value: \fB300\fR.
|
Default value: \fB300\fR.
|
||||||
.RE
|
.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
|
.sp
|
||||||
.ne 2
|
.ne 2
|
||||||
.na
|
.na
|
||||||
|
|
|
@ -107,6 +107,7 @@ static kmutex_t zfs_snapshot_lock;
|
||||||
* Control Directory Tunables (.zfs)
|
* Control Directory Tunables (.zfs)
|
||||||
*/
|
*/
|
||||||
int zfs_expire_snapshot = ZFSCTL_EXPIRE_SNAPSHOT;
|
int zfs_expire_snapshot = ZFSCTL_EXPIRE_SNAPSHOT;
|
||||||
|
int zfs_admin_snapshot = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dedicated task queue for unmounting snapshots.
|
* Dedicated task queue for unmounting snapshots.
|
||||||
|
@ -585,7 +586,45 @@ zfsctl_root(znode_t *zp)
|
||||||
igrab(ZTOZSB(zp)->z_ctldir);
|
igrab(ZTOZSB(zp)->z_ctldir);
|
||||||
return (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
|
int
|
||||||
zfsctl_fid(struct inode *ip, fid_t *fidp)
|
zfsctl_fid(struct inode *ip, fid_t *fidp)
|
||||||
{
|
{
|
||||||
|
@ -603,6 +642,11 @@ zfsctl_fid(struct inode *ip, fid_t *fidp)
|
||||||
return (SET_ERROR(ENOSPC));
|
return (SET_ERROR(ENOSPC));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (zfsctl_is_snapdir(ip)) {
|
||||||
|
ZFS_EXIT(zsb);
|
||||||
|
return (zfsctl_snapdir_fid(ip, fidp));
|
||||||
|
}
|
||||||
|
|
||||||
zfid = (zfid_short_t *)fidp;
|
zfid = (zfid_short_t *)fidp;
|
||||||
|
|
||||||
zfid->zf_len = SHORT_FID_LEN;
|
zfid->zf_len = SHORT_FID_LEN;
|
||||||
|
@ -671,6 +715,48 @@ out:
|
||||||
return (error);
|
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 "..".
|
* Special case the handling of "..".
|
||||||
*/
|
*/
|
||||||
|
@ -747,6 +833,9 @@ zfsctl_snapdir_rename(struct inode *sdip, char *snm,
|
||||||
char *to, *from, *real, *fsname;
|
char *to, *from, *real, *fsname;
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
|
if (!zfs_admin_snapshot)
|
||||||
|
return (EACCES);
|
||||||
|
|
||||||
ZFS_ENTER(zsb);
|
ZFS_ENTER(zsb);
|
||||||
|
|
||||||
to = kmem_alloc(MAXNAMELEN, KM_SLEEP);
|
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;
|
char *snapname, *real;
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
|
if (!zfs_admin_snapshot)
|
||||||
|
return (EACCES);
|
||||||
|
|
||||||
ZFS_ENTER(zsb);
|
ZFS_ENTER(zsb);
|
||||||
|
|
||||||
snapname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
|
snapname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
|
||||||
|
@ -864,6 +956,9 @@ zfsctl_snapdir_mkdir(struct inode *dip, char *dirname, vattr_t *vap,
|
||||||
char *dsname;
|
char *dsname;
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
|
if (!zfs_admin_snapshot)
|
||||||
|
return (EACCES);
|
||||||
|
|
||||||
dsname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
|
dsname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
|
||||||
|
|
||||||
if (zfs_component_namecheck(dirname, NULL, NULL) != 0) {
|
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);
|
zpl_follow_down_one(path);
|
||||||
snap_zsb = ITOZSB(path->dentry->d_inode);
|
snap_zsb = ITOZSB(path->dentry->d_inode);
|
||||||
|
snap_zsb->z_parent = zsb;
|
||||||
dentry = path->dentry;
|
dentry = path->dentry;
|
||||||
path->mnt->mnt_flags |= MNT_SHRINKABLE;
|
path->mnt->mnt_flags |= MNT_SHRINKABLE;
|
||||||
zpl_follow_up(path);
|
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);
|
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);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1127,5 +1248,8 @@ zfsctl_fini(void)
|
||||||
mutex_destroy(&zfs_snapshot_lock);
|
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_param(zfs_expire_snapshot, int, 0644);
|
||||||
MODULE_PARM_DESC(zfs_expire_snapshot, "Seconds to expire .zfs/snapshot");
|
MODULE_PARM_DESC(zfs_expire_snapshot, "Seconds to expire .zfs/snapshot");
|
||||||
|
|
|
@ -1600,6 +1600,8 @@ zfs_vget(struct super_block *sb, struct inode **ipp, fid_t *fidp)
|
||||||
zp_gen = zp_gen & gen_mask;
|
zp_gen = zp_gen & gen_mask;
|
||||||
if (zp_gen == 0)
|
if (zp_gen == 0)
|
||||||
zp_gen = 1;
|
zp_gen = 1;
|
||||||
|
if ((fid_gen == 0) && (zsb->z_root == object))
|
||||||
|
fid_gen = zp_gen;
|
||||||
if (zp->z_unlinked || zp_gen != fid_gen) {
|
if (zp->z_unlinked || zp_gen != fid_gen) {
|
||||||
dprintf("znode gen (%llu) != fid gen (%llu)\n", zp_gen,
|
dprintf("znode gen (%llu) != fid gen (%llu)\n", zp_gen,
|
||||||
fid_gen);
|
fid_gen);
|
||||||
|
|
Loading…
Reference in New Issue