Skip to content

Commit

Permalink
Add user/group/project space files to .zfs
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
atkinsam committed Jan 19, 2024
1 parent a0b2a93 commit c19ffe2
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 3 deletions.
12 changes: 10 additions & 2 deletions include/os/linux/zfs/sys/zfs_ctldir.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
#define ZFS_CTLDIR_NAME ".zfs"
#define ZFS_SNAPDIR_NAME "snapshot"
#define ZFS_SHAREDIR_NAME "shares"
#define ZFS_USERSPACEFILE_NAME "userspace"
#define ZFS_GROUPSPACEFILE_NAME "groupspace"
#define ZFS_PROJSPACEFILE_NAME "projectspace"

#define zfs_has_ctldir(zdp) \
((zdp)->z_id == ZTOZSB(zdp)->z_root && \
Expand All @@ -48,6 +51,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 *);
Expand Down Expand Up @@ -95,8 +99,12 @@ 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_USERSPACE 0x0000FFFFFFFFFFFDULL
#define ZFSCTL_INO_GROUPSPACE 0x0000FFFFFFFFFFFCULL
#define ZFSCTL_INO_PROJSPACE 0x0000FFFFFFFFFFFBULL
#define ZFSCTL_INO_SNAPDIR 0x0000FFFFFFFFFFFAULL
#define ZFSCTL_INO_SNAPDIRS 0x0000FFFFFFFFFFF9ULL


#define ZFSCTL_EXPIRE_SNAPSHOT 300

Expand Down
9 changes: 9 additions & 0 deletions include/os/linux/zfs/sys/zpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ 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_userspacefile;
extern const struct inode_operations zpl_ops_userspace_file;

extern const struct file_operations zpl_fops_groupspacefile;
extern const struct inode_operations zpl_ops_groupspace_file;

extern const struct file_operations zpl_fops_projectspacefile;
extern const struct inode_operations zpl_ops_projectspace_file;

#if defined(HAVE_VFS_ITERATE) || defined(HAVE_VFS_ITERATE_SHARED)

#define ZPL_DIR_CONTEXT_INIT(_dirent, _actor, _pos) { \
Expand Down
19 changes: 19 additions & 0 deletions man/man4/zfs.4
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,25 @@ 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 three files in the
.Sy .zfs
directory:
.Sy userspace ,
.Sy groupspace ,
and
.Sy projectspace .
These files can be used in lieu of the
.Sy zfs-userspace(8) ,
.Sy zfs-groupspace(8) ,
and
.Sy zfs-projectspace(8)
tools, respectively, 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:
Expand Down
20 changes: 20 additions & 0 deletions module/os/linux/zfs/zfs_ctldir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -818,6 +819,21 @@ 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_USERSPACEFILE_NAME) == 0 &&
zfs_ctldir_spacefiles) {
*ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_USERSPACE,
&zpl_fops_userspacefile, &zpl_ops_userspace_file);
(*ipp)->i_mode = (S_IFREG|S_IRUGO);
} else if (strcmp(name, ZFS_GROUPSPACEFILE_NAME) == 0 &&
zfs_ctldir_spacefiles) {
*ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_GROUPSPACE,
&zpl_fops_groupspacefile, &zpl_ops_groupspace_file);
(*ipp)->i_mode = (S_IFREG|S_IRUGO);
} else if (strcmp(name, ZFS_PROJSPACEFILE_NAME) == 0 &&
zfs_ctldir_spacefiles) {
*ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_PROJSPACE,
&zpl_fops_projectspacefile, &zpl_ops_projectspace_file);
(*ipp)->i_mode = (S_IFREG|S_IRUGO);
} else {
*ipp = NULL;
}
Expand Down Expand Up @@ -1315,3 +1331,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/projectspace files in .zfs/");
188 changes: 188 additions & 0 deletions module/os/linux/zfs/zpl_ctldir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -78,6 +79,33 @@ zpl_root_iterate(struct file *filp, zpl_dir_context_t *ctx)

ctx->pos++;
}

if (ctx->pos == 4 && zfs_ctldir_spacefiles) {
if (!zpl_dir_emit(ctx, ZFS_USERSPACEFILE_NAME,
strlen(ZFS_USERSPACEFILE_NAME),
ZFSCTL_INO_USERSPACE, DT_REG))
goto out;

ctx->pos++;
}

if (ctx->pos == 5 && zfs_ctldir_spacefiles) {
if (!zpl_dir_emit(ctx, ZFS_GROUPSPACEFILE_NAME,
strlen(ZFS_GROUPSPACEFILE_NAME),
ZFSCTL_INO_GROUPSPACE, DT_REG))
goto out;

ctx->pos++;
}

if (ctx->pos == 6 && zfs_ctldir_spacefiles) {
if (!zpl_dir_emit(ctx, ZFS_PROJSPACEFILE_NAME,
strlen(ZFS_PROJSPACEFILE_NAME),
ZFSCTL_INO_PROJSPACE, DT_REG))
goto out;

ctx->pos++;
}
out:
zpl_exit(zfsvfs, FTAG);

Expand Down Expand Up @@ -671,3 +699,163 @@ const struct inode_operations zpl_ops_shares = {
.lookup = zpl_shares_lookup,
.getattr = zpl_shares_getattr,
};

/*
* Helpers for:
* .zfs/userspace
* .zfs/groupspace
* .zfs/projectspace
*/
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 = 100 * sizeof (zfs_useracct_t);
zfs_useracct_t *buf = (zfs_useracct_t *)vmem_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);
}
}
vmem_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_userspace_show(struct seq_file *seq, void *v)
{
zfs_userquota_prop_t *props = (zfs_userquota_prop_t *)seq->private;
seq_printf(seq, "id,used,quota,objused,objquota\n");
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 < 4; ++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 < 4; ++i)
seq_print_spaceval(seq, spacelist, props[i]);
seq_putc(seq, '\n');
}
nvlist_free(ids);
return (0);
}

static zfs_userquota_prop_t userspace_props[4] = {
ZFS_PROP_USERUSED,
ZFS_PROP_USERQUOTA,
ZFS_PROP_USEROBJUSED,
ZFS_PROP_USEROBJQUOTA
};

static zfs_userquota_prop_t groupspace_props[4] = {
ZFS_PROP_GROUPUSED,
ZFS_PROP_GROUPQUOTA,
ZFS_PROP_GROUPOBJUSED,
ZFS_PROP_GROUPOBJQUOTA
};

static zfs_userquota_prop_t projectspace_props[4] = {
ZFS_PROP_PROJECTUSED,
ZFS_PROP_PROJECTQUOTA,
ZFS_PROP_PROJECTOBJUSED,
ZFS_PROP_PROJECTOBJQUOTA
};

static int zpl_fops_userspace_open(struct inode *inode, struct file *file)
{
return single_open(file, zpl_userspace_show,
(void *)userspace_props);
}

static int zpl_fops_groupspace_open(struct inode *inode, struct file *file)
{
return single_open(file, zpl_userspace_show,
(void *)groupspace_props);
}

static int zpl_fops_projectspace_open(struct inode *inode, struct file *file)
{
return single_open(file, zpl_userspace_show,
(void *)projectspace_props);
}

/* .zfs/userspace */
const struct file_operations zpl_fops_userspacefile = {
.open = zpl_fops_userspace_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};

/* .zfs/groupspace */
const struct file_operations zpl_fops_groupspacefile = {
.open = zpl_fops_groupspace_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};

/* .zfs/projectspace */
const struct file_operations zpl_fops_projectspacefile = {
.open = zpl_fops_projectspace_open,
.read = seq_read,
.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 = {};
2 changes: 1 addition & 1 deletion tests/runfiles/linux.run
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,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', 'userspace_ctldir_files_001']
tags = ['functional', 'userquota']

[tests/functional/zvol/zvol_misc:Linux]
Expand Down
1 change: 1 addition & 0 deletions tests/zfs-tests/tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -2035,6 +2035,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/userspace_ctldir_files_001.ksh \
functional/vdev_zaps/cleanup.ksh \
functional/vdev_zaps/setup.ksh \
functional/vdev_zaps/vdev_zaps_001_pos.ksh \
Expand Down
Loading

0 comments on commit c19ffe2

Please sign in to comment.