Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support .zfs/snapshot access via NFS #1655

Closed
wants to merge 9 commits into from
2 changes: 2 additions & 0 deletions include/sys/zfs_ctldir.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ typedef struct {
struct inode *se_inode;
taskqid_t se_taskqid;
avl_node_t se_node;
struct dentry *se_root_dentry;
} zfs_snapentry_t;

/* zfsctl generic functions */
extern int snapentry_compare(const void *a, const void *b);
extern boolean_t zfsctl_is_node(struct inode *ip);
extern boolean_t zfsctl_is_snapdir(struct inode *ip);
extern boolean_t zfsctl_is_ctl(struct inode *ip);
extern void zfsctl_inode_inactive(struct inode *ip);
extern void zfsctl_inode_destroy(struct inode *ip);
extern int zfsctl_create(zfs_sb_t *zsb);
Expand Down
2 changes: 2 additions & 0 deletions include/sys/zfs_vfsops.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ typedef struct zfs_sb {
sa_attr_type_t *z_attr_table; /* SA attr mapping->id */
#define ZFS_OBJ_MTX_SZ 256
kmutex_t z_hold_mtx[ZFS_OBJ_MTX_SZ]; /* znode hold locks */
char z_mnt_path[PATH_MAX]; /* path where this sb mounted */
struct vfsmount *z_mnt;
} zfs_sb_t;

#define ZFS_SUPER_MAGIC 0x2fc12fc1
Expand Down
203 changes: 155 additions & 48 deletions module/zfs/zfs_ctldir.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ zfsctl_is_snapdir(struct inode *ip)
return (zfsctl_is_node(ip) && (ip->i_ino <= ZFSCTL_INO_SNAPDIRS));
}

boolean_t
zfsctl_is_ctl(struct inode *ip)
{
return (zfsctl_is_node(ip) && (ip->i_ino == ZFSCTL_INO_ROOT ||
ip->i_ino == ZFSCTL_INO_SNAPDIR ||
ip->i_ino == ZFSCTL_INO_SHARES));
}

/*
* Allocate a new inode with the passed id and ops.
*/
Expand Down Expand Up @@ -820,6 +828,7 @@ zfsctl_mount_snapshot(struct path *path, int flags)
char *argv[] = { "/bin/sh", "-c", NULL, NULL };
char *envp[] = { NULL };
int error;
struct path mnt_path = *path;

ZFS_ENTER(zsb);

Expand Down Expand Up @@ -859,6 +868,14 @@ zfsctl_mount_snapshot(struct path *path, int flags)
error = 0;
mutex_enter(&zsb->z_ctldir_lock);

path_get(&mnt_path);
if (!follow_down(&mnt_path)) {
printk("ZFS: snapshot %s auto mounted at %s unexpectedly "
"unmounted\n", full_name, full_path);
error = ENOENT;
goto mutex_error;
}

/*
* Ensure a previous entry does not exist, if it does safely remove
* it any cancel the outstanding expiration. This can occur when a
Expand All @@ -876,12 +893,23 @@ zfsctl_mount_snapshot(struct path *path, int flags)
sep->se_name = full_name;
sep->se_path = full_path;
sep->se_inode = ip;
sep->se_root_dentry = mnt_path.dentry;

/*
* Ensure MNT_SHRINKABLE is set on snapshots to ensure they are
* unmounted automatically with the parent file system.
*/
mnt_path.mnt->mnt_flags |= MNT_SHRINKABLE;

path_put(&mnt_path);

avl_add(&zsb->z_ctldir_snaps, sep);

sep->se_taskqid = taskq_dispatch_delay(zfs_expire_taskq,
zfsctl_expire_snapshot, sep, TQ_SLEEP,
ddi_get_lbolt() + zfs_expire_snapshot * HZ);

mutex_error:
mutex_exit(&zsb->z_ctldir_lock);
error:
if (error) {
Expand All @@ -894,77 +922,156 @@ zfsctl_mount_snapshot(struct path *path, int flags)
return (error);
}

/*
* Check if this super block has a matching objset id.
*/
static int
zfsctl_test_super(struct super_block *sb, void *objsetidp)
static char*
zfsctl_get_mnt_path(zfs_sb_t *zsb)
{
zfs_sb_t *zsb = sb->s_fs_info;
uint64_t objsetid = *(uint64_t *)objsetidp;
int error;
struct path path;

if (zsb->z_mnt_path[0] != 0)
return zsb->z_mnt_path;

path.mnt = zsb->z_mnt;
path.dentry = zsb->z_mnt->mnt_root;
error = zfsctl_snapshot_zpath(&path, sizeof(zsb->z_mnt_path),
zsb->z_mnt_path);
if (error) {
zsb->z_mnt_path[0] = 0;
return ERR_PTR(error);
}

return (dmu_objset_id(zsb->z_os) == objsetid);
return zsb->z_mnt_path;
}

/*
* Prevent a new super block from being allocated if an existing one
* could not be located. We only want to preform a lookup operation.
*/
static int
zfsctl_set_super(struct super_block *sb, void *objsetidp)
zfsctl_get_snapshot_name(zfs_sb_t *zsb, uint64_t objsetid, char *name)
{
return (-EEXIST);
int error, ret = ENOENT;
uint64_t id;
uint64_t cookie = 0;
boolean_t case_conflict;

do {
error = -dmu_snapshot_list_next(zsb->z_os, MAXNAMELEN,
name, &id, &cookie, &case_conflict);

if (error == 0 && id == objsetid) {
ret = 0;
break;
}
} while (error == 0);

return ret;
}

int
zfsctl_lookup_objset(struct super_block *sb, uint64_t objsetid, zfs_sb_t **zsbp)
static int
zfsctl_lookup_snapshot_path(zfs_sb_t *zsb, uint64_t objsetid)
{
zfs_sb_t *zsb = sb->s_fs_info;
struct super_block *sbp;
zfs_snapentry_t *sep;
uint64_t id;
int error;
struct nameidata nd;
char *mnt_path;
char *path_buff = NULL;
char *snapname = NULL;
int error, ret;

mnt_path = zfsctl_get_mnt_path(zsb);
if (IS_ERR(mnt_path))
return PTR_ERR(mnt_path);
else if (mnt_path == NULL)
return EINVAL;

path_buff = kmem_zalloc(PATH_MAX, KM_SLEEP);
snapname = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
if (!path_buff || !snapname) {
error = ENOMEM;
goto out_path_buff;
}

ASSERT(zsb->z_ctldir != NULL);
error = zfsctl_get_snapshot_name(zsb, objsetid, snapname);
if (error)
goto out_path_buff;

ret = snprintf(path_buff, PATH_MAX, "%s/%s/%s/%s",
mnt_path, ZFS_CTLDIR_NAME, ZFS_SNAPDIR_NAME, snapname);
if (ret > (PATH_MAX - 1)) {
error = EINVAL;
goto out_path_buff;
}

error = path_lookup(path_buff, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &nd);
if (!error)
path_put(&nd.path);

out_path_buff:
kmem_free(path_buff, PATH_MAX);
kmem_free(snapname, MAXNAMELEN);

return error;
}

static zfs_sb_t*
zfsctl_get_zsb(zfs_sb_t *zsb, uint64_t objsetid)
{
int error;
zfs_snapentry_t *sep = NULL;
zfs_sb_t *snap_zsb = NULL;

mutex_enter(&zsb->z_ctldir_lock);

/*
* Verify that the snapshot is mounted.
*/
sep = avl_first(&zsb->z_ctldir_snaps);
while (sep != NULL) {
error = dmu_snapshot_lookup(zsb->z_os, sep->se_name, &id);
if (error)
goto out;
uint64_t id;
char *sname_simple = strchr(sep->se_name, '@');

if (id == objsetid)
if (!sname_simple) {
snap_zsb = ERR_PTR(EINVAL);
break;
}

sname_simple++;
error = dmu_snapshot_lookup(zsb->z_os, sname_simple, &id);
if (error) {
snap_zsb = ERR_PTR(error);
break;
}

if (id == objsetid) {
snap_zsb = sep->se_root_dentry->d_sb->s_fs_info;
break;
}

sep = AVL_NEXT(&zsb->z_ctldir_snaps, sep);
}

if (sep != NULL) {
/*
* Lookup the mounted root rather than the covered mount
* point. This may fail if the snapshot has just been
* unmounted by an unrelated user space process. This
* race cannot occur to an expired mount point because
* we hold the zsb->z_ctldir_lock to prevent the race.
*/
sbp = zpl_sget(&zpl_fs_type, zfsctl_test_super,
zfsctl_set_super, 0, &id);
if (IS_ERR(sbp)) {
error = -PTR_ERR(sbp);
} else {
*zsbp = sbp->s_fs_info;
deactivate_super(sbp);
}
} else {
error = SET_ERROR(EINVAL);
mutex_exit(&zsb->z_ctldir_lock);

return snap_zsb;
}

int
zfsctl_lookup_objset(struct super_block *sb, uint64_t objsetid, zfs_sb_t **zsbp)
{
zfs_sb_t *zsb = sb->s_fs_info;
int error = 0;

ASSERT(zsb->z_ctldir != NULL);

*zsbp = zfsctl_get_zsb(zsb, objsetid);
if (*zsbp == NULL) {
error = zfsctl_lookup_snapshot_path(zsb, objsetid);
if (error)
goto out;

*zsbp = zfsctl_get_zsb(zsb, objsetid);
}

if (IS_ERR(*zsbp))
error = PTR_ERR(*zsbp);
else if (*zsbp == NULL)
error = ENOENT;
else
error = 0;

out:
mutex_exit(&zsb->z_ctldir_lock);
ASSERT3S(error, >=, 0);

return (error);
Expand Down
36 changes: 25 additions & 11 deletions module/zfs/zfs_vfsops.c
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,8 @@ zfs_domount(struct super_block *sb, void *data, int silent)

if (!zsb->z_issnap)
zfsctl_create(zsb);
else
dget(sb->s_root);
out:
if (error) {
dmu_objset_disown(zsb->z_os, zsb);
Expand All @@ -1319,8 +1321,14 @@ zfs_preumount(struct super_block *sb)
{
zfs_sb_t *zsb = sb->s_fs_info;

if (zsb != NULL && zsb->z_ctldir != NULL)
if(!zsb)
return;

if (zsb->z_ctldir != NULL)
zfsctl_destroy(zsb);

if (zsb->z_issnap)
dput(sb->s_root);
}
EXPORT_SYMBOL(zfs_preumount);

Expand Down Expand Up @@ -1425,18 +1433,24 @@ zfs_vget(struct super_block *sb, struct inode **ipp, fid_t *fidp)
}

/* A zero fid_gen means we are in the .zfs control directories */
if (fid_gen == 0 &&
(object == ZFSCTL_INO_ROOT || object == ZFSCTL_INO_SNAPDIR)) {
*ipp = zsb->z_ctldir;
ASSERT(*ipp != NULL);
if (object == ZFSCTL_INO_SNAPDIR) {
VERIFY(zfsctl_root_lookup(*ipp, "snapshot", ipp,
0, kcred, NULL, NULL) == 0);
} else {
if (fid_gen == 0) {
if (object == ZFSCTL_INO_ROOT || object == ZFSCTL_INO_SNAPDIR) {
*ipp = zsb->z_ctldir;
ASSERT(*ipp != NULL);
if (object == ZFSCTL_INO_SNAPDIR) {
VERIFY(zfsctl_root_lookup(*ipp, "snapshot", ipp,
0, kcred, NULL, NULL) == 0);
} else {
igrab(*ipp);
}
ZFS_EXIT(zsb);
return (0);
} else if (zsb->z_issnap) {
*ipp = zsb->z_sb->s_root->d_inode;
igrab(*ipp);
ZFS_EXIT(zsb);
return (0);
}
ZFS_EXIT(zsb);
return (0);
}

gen_mask = -1ULL >> (64 - 8 * i);
Expand Down
Loading