Add user/group/project space files to .zfs

Users who access OpenZFS across a network may not have access
to most administrative tools in OpenZFS. This commit exposes
one of those tools, user/group/project space & quota utilization,
to the .zfs directory within each file system dataset. These files
can be accessed over NFS. A module parameter is added to enable
or disable these files.

Signed-off-by: Sam Atkinson <samatk@amazon.com>
This commit is contained in:
Sam Atkinson 2024-01-11 14:30:42 +00:00
parent 8f2f6cd2ac
commit f5215349f8
11 changed files with 860 additions and 11 deletions

View File

@ -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 <linux/seq_file.h>
#include <linux/fs.h>
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])
])
])

View File

@ -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

View File

@ -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

View File

@ -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) { \

View File

@ -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:

View File

@ -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/");

View File

@ -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()

View File

@ -31,6 +31,7 @@
#include <sys/zfs_vfsops.h>
#include <sys/zfs_vnops.h>
#include <sys/zfs_ctldir.h>
#include <sys/zfs_quota.h>
#include <sys/zpl.h>
#include <sys/dmu.h>
#include <sys/dsl_dataset.h>
@ -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 = {};

View File

@ -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]

View File

@ -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 \

View File

@ -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"