Add attr_x commands and documentation to utils

Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
Zach Brown
2024-06-18 15:14:30 -07:00
parent 6a99ca9ede
commit de304628ea
2 changed files with 300 additions and 0 deletions

View File

@@ -209,6 +209,16 @@ A path within a ScoutFS filesystem.
.RE
.PD
.TP
.BI "get-attr-x FILE"
.sp
Display ScoutFS-specific attributes from a file. If no options are
given than all the attributes that the command supports will be
displayed. If attributes are specified with options then only those
attributes are displayed. If only one attribute is specified then it
will not have a label prefix in the display output. The --help option
will list the attributes that the command supports. The file system may
support a different set of attributes.
.TP
.BI "get-referring-entries [-p|--path PATH] INO"
.sp
@@ -506,6 +516,15 @@ A path within a ScoutFS filesystem.
.RE
.PD
.TP
.BI "set-attr-x FILE"
.sp
Set ScoutFS-specific attributes on a file. Only the attributes that are
spcified by options will be set. The --help option will list the
attributes that the command understands. The file system may support a
different set of attributes.
.PD
.TP
.BI "setattr FILE [-d, --data-version=VERSION [-s, --size=SIZE [-o, --offline]]] [-t, --ctime=TIMESPEC]"
.sp

281
utils/src/attr_x.c Normal file
View File

@@ -0,0 +1,281 @@
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <argp.h>
#include <stdbool.h>
#include "sparse.h"
#include "util.h"
#include "format.h"
#include "ioctl.h"
#include "parse.h"
#include "cmd.h"
struct attr_x_args {
bool set;
char *filename;
struct scoutfs_ioctl_inode_attr_x iax;
};
#define pr(iax, name, label, fmt, args...) \
do { \
if ((iax->x_mask & SCOUTFS_IOC_IAX_##name)) { \
if (__builtin_popcount(iax->x_mask) > 1) \
printf(label ": " fmt "\n", ##args); \
else \
printf(fmt "\n", ##args); \
} \
} while (0)
#define prb(iax, name, label) \
pr(iax, name, label, "%u", !!((iax)->bits & SCOUTFS_IOC_IAX_B_##name))
static int do_attr_x(struct attr_x_args *args)
{
struct scoutfs_ioctl_inode_attr_x *iax = &args->iax;
int fd = -1;
int ret;
int op;
if (args->set) {
/* nothing to do if not setting */
if (iax->x_mask == 0)
return 0;
op = SCOUTFS_IOC_SET_ATTR_X;
} else {
/* get all known if none specified */
if (iax->x_mask == 0)
iax->x_mask = ~SCOUTFS_IOC_IAX__UNKNOWN;
op = SCOUTFS_IOC_GET_ATTR_X;
}
fd = open(args->filename, O_RDONLY);
if (fd < 0) {
ret = -errno;
fprintf(stderr, "failed to open '%s': %s (%d)\n",
args->filename, strerror(errno), errno);
goto out;
}
ret = ioctl(fd, op, iax);
if (ret < 0) {
ret = -errno;
fprintf(stderr, "attr_x ioctl failed on '%s': "
"%s (%d)\n", args->filename, strerror(errno), errno);
goto out;
}
if (!args->set) {
pr(iax, META_SEQ, "meta_seq", "%llu", iax->meta_seq);
pr(iax, DATA_SEQ, "data_seq", "%llu", iax->data_seq);
pr(iax, DATA_VERSION, "data_version", "%llu", iax->data_version);
pr(iax, ONLINE_BLOCKS, "online_blocks", "%llu", iax->online_blocks);
pr(iax, OFFLINE_BLOCKS, "offline_blocks", "%llu", iax->offline_blocks);
pr(iax, CTIME, "ctime", "%llu.%u", iax->ctime_sec, iax->ctime_nsec);
pr(iax, CRTIME, "crtime", "%llu.%u", iax->crtime_sec, iax->crtime_nsec);
pr(iax, SIZE, "size", "%llu", iax->size);
}
ret = 0;
out:
if (fd >= 0)
close(fd);
return ret;
}
/*
* This is called for both get and set. The get calls won't have
* arguments and are only setting the mask. The set calls parse the
* value to set. We could have defaults by making set option arguments
* optional, like setting the current time for timestamps, but that
* hasn't been needed.
*
* Option value parsing places no constraints on the attributes or
* values themselves once parsed. This lets us use the set command to
* test the kernel's testing for invalid attribute combinations and
* values.
*/
static int parse_opt(int key, char *arg, struct argp_state *state)
{
struct attr_x_args *args = state->input;
struct timespec ts;
int ret;
switch (key) {
case 'm':
args->iax.x_mask |= SCOUTFS_IOC_IAX_META_SEQ;
if (arg) {
ret = parse_u64(arg, &args->iax.meta_seq);
if (ret)
return ret;
}
break;
case 'd':
args->iax.x_mask |= SCOUTFS_IOC_IAX_DATA_SEQ;
if (arg) {
ret = parse_u64(arg, &args->iax.data_seq);
if (ret)
return ret;
}
break;
case 'v':
args->iax.x_mask |= SCOUTFS_IOC_IAX_DATA_VERSION;
if (arg) {
ret = parse_u64(arg, &args->iax.data_version);
if (ret)
return ret;
if (args->iax.data_version == 0)
argp_error(state, "data version must not be 0");
}
break;
case 'n':
args->iax.x_mask |= SCOUTFS_IOC_IAX_ONLINE_BLOCKS;
if (arg) {
ret = parse_u64(arg, &args->iax.online_blocks);
if (ret)
return ret;
}
break;
case 'f':
args->iax.x_mask |= SCOUTFS_IOC_IAX_OFFLINE_BLOCKS;
if (arg) {
ret = parse_u64(arg, &args->iax.offline_blocks);
if (ret)
return ret;
}
break;
case 'c':
args->iax.x_mask |= SCOUTFS_IOC_IAX_CTIME;
if (arg) {
ret = parse_timespec(arg, &ts);
if (ret)
return ret;
args->iax.ctime_sec = ts.tv_sec;
args->iax.ctime_nsec = ts.tv_nsec;
}
break;
case 'r':
args->iax.x_mask |= SCOUTFS_IOC_IAX_CRTIME;
if (arg) {
ret = parse_timespec(arg, &ts);
if (ret)
return ret;
args->iax.crtime_sec = ts.tv_sec;
args->iax.crtime_nsec = ts.tv_nsec;
}
break;
case 's':
args->iax.x_mask |= SCOUTFS_IOC_IAX_SIZE;
if (arg) {
ret = parse_u64(arg, &args->iax.size);
if (ret)
return ret;
}
break;
case ARGP_KEY_ARG:
if (!args->filename)
args->filename = strdup_or_error(state, arg);
else
argp_error(state, "more than one argument given");
break;
case ARGP_KEY_FINI:
if (!args->filename)
argp_error(state, "no filename given");
break;
default:
break;
}
return 0;
}
/*
* The get options are derived from these by copying the struct and
* modifying fields.
*/
static struct argp_option set_options[] = {
{ "meta_seq", 'm', "SEQ", 0, "Inode Metadata change index sequence number"},
{ "data_seq", 'd', "SEQ", 0, "File Data change index sequence number"},
{ "data_version", 'v', "VERSION", 0, "File Data contents version"},
{ "online_blocks", 'n', "COUNT", 0, "Online data block count"},
{ "offline_blocks", 'f', "COUNT", 0, "Offline data block count"},
{ "ctime", 'c', "SECS.NSECS", 0, "Inode change time (posix ctime)"},
{ "crtime", 'r', "SECS.NSECS", 0, "ScoutFS creation time"},
{ "size", 's', "SIZE", 0, "Inode i_size field"},
{ NULL }
};
static struct argp get_argp = {
NULL, /* dynamically built */
parse_opt,
"FILE",
"get extensible file attributes"
};
static int get_attr_x_cmd(int argc, char **argv)
{
struct attr_x_args args = {0,};
int ret;
ret = argp_parse(&get_argp, argc, argv, 0, NULL, &args);
if (ret)
return ret;
return do_attr_x(&args);
}
/*
* The set options match the get arguments but don't take argument
* values to set.
*/
static void build_get_options(void)
{
struct argp_option **opts = (struct argp_option **)&get_argp.options;
int i;
*opts = calloc(array_size(set_options), sizeof(set_options[0]));
assert(*opts);
memcpy(*opts, set_options, array_size(set_options) * sizeof(set_options[0]));
for (i = 0; i < array_size(set_options) - 1; i++)
(*opts)[i].arg = NULL;
}
static void __attribute__((constructor)) get_ctor(void)
{
build_get_options();
cmd_register_argp("get-attr-x", &get_argp, GROUP_AGENT, get_attr_x_cmd);
}
static struct argp set_argp = {
set_options,
parse_opt,
"FILE",
"Set extensible file attributes"
};
static int set_attr_x_cmd(int argc, char **argv)
{
struct attr_x_args args = {.set = true,};
int ret;
ret = argp_parse(&set_argp, argc, argv, 0, NULL, &args);
if (ret)
return ret;
return do_attr_x(&args);
}
static void __attribute__((constructor)) set_ctor(void)
{
cmd_register_argp("set-attr-x", &set_argp, GROUP_AGENT, set_attr_x_cmd);
}