diff --git a/config/kernel-seq-read-iter.m4 b/config/kernel-seq-read-iter.m4 new file mode 100644 index 0000000000..dd2ad521cd --- /dev/null +++ b/config/kernel-seq-read-iter.m4 @@ -0,0 +1,27 @@ +dnl # +dnl # Linux 5.10 added the the seq_read_iter helper +dnl # +AC_DEFUN([ZFS_AC_KERNEL_SRC_SEQ_READ_ITER], [ + ZFS_LINUX_TEST_SRC([seq_file_has_seq_read_iter], [ + #include + #include + static const struct file_operations + fops __attribute__ ((unused)) = { + .read_iter = seq_read_iter + }; + ],[]) +]) + +AC_DEFUN([ZFS_AC_KERNEL_SEQ_READ_ITER], [ + dnl # + dnl # Linux 5.10 added the the seq_read_iter helper + dnl # + AC_MSG_CHECKING([whether seq_file's seq_read_iter() exists]) + ZFS_LINUX_TEST_RESULT([seq_file_has_seq_read_iter], [ + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_SEQ_READ_ITER, 1, + [seq_file has seq_read_iter()]) + ],[ + AC_MSG_RESULT([no]) + ]) +]) diff --git a/config/kernel.m4 b/config/kernel.m4 index e3f8645774..4c032628e3 100644 --- a/config/kernel.m4 +++ b/config/kernel.m4 @@ -166,6 +166,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_SRC], [ ZFS_AC_KERNEL_SRC_REGISTER_SYSCTL_TABLE ZFS_AC_KERNEL_SRC_COPY_SPLICE_READ ZFS_AC_KERNEL_SRC_SYNC_BDEV + ZFS_AC_KERNEL_SRC_SEQ_READ_ITER case "$host_cpu" in powerpc*) ZFS_AC_KERNEL_SRC_CPU_HAS_FEATURE @@ -314,6 +315,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_RESULT], [ ZFS_AC_KERNEL_REGISTER_SYSCTL_TABLE ZFS_AC_KERNEL_COPY_SPLICE_READ ZFS_AC_KERNEL_SYNC_BDEV + ZFS_AC_KERNEL_SEQ_READ_ITER case "$host_cpu" in powerpc*) ZFS_AC_KERNEL_CPU_HAS_FEATURE diff --git a/include/os/linux/zfs/sys/zfs_ctldir.h b/include/os/linux/zfs/sys/zfs_ctldir.h index ad16ab5e44..816e46702d 100644 --- a/include/os/linux/zfs/sys/zfs_ctldir.h +++ b/include/os/linux/zfs/sys/zfs_ctldir.h @@ -39,6 +39,11 @@ #define ZFS_CTLDIR_NAME ".zfs" #define ZFS_SNAPDIR_NAME "snapshot" #define ZFS_SHAREDIR_NAME "shares" +#define ZFS_SPACEDIR_NAME "space" +#define ZFS_QUOTADIR_NAME "quota" +#define ZFS_USERFILE_NAME "user" +#define ZFS_GROUPFILE_NAME "group" +#define ZFS_PROJECTFILE_NAME "project" #define zfs_has_ctldir(zdp) \ ((zdp)->z_id == ZTOZSB(zdp)->z_root && \ @@ -48,6 +53,7 @@ (ZTOZSB(zdp)->z_show_ctldir)) extern int zfs_expire_snapshot; +extern int zfs_ctldir_spacefiles; /* zfsctl generic functions */ extern int zfsctl_create(zfsvfs_t *); @@ -86,6 +92,16 @@ extern int zfsctl_shares_lookup(struct inode *dip, char *name, struct inode **ipp, int flags, cred_t *cr, int *direntflags, pathname_t *realpnp); +/* zfsctl '.zfs/space' functions */ +extern int zfsctl_spacedir_lookup(struct inode *dip, char *name, + struct inode **ipp, int flags, cred_t *cr, int *direntflags, + pathname_t *realpnp); + +/* zfsctl '.zfs/quota' functions */ +extern int zfsctl_quotadir_lookup(struct inode *dip, char *name, + struct inode **ipp, int flags, cred_t *cr, int *direntflags, + pathname_t *realpnp); + /* * These inodes numbers are reserved for the .zfs control directory. * It is important that they be no larger that 48-bits because only @@ -95,8 +111,22 @@ extern int zfsctl_shares_lookup(struct inode *dip, char *name, */ #define ZFSCTL_INO_ROOT 0x0000FFFFFFFFFFFFULL #define ZFSCTL_INO_SHARES 0x0000FFFFFFFFFFFEULL -#define ZFSCTL_INO_SNAPDIR 0x0000FFFFFFFFFFFDULL -#define ZFSCTL_INO_SNAPDIRS 0x0000FFFFFFFFFFFCULL +#define ZFSCTL_INO_SPACEDIR 0x0000FFFFFFFFFFFDULL +#define ZFSCTL_INO_SPACE_USER 0x0000FFFFFFFFFFFCULL +#define ZFSCTL_INO_SPACE_GROUP 0x0000FFFFFFFFFFFBULL +#define ZFSCTL_INO_SPACE_PROJ 0x0000FFFFFFFFFFFAULL +#define ZFSCTL_INO_QUOTADIR 0x0000FFFFFFFFFFF9ULL +#define ZFSCTL_INO_QUOTA_USER 0x0000FFFFFFFFFFF8ULL +#define ZFSCTL_INO_QUOTA_GROUP 0x0000FFFFFFFFFFF7ULL +#define ZFSCTL_INO_QUOTA_PROJ 0x0000FFFFFFFFFFF6ULL +/* + * snapdir inode numbers must be lowest; each snapshot gets its own + * directory and inode number, assigned by decrementing from + * ZFSCTL_INO_SNAPDIRS. + */ +#define ZFSCTL_INO_SNAPDIR 0x0000FFFFFFFFFFF5ULL +#define ZFSCTL_INO_SNAPDIRS 0x0000FFFFFFFFFFF4ULL + #define ZFSCTL_EXPIRE_SNAPSHOT 300 diff --git a/include/os/linux/zfs/sys/zpl.h b/include/os/linux/zfs/sys/zpl.h index 91a4751fff..ee3eda8fda 100644 --- a/include/os/linux/zfs/sys/zpl.h +++ b/include/os/linux/zfs/sys/zpl.h @@ -118,6 +118,30 @@ extern const struct inode_operations zpl_ops_snapdir; extern const struct file_operations zpl_fops_shares; extern const struct inode_operations zpl_ops_shares; +extern const struct file_operations zpl_fops_spacedir; +extern const struct inode_operations zpl_ops_spacedir; + +extern const struct file_operations zpl_fops_userspace_file; +extern const struct inode_operations zpl_ops_userspace_file; + +extern const struct file_operations zpl_fops_groupspace_file; +extern const struct inode_operations zpl_ops_groupspace_file; + +extern const struct file_operations zpl_fops_projectspace_file; +extern const struct inode_operations zpl_ops_projectspace_file; + +extern const struct file_operations zpl_fops_quotadir; +extern const struct inode_operations zpl_ops_quotadir; + +extern const struct file_operations zpl_fops_userquota_file; +extern const struct inode_operations zpl_ops_userquota_file; + +extern const struct file_operations zpl_fops_groupquota_file; +extern const struct inode_operations zpl_ops_groupquota_file; + +extern const struct file_operations zpl_fops_projectquota_file; +extern const struct inode_operations zpl_ops_projectquota_file; + #if defined(HAVE_VFS_ITERATE) || defined(HAVE_VFS_ITERATE_SHARED) #define ZPL_DIR_CONTEXT_INIT(_dirent, _actor, _pos) { \ diff --git a/man/man4/zfs.4 b/man/man4/zfs.4 index 30c168253f..1c48e04aa8 100644 --- a/man/man4/zfs.4 +++ b/man/man4/zfs.4 @@ -1375,6 +1375,20 @@ which have the .Em no_root_squash option set. . +.It Sy zfs_ctldir_spacefiles Ns = Ns Sy 0 Ns | Ns 1 Pq int +Enable the existence of (quota|space)/(user|group|project) files in the +.Sy .zfs +directory. +These files can be used in lieu of the +.Sy zfs-userspace(8) , +.Sy zfs-groupspace(8) , +and +.Sy zfs-projectspace(8) +tools to determine space and quota utilization for a given dataset. +These would normally be used when the dataset is being accessed over +a network, e.g. using NFS, where ZFS command line tools, libzfs, +etc. are not available. +. .It Sy zfs_flags Ns = Ns Sy 0 Pq int Set additional debugging flags. The following flags may be bitwise-ored together: diff --git a/module/os/linux/zfs/zfs_ctldir.c b/module/os/linux/zfs/zfs_ctldir.c index 54ed70d039..9a9921b398 100644 --- a/module/os/linux/zfs/zfs_ctldir.c +++ b/module/os/linux/zfs/zfs_ctldir.c @@ -111,6 +111,7 @@ static krwlock_t zfs_snapshot_lock; */ int zfs_expire_snapshot = ZFSCTL_EXPIRE_SNAPSHOT; static int zfs_admin_snapshot = 0; +int zfs_ctldir_spacefiles = 0; typedef struct { char *se_name; /* full snapshot name */ @@ -818,6 +819,14 @@ zfsctl_root_lookup(struct inode *dip, const char *name, struct inode **ipp, } else if (strcmp(name, ZFS_SHAREDIR_NAME) == 0) { *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_SHARES, &zpl_fops_shares, &zpl_ops_shares); + } else if (strcmp(name, ZFS_SPACEDIR_NAME) == 0 && + zfs_ctldir_spacefiles) { + *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_SPACEDIR, + &zpl_fops_spacedir, &zpl_ops_spacedir); + } else if (strcmp(name, ZFS_QUOTADIR_NAME) == 0 && + zfs_ctldir_spacefiles) { + *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_QUOTADIR, + &zpl_fops_quotadir, &zpl_ops_quotadir); } else { *ipp = NULL; } @@ -1282,6 +1291,74 @@ zfsctl_shares_lookup(struct inode *dip, char *name, struct inode **ipp, return (error); } +int +zfsctl_spacedir_lookup(struct inode *dip, char *name, struct inode **ipp, + int flags, cred_t *cr, int *direntflags, pathname_t *realpnp) +{ + zfsvfs_t *zfsvfs = ITOZSB(dip); + int error; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + if (strcmp(name, ZFS_USERFILE_NAME) == 0) { + *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_SPACE_USER, + &zpl_fops_userspace_file, &zpl_ops_userspace_file); + (*ipp)->i_mode = (S_IFREG|S_IRUGO); + } else if (strcmp(name, ZFS_GROUPFILE_NAME) == 0) { + *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_SPACE_GROUP, + &zpl_fops_groupspace_file, &zpl_ops_groupspace_file); + (*ipp)->i_mode = (S_IFREG|S_IRUGO); + } else if (strcmp(name, ZFS_PROJECTFILE_NAME) == 0) { + *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_SPACE_PROJ, + &zpl_fops_projectspace_file, &zpl_ops_projectspace_file); + (*ipp)->i_mode = (S_IFREG|S_IRUGO); + } else { + *ipp = NULL; + } + + if (*ipp == NULL) + error = SET_ERROR(ENOENT); + + zfs_exit(zfsvfs, FTAG); + + return (error); +} + +int +zfsctl_quotadir_lookup(struct inode *dip, char *name, struct inode **ipp, + int flags, cred_t *cr, int *direntflags, pathname_t *realpnp) +{ + zfsvfs_t *zfsvfs = ITOZSB(dip); + int error; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + if (strcmp(name, ZFS_USERFILE_NAME) == 0) { + *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_QUOTA_USER, + &zpl_fops_userquota_file, &zpl_ops_userquota_file); + (*ipp)->i_mode = (S_IFREG|S_IRUGO); + } else if (strcmp(name, ZFS_GROUPFILE_NAME) == 0) { + *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_QUOTA_GROUP, + &zpl_fops_groupquota_file, &zpl_ops_groupquota_file); + (*ipp)->i_mode = (S_IFREG|S_IRUGO); + } else if (strcmp(name, ZFS_PROJECTFILE_NAME) == 0) { + *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_QUOTA_PROJ, + &zpl_fops_projectquota_file, &zpl_ops_projectquota_file); + (*ipp)->i_mode = (S_IFREG|S_IRUGO); + } else { + *ipp = NULL; + } + + if (*ipp == NULL) + error = SET_ERROR(ENOENT); + + zfs_exit(zfsvfs, FTAG); + + return (error); +} + /* * Initialize the various pieces we'll need to create and manipulate .zfs * directories. Currently this is unused but available. @@ -1315,3 +1392,7 @@ 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"); + +module_param(zfs_ctldir_spacefiles, int, 0644); +MODULE_PARM_DESC(zfs_ctldir_spacefiles, + "Enable user/group/project space/quota files in .zfs/"); diff --git a/module/os/linux/zfs/zfs_vfsops.c b/module/os/linux/zfs/zfs_vfsops.c index 2015c20d73..1e8fcbd1b8 100644 --- a/module/os/linux/zfs/zfs_vfsops.c +++ b/module/os/linux/zfs/zfs_vfsops.c @@ -1760,13 +1760,37 @@ zfs_vget(struct super_block *sb, struct inode **ipp, fid_t *fidp) if ((err = zfs_enter(zfsvfs, FTAG)) != 0) return (err); /* A zero fid_gen means we are in the .zfs control directories */ - if (fid_gen == 0 && - (object == ZFSCTL_INO_ROOT || object == ZFSCTL_INO_SNAPDIR)) { + if (fid_gen == 0 && object >= ZFSCTL_INO_SNAPDIR && + object <= ZFSCTL_INO_ROOT && object != ZFSCTL_INO_SHARES) { *ipp = zfsvfs->z_ctldir; ASSERT(*ipp != NULL); if (object == ZFSCTL_INO_SNAPDIR) { - VERIFY(zfsctl_root_lookup(*ipp, "snapshot", ipp, - 0, kcred, NULL, NULL) == 0); + VERIFY0(zfsctl_root_lookup(*ipp, + ZFS_SNAPDIR_NAME, ipp, 0, kcred, NULL, NULL)); + } else if (object == ZFSCTL_INO_SPACEDIR) { + VERIFY0(zfsctl_root_lookup(*ipp, + ZFS_SPACEDIR_NAME, ipp, 0, kcred, NULL, NULL)); + } else if (object == ZFSCTL_INO_QUOTADIR) { + VERIFY0(zfsctl_root_lookup(*ipp, + ZFS_QUOTADIR_NAME, ipp, 0, kcred, NULL, NULL)); + } else if (object == ZFSCTL_INO_SPACE_USER) { + VERIFY0(zfsctl_spacedir_lookup(*ipp, + ZFS_USERFILE_NAME, ipp, 0, kcred, NULL, NULL)); + } else if (object == ZFSCTL_INO_SPACE_GROUP) { + VERIFY0(zfsctl_spacedir_lookup(*ipp, + ZFS_GROUPFILE_NAME, ipp, 0, kcred, NULL, NULL)); + } else if (object == ZFSCTL_INO_SPACE_PROJ) { + VERIFY0(zfsctl_spacedir_lookup(*ipp, + ZFS_PROJECTFILE_NAME, ipp, 0, kcred, NULL, NULL)); + } else if (object == ZFSCTL_INO_QUOTA_USER) { + VERIFY0(zfsctl_quotadir_lookup(*ipp, + ZFS_USERFILE_NAME, ipp, 0, kcred, NULL, NULL)); + } else if (object == ZFSCTL_INO_QUOTA_GROUP) { + VERIFY0(zfsctl_quotadir_lookup(*ipp, + ZFS_GROUPFILE_NAME, ipp, 0, kcred, NULL, NULL)); + } else if (object == ZFSCTL_INO_QUOTA_PROJ) { + VERIFY0(zfsctl_quotadir_lookup(*ipp, + ZFS_PROJECTFILE_NAME, ipp, 0, kcred, NULL, NULL)); } else { /* * Must have an existing ref, so igrab() diff --git a/module/os/linux/zfs/zpl_ctldir.c b/module/os/linux/zfs/zpl_ctldir.c index 8ee7fcecc7..981a024af6 100644 --- a/module/os/linux/zfs/zpl_ctldir.c +++ b/module/os/linux/zfs/zpl_ctldir.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -60,27 +61,55 @@ zpl_root_iterate(struct file *filp, zpl_dir_context_t *ctx) if ((error = zpl_enter(zfsvfs, FTAG)) != 0) return (error); - if (!zpl_dir_emit_dots(filp, ctx)) + if (!zpl_dir_emit_dots(filp, ctx)) { + error = SET_ERROR(-EIO); goto out; + } if (ctx->pos == 2) { if (!zpl_dir_emit(ctx, ZFS_SNAPDIR_NAME, - strlen(ZFS_SNAPDIR_NAME), ZFSCTL_INO_SNAPDIR, DT_DIR)) + strlen(ZFS_SNAPDIR_NAME), ZFSCTL_INO_SNAPDIR, DT_DIR)) { + error = SET_ERROR(-EIO); goto out; + } ctx->pos++; } if (ctx->pos == 3) { if (!zpl_dir_emit(ctx, ZFS_SHAREDIR_NAME, - strlen(ZFS_SHAREDIR_NAME), ZFSCTL_INO_SHARES, DT_DIR)) + strlen(ZFS_SHAREDIR_NAME), ZFSCTL_INO_SHARES, DT_DIR)) { + error = SET_ERROR(-EIO); goto out; + } ctx->pos++; } + + if (ctx->pos == 4 && zfs_ctldir_spacefiles) { + if (!zpl_dir_emit(ctx, ZFS_SPACEDIR_NAME, + strlen(ZFS_SPACEDIR_NAME), + ZFSCTL_INO_SPACEDIR, DT_DIR)) { + error = SET_ERROR(-EIO); + goto out; + } + + ctx->pos++; + } + + if (ctx->pos == 5 && zfs_ctldir_spacefiles) { + if (!zpl_dir_emit(ctx, ZFS_QUOTADIR_NAME, + strlen(ZFS_QUOTADIR_NAME), + ZFSCTL_INO_QUOTADIR, DT_DIR)) { + error = SET_ERROR(-EIO); + goto out; + } + + ctx->pos++; + } + out: zpl_exit(zfsvfs, FTAG); - return (error); } @@ -671,3 +700,551 @@ const struct inode_operations zpl_ops_shares = { .lookup = zpl_shares_lookup, .getattr = zpl_shares_getattr, }; + +static int +zpl_spacedir_iterate(struct file *filp, zpl_dir_context_t *ctx) +{ + zfsvfs_t *zfsvfs = ITOZSB(file_inode(filp)); + int error = 0; + + if ((error = zpl_enter(zfsvfs, FTAG)) != 0) + return (error); + + if (!zpl_dir_emit_dots(filp, ctx)) { + error = SET_ERROR(-EIO); + goto out; + } + + if (ctx->pos == 2) { + if (!zpl_dir_emit(ctx, ZFS_USERFILE_NAME, + strlen(ZFS_USERFILE_NAME), ZFSCTL_INO_SPACE_USER, + DT_REG)) { + error = SET_ERROR(-EIO); + goto out; + } + + ctx->pos++; + } + + if (ctx->pos == 3) { + if (!zpl_dir_emit(ctx, ZFS_GROUPFILE_NAME, + strlen(ZFS_GROUPFILE_NAME), ZFSCTL_INO_SPACE_GROUP, + DT_REG)) { + error = SET_ERROR(-EIO); + goto out; + } + + ctx->pos++; + } + + if (ctx->pos == 4) { + if (!zpl_dir_emit(ctx, ZFS_PROJECTFILE_NAME, + strlen(ZFS_PROJECTFILE_NAME), ZFSCTL_INO_SPACE_PROJ, + DT_REG)) { + error = SET_ERROR(-EIO); + goto out; + } + + ctx->pos++; + } + +out: + zpl_exit(zfsvfs, FTAG); + return (error); +} + +#if !defined(HAVE_VFS_ITERATE) && !defined(HAVE_VFS_ITERATE_SHARED) +static int +zpl_spacedir_readdir(struct file *filp, void *dirent, filldir_t filldir) +{ + zpl_dir_context_t ctx = + ZPL_DIR_CONTEXT_INIT(dirent, filldir, filp->f_pos); + int error; + + error = zpl_spacedir_iterate(filp, &ctx); + filp->f_pos = ctx.pos; + + return (error); +} +#endif /* !HAVE_VFS_ITERATE && !HAVE_VFS_ITERATE_SHARED */ + +static struct dentry * +zpl_spacedir_lookup(struct inode *dip, struct dentry *dentry, + unsigned int flags) +{ + cred_t *cr = CRED(); + struct inode *ip; + int error; + + crhold(cr); + error = -zfsctl_spacedir_lookup(dip, dname(dentry), &ip, 0, cr, + NULL, NULL); + ASSERT3S(error, <=, 0); + crfree(cr); + + if (error) { + if (error == -ENOENT) + return (d_splice_alias(NULL, dentry)); + else + return (ERR_PTR(error)); + } + + return (d_splice_alias(ip, dentry)); +} + +static int +#ifdef HAVE_USERNS_IOPS_GETATTR +zpl_spacedir_getattr_impl(struct user_namespace *user_ns, + const struct path *path, struct kstat *stat, u32 request_mask, + unsigned int query_flags) +#elif defined(HAVE_IDMAP_IOPS_GETATTR) +zpl_spacedir_getattr_impl(struct mnt_idmap *user_ns, + const struct path *path, struct kstat *stat, u32 request_mask, + unsigned int query_flags) +#else +zpl_spacedir_getattr_impl(const struct path *path, struct kstat *stat, + u32 request_mask, unsigned int query_flags) +#endif +{ + (void) request_mask, (void) query_flags; + struct inode *ip = path->dentry->d_inode; + +#if (defined(HAVE_USERNS_IOPS_GETATTR) || defined(HAVE_IDMAP_IOPS_GETATTR)) +#ifdef HAVE_GENERIC_FILLATTR_USERNS + generic_fillattr(user_ns, ip, stat); +#elif defined(HAVE_GENERIC_FILLATTR_IDMAP) + generic_fillattr(user_ns, ip, stat); +#elif defined(HAVE_GENERIC_FILLATTR_IDMAP_REQMASK) + generic_fillattr(user_ns, request_mask, ip, stat); +#else + (void) user_ns; +#endif +#else + generic_fillattr(ip, stat); +#endif + stat->atime = current_time(ip); + + return (0); +} +ZPL_GETATTR_WRAPPER(zpl_spacedir_getattr); + +/* + * The '.zfs/space' directory file operations. + */ +const struct file_operations zpl_fops_spacedir = { + .open = zpl_common_open, + .llseek = generic_file_llseek, + .read = generic_read_dir, +#ifdef HAVE_VFS_ITERATE_SHARED + .iterate_shared = zpl_spacedir_iterate, +#elif defined(HAVE_VFS_ITERATE) + .iterate = zpl_spacedir_iterate, +#else + .readdir = zpl_spacedir_readdir, +#endif + +}; + +/* + * The '.zfs/space' directory inode operations. + */ +const struct inode_operations zpl_ops_spacedir = { + .lookup = zpl_spacedir_lookup, + .getattr = zpl_spacedir_getattr, +}; + +static int +zpl_quotadir_iterate(struct file *filp, zpl_dir_context_t *ctx) +{ + zfsvfs_t *zfsvfs = ITOZSB(file_inode(filp)); + int error = 0; + + if ((error = zpl_enter(zfsvfs, FTAG)) != 0) + return (error); + + if (!zpl_dir_emit_dots(filp, ctx)) { + error = SET_ERROR(-EIO); + goto out; + } + + if (ctx->pos == 2) { + if (!zpl_dir_emit(ctx, ZFS_USERFILE_NAME, + strlen(ZFS_USERFILE_NAME), ZFSCTL_INO_QUOTA_USER, + DT_REG)) { + error = SET_ERROR(-EIO); + goto out; + } + + ctx->pos++; + } + + if (ctx->pos == 3) { + if (!zpl_dir_emit(ctx, ZFS_GROUPFILE_NAME, + strlen(ZFS_GROUPFILE_NAME), ZFSCTL_INO_QUOTA_GROUP, + DT_REG)) { + error = SET_ERROR(-EIO); + goto out; + } + + ctx->pos++; + } + + if (ctx->pos == 4) { + if (!zpl_dir_emit(ctx, ZFS_PROJECTFILE_NAME, + strlen(ZFS_PROJECTFILE_NAME), ZFSCTL_INO_QUOTA_PROJ, + DT_REG)) { + error = SET_ERROR(-EIO); + goto out; + } + + ctx->pos++; + } + +out: + zpl_exit(zfsvfs, FTAG); + return (error); +} + +#if !defined(HAVE_VFS_ITERATE) && !defined(HAVE_VFS_ITERATE_SHARED) +static int +zpl_quotadir_readdir(struct file *filp, void *dirent, filldir_t filldir) +{ + zpl_dir_context_t ctx = + ZPL_DIR_CONTEXT_INIT(dirent, filldir, filp->f_pos); + int error; + + error = zpl_quotadir_iterate(filp, &ctx); + filp->f_pos = ctx.pos; + + return (error); +} +#endif /* !HAVE_VFS_ITERATE && !HAVE_VFS_ITERATE_SHARED */ + +static struct dentry * +zpl_quotadir_lookup(struct inode *dip, struct dentry *dentry, + unsigned int flags) +{ + cred_t *cr = CRED(); + struct inode *ip; + int error; + + crhold(cr); + error = -zfsctl_quotadir_lookup(dip, dname(dentry), &ip, 0, cr, + NULL, NULL); + ASSERT3S(error, <=, 0); + crfree(cr); + + if (error) { + if (error == -ENOENT) + return (d_splice_alias(NULL, dentry)); + else + return (ERR_PTR(error)); + } + + return (d_splice_alias(ip, dentry)); +} + +static int +#ifdef HAVE_USERNS_IOPS_GETATTR +zpl_quotadir_getattr_impl(struct user_namespace *user_ns, + const struct path *path, struct kstat *stat, u32 request_mask, + unsigned int query_flags) +#elif defined(HAVE_IDMAP_IOPS_GETATTR) +zpl_quotadir_getattr_impl(struct mnt_idmap *user_ns, + const struct path *path, struct kstat *stat, u32 request_mask, + unsigned int query_flags) +#else +zpl_quotadir_getattr_impl(const struct path *path, struct kstat *stat, + u32 request_mask, unsigned int query_flags) +#endif +{ + (void) request_mask, (void) query_flags; + struct inode *ip = path->dentry->d_inode; + +#if (defined(HAVE_USERNS_IOPS_GETATTR) || defined(HAVE_IDMAP_IOPS_GETATTR)) +#ifdef HAVE_GENERIC_FILLATTR_USERNS + generic_fillattr(user_ns, ip, stat); +#elif defined(HAVE_GENERIC_FILLATTR_IDMAP) + generic_fillattr(user_ns, ip, stat); +#elif defined(HAVE_GENERIC_FILLATTR_IDMAP_REQMASK) + generic_fillattr(user_ns, request_mask, ip, stat); +#else + (void) user_ns; +#endif +#else + generic_fillattr(ip, stat); +#endif + stat->atime = current_time(ip); + + return (0); +} +ZPL_GETATTR_WRAPPER(zpl_quotadir_getattr); + +/* + * The '.zfs/quota' directory file operations. + */ +const struct file_operations zpl_fops_quotadir = { + .open = zpl_common_open, + .llseek = generic_file_llseek, + .read = generic_read_dir, +#ifdef HAVE_VFS_ITERATE_SHARED + .iterate_shared = zpl_quotadir_iterate, +#elif defined(HAVE_VFS_ITERATE) + .iterate = zpl_quotadir_iterate, +#else + .readdir = zpl_quotadir_readdir, +#endif + +}; + +/* + * The '.zfs/quota' directory inode operations. + */ +const struct inode_operations zpl_ops_quotadir = { + .lookup = zpl_quotadir_lookup, + .getattr = zpl_quotadir_getattr, +}; + +/* + * Helpers for: + * .zfs/(space|quota)user + * .zfs/(space|quota)group + * .zfs/(space|quota)project + */ +static int foreach_zfs_useracct(zfsvfs_t *zfsvfs, + zfs_userquota_prop_t type, uint64_t cookie, + int (*fn)(zfs_useracct_t *zua, zfs_userquota_prop_t type, void *v), + void *fn_arg) +{ + uint64_t cbufsize, bufsize = 16 * sizeof (zfs_useracct_t); + zfs_useracct_t *buf = (zfs_useracct_t *)kmem_alloc(bufsize, KM_SLEEP); + int err = 0; + for (;;) { + cbufsize = bufsize; + if (zfs_userspace_many(zfsvfs, type, &cookie, + buf, &cbufsize)) { + err = 1; + break; + }; + if (cbufsize == 0) { + break; + } + zfs_useracct_t *zua = buf; + while (cbufsize > 0) { + if (fn(zua, type, fn_arg)) { + err = 1; + } + zua++; + cbufsize -= sizeof (zfs_useracct_t); + } + } + kmem_free(buf, bufsize); + return (err); +} + +static int zua_nvlist_add(zfs_useracct_t *zua, + zfs_userquota_prop_t type, void *v) +{ + nvlist_t *ids = (nvlist_t *)v; + char name[MAXNAMELEN]; + (void) snprintf(name, sizeof (name), "%u", zua->zu_rid); + nvlist_t *spacelist; + if (nvlist_lookup_nvlist(ids, name, &spacelist) || + spacelist == NULL) { + VERIFY0(nvlist_alloc(&spacelist, NV_UNIQUE_NAME, KM_NOSLEEP)); + VERIFY0(nvlist_add_nvlist(ids, name, spacelist)); + /* lookup again because nvlist_add_nvlist does a deep-copy */ + VERIFY0(nvlist_lookup_nvlist(ids, name, &spacelist)); + } + VERIFY0(nvlist_add_uint64(spacelist, + zfs_userquota_prop_prefixes[type], zua->zu_space)); + return (0); +} + +static void seq_print_spaceval(struct seq_file *seq, nvlist_t *spacelist, + zfs_userquota_prop_t type) +{ + uint64_t spaceval; + seq_printf(seq, ","); + if (!nvlist_lookup_uint64(spacelist, + zfs_userquota_prop_prefixes[type], + &spaceval)) { + seq_printf(seq, "%llu", spaceval); + } +} + +static int zpl_quotaspace_show(struct seq_file *seq) +{ + zfs_userquota_prop_t *props = (zfs_userquota_prop_t *)seq->private; + zfsvfs_t *zfsvfs = ITOZSB(file_inode(seq->file)); + nvlist_t *ids; + unsigned int i; + VERIFY0(nvlist_alloc(&ids, NV_UNIQUE_NAME, 0)); + for (i = 0; i < 2; ++i) + VERIFY0(foreach_zfs_useracct(zfsvfs, props[i], 0, + zua_nvlist_add, ids)); + for (nvpair_t *idpair = nvlist_next_nvpair(ids, NULL); + idpair != NULL; idpair = nvlist_next_nvpair(ids, idpair)) { + const char *id = nvpair_name(idpair); + seq_printf(seq, "%s", id); + nvlist_t *spacelist; + VERIFY0(nvpair_value_nvlist(idpair, &spacelist)); + for (i = 0; i < 2; ++i) + seq_print_spaceval(seq, spacelist, props[i]); + seq_putc(seq, '\n'); + } + nvlist_free(ids); + return (0); +} + +static int zpl_quota_show(struct seq_file *seq, void *v) +{ + seq_printf(seq, "id,quota,objquota\n"); + return (zpl_quotaspace_show(seq)); +} + +static int zpl_space_show(struct seq_file *seq, void *v) +{ + seq_printf(seq, "id,used,objused\n"); + return (zpl_quotaspace_show(seq)); +} + +static zfs_userquota_prop_t userspace_props[2] = { + ZFS_PROP_USERUSED, + ZFS_PROP_USEROBJUSED +}; + +static zfs_userquota_prop_t groupspace_props[2] = { + ZFS_PROP_GROUPUSED, + ZFS_PROP_GROUPOBJUSED +}; + +static zfs_userquota_prop_t projectspace_props[2] = { + ZFS_PROP_PROJECTUSED, + ZFS_PROP_PROJECTOBJUSED +}; + +static zfs_userquota_prop_t userquota_props[2] = { + ZFS_PROP_USERQUOTA, + ZFS_PROP_USEROBJQUOTA +}; + +static zfs_userquota_prop_t groupquota_props[2] = { + ZFS_PROP_GROUPQUOTA, + ZFS_PROP_GROUPOBJQUOTA +}; + +static zfs_userquota_prop_t projectquota_props[2] = { + ZFS_PROP_PROJECTQUOTA, + ZFS_PROP_PROJECTOBJQUOTA +}; + +static int zpl_fops_userspace_open(struct inode *inode, struct file *file) +{ + return single_open(file, zpl_space_show, + (void *)userspace_props); +} + +static int zpl_fops_groupspace_open(struct inode *inode, struct file *file) +{ + return single_open(file, zpl_space_show, + (void *)groupspace_props); +} + +static int zpl_fops_projectspace_open(struct inode *inode, struct file *file) +{ + return single_open(file, zpl_space_show, + (void *)projectspace_props); +} + +static int zpl_fops_userquota_open(struct inode *inode, struct file *file) +{ + return single_open(file, zpl_quota_show, + (void *)userquota_props); +} + +static int zpl_fops_groupquota_open(struct inode *inode, struct file *file) +{ + return single_open(file, zpl_quota_show, + (void *)groupquota_props); +} + +static int zpl_fops_projectquota_open(struct inode *inode, struct file *file) +{ + return single_open(file, zpl_quota_show, + (void *)projectquota_props); +} + +/* .zfs/space/user */ +const struct file_operations zpl_fops_userspace_file = { + .open = zpl_fops_userspace_open, +#ifdef HAVE_SEQ_READ_ITER + .read_iter = seq_read_iter, +#endif + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* .zfs/space/group */ +const struct file_operations zpl_fops_groupspace_file = { + .open = zpl_fops_groupspace_open, + .read = seq_read, +#ifdef HAVE_SEQ_READ_ITER + .read_iter = seq_read_iter, +#endif + .llseek = seq_lseek, + .release = single_release, +}; + +/* .zfs/space/project */ +const struct file_operations zpl_fops_projectspace_file = { + .open = zpl_fops_projectspace_open, + .read = seq_read, +#ifdef HAVE_SEQ_READ_ITER + .read_iter = seq_read_iter, +#endif + .llseek = seq_lseek, + .release = single_release, +}; + +/* .zfs/quota/user */ +const struct file_operations zpl_fops_userquota_file = { + .open = zpl_fops_userquota_open, +#ifdef HAVE_SEQ_READ_ITER + .read_iter = seq_read_iter, +#endif + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* .zfs/quota/group */ +const struct file_operations zpl_fops_groupquota_file = { + .open = zpl_fops_groupquota_open, + .read = seq_read, +#ifdef HAVE_SEQ_READ_ITER + .read_iter = seq_read_iter, +#endif + .llseek = seq_lseek, + .release = single_release, +}; + +/* .zfs/quota/project */ +const struct file_operations zpl_fops_projectquota_file = { + .open = zpl_fops_projectquota_open, + .read = seq_read, +#ifdef HAVE_SEQ_READ_ITER + .read_iter = seq_read_iter, +#endif + .llseek = seq_lseek, + .release = single_release, +}; + + +const struct inode_operations zpl_ops_userspace_file = {}; +const struct inode_operations zpl_ops_groupspace_file = {}; +const struct inode_operations zpl_ops_projectspace_file = {}; +const struct inode_operations zpl_ops_userquota_file = {}; +const struct inode_operations zpl_ops_groupquota_file = {}; +const struct inode_operations zpl_ops_projectquota_file = {}; diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index a0b74ef4a8..b2bd947910 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -211,7 +211,7 @@ tags = ['functional', 'user_namespace'] [tests/functional/userquota:Linux] tests = ['groupspace_001_pos', 'groupspace_002_pos', 'groupspace_003_pos', - 'userquota_013_pos', 'userspace_003_pos'] + 'userquota_013_pos', 'userspace_003_pos', 'ctldir_files_001'] tags = ['functional', 'userquota'] [tests/functional/zvol/zvol_misc:Linux] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index fe9c921087..b80411c1f8 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -2039,6 +2039,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/userquota/userspace_encrypted.ksh \ functional/userquota/userspace_send_encrypted.ksh \ functional/userquota/userspace_encrypted_13709.ksh \ + functional/userquota/ctldir_files_001.ksh \ functional/vdev_zaps/cleanup.ksh \ functional/vdev_zaps/setup.ksh \ functional/vdev_zaps/vdev_zaps_001_pos.ksh \ diff --git a/tests/zfs-tests/tests/functional/userquota/ctldir_files_001.ksh b/tests/zfs-tests/tests/functional/userquota/ctldir_files_001.ksh new file mode 100755 index 0000000000..fc9e50a369 --- /dev/null +++ b/tests/zfs-tests/tests/functional/userquota/ctldir_files_001.ksh @@ -0,0 +1,69 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib + +# +# DESCRIPTION: +# Check the zfs space files in .zfs directory +# +# +# STRATEGY: +# 1. set zfs userquota to a fs +# 2. write some data to the fs with specified user and group +# 3. use zfs space files in .zfs to check the result +# + +function cleanup +{ + log_must cleanup_quota + echo 0 >$SPACEFILES_ENABLED_PARAM || log_fail +} + +log_onexit cleanup + +log_assert "Check the .zfs space files" + +typeset userquota=104857600 +typeset groupquota=524288000 + +log_must zfs set userquota@$QUSER1=$userquota $QFS +log_must zfs set groupquota@$QGROUP=$groupquota $QFS +mkmount_writable $QFS +log_must user_run $QUSER1 mkfile 50m $QFILE + +typeset SPACEFILES_ENABLED_PARAM=/sys/module/zfs/parameters/zfs_ctldir_spacefiles +echo 1 >$SPACEFILES_ENABLED_PARAM || log_fail +typeset mntp=$(get_prop mountpoint $QFS) +typeset user_id=$(id -u $QUSER1) || log_fail +typeset group_id=$(id -g $QUSER1) || log_fail + +sync_all_pools + +log_must eval "grep \"^$user_id,[[:digit:]]*\" $mntp/.zfs/space/user" +log_must eval "grep \"^$user_id,$userquota\" $mntp/.zfs/quota/user" +log_must eval "grep \"^$group_id,[[:digit:]]*\" $mntp/.zfs/space/group" +log_must eval "grep \"^$group_id,$groupquota\" $mntp/.zfs/quota/group" + +log_pass "Check the .zfs space files" +