blob: e3ffb75aa448321a5790a8b1d9aa5af6f1987e90 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2005 Silicon Graphics, Inc.
* All Rights Reserved.
*/
#include "libxfs.h"
#include "command.h"
#include "attrset.h"
#include "io.h"
#include "output.h"
#include "type.h"
#include "init.h"
#include "fprint.h"
#include "faddr.h"
#include "field.h"
#include "inode.h"
#include "malloc.h"
#include <sys/xattr.h>
#include "libfrog/fsproperties.h"
#include "libxfs/listxattr.h"
static int attr_list_f(int argc, char **argv);
static int attr_get_f(int argc, char **argv);
static int attr_set_f(int argc, char **argv);
static int attr_remove_f(int argc, char **argv);
static void attrlist_help(void);
static void attrget_help(void);
static void attrset_help(void);
static const cmdinfo_t attr_list_cmd =
{ "attr_list", "alist", attr_list_f, 0, -1, 0,
N_("[-r|-s|-u|-p|-Z] [-v]"),
N_("list attributes on the current inode"), attrlist_help };
static const cmdinfo_t attr_get_cmd =
{ "attr_get", "aget", attr_get_f, 1, -1, 0,
N_("[-r|-s|-u|-p|-Z] name"),
N_("get the named attribute on the current inode"), attrget_help };
static const cmdinfo_t attr_set_cmd =
{ "attr_set", "aset", attr_set_f, 1, -1, 0,
N_("[-r|-s|-u|-p|-Z] [-n] [-R|-C] [-v n] name"),
N_("set the named attribute on the current inode"), attrset_help };
static const cmdinfo_t attr_remove_cmd =
{ "attr_remove", "aremove", attr_remove_f, 1, -1, 0,
N_("[-r|-s|-u|-p|-Z] [-n] name"),
N_("remove the named attribute from the current inode"), attrset_help };
static void
attrlist_help(void)
{
dbprintf(_(
"\n"
" The attr_list command provide interfaces for listing all extended attributes\n"
" attached to an inode.\n"
" There are 4 namespace flags:\n"
" -r -- 'root'\n"
" -u -- 'user' (default)\n"
" -s -- 'secure'\n"
" -p -- 'parent'\n"
" -Z -- fs property\n"
"\n"
" -v -- print the value of the attributes\n"
"\n"));
}
static void
attrget_help(void)
{
dbprintf(_(
"\n"
" The attr_get command provide interfaces for retrieving the values of extended\n"
" attributes of a file. This command requires attribute names to be specified.\n"
" There are 4 namespace flags:\n"
" -r -- 'root'\n"
" -u -- 'user' (default)\n"
" -s -- 'secure'\n"
" -p -- 'parent'\n"
" -Z -- fs property\n"
"\n"));
}
static void
attrset_help(void)
{
dbprintf(_(
"\n"
" The 'attr_set' and 'attr_remove' commands provide interfaces for debugging\n"
" the extended attribute allocation and removal code.\n"
" Both commands require an attribute name to be specified, and the attr_set\n"
" command allows an optional value length (-v) to be provided as well.\n"
" There are 4 namespace flags:\n"
" -r -- 'root'\n"
" -u -- 'user' (default)\n"
" -s -- 'secure'\n"
" -p -- 'parent'\n"
" -Z -- fs property\n"
"\n"
" For attr_set, these options further define the type of set operation:\n"
" -C -- 'create' - create attribute, fail if it already exists\n"
" -R -- 'replace' - replace attribute, fail if it does not exist\n"
"\n"
" If the attribute value is a string, it can be specified after the\n"
" attribute name.\n"
"\n"
" The backward compatibility mode 'noattr2' can be emulated (-n) also.\n"
"\n"));
}
void
attrset_init(void)
{
if (!expert_mode)
return;
add_command(&attr_list_cmd);
add_command(&attr_get_cmd);
add_command(&attr_set_cmd);
add_command(&attr_remove_cmd);
}
static unsigned char *
get_buf_from_file(
const char *fname,
size_t bufsize,
int *namelen)
{
FILE *fp;
unsigned char *buf;
size_t sz;
buf = malloc(bufsize + 1);
if (!buf) {
perror("malloc");
return NULL;
}
fp = fopen(fname, "r");
if (!fp) {
perror(fname);
goto out_free;
}
sz = fread(buf, sizeof(char), bufsize, fp);
if (sz == 0) {
printf("%s: Could not read anything from file\n", fname);
goto out_fp;
}
fclose(fp);
*namelen = sz;
return buf;
out_fp:
fclose(fp);
out_free:
free(buf);
return NULL;
}
#define LIBXFS_ATTR_NS (LIBXFS_ATTR_SECURE | \
LIBXFS_ATTR_ROOT | \
LIBXFS_ATTR_PARENT)
static bool
adjust_fsprop_attr_name(
struct xfs_da_args *args,
bool *free_name)
{
const char *o = (const char *)args->name;
char *p;
int ret;
if ((args->attr_filter & LIBXFS_ATTR_NS) != LIBXFS_ATTR_ROOT) {
dbprintf(_("fs properties must be ATTR_ROOT\n"));
return false;
}
ret = fsprop_name_to_attr_name(o, &p);
if (ret < 0) {
dbprintf(_("could not allocate fs property name string\n"));
return false;
}
args->namelen = ret;
args->name = (const uint8_t *)p;
if (*free_name)
free((void *)o);
*free_name = true;
if (args->namelen > MAXNAMELEN) {
dbprintf(_("%s: name too long\n"), args->name);
return false;
}
if (args->valuelen > ATTR_MAX_VALUELEN) {
dbprintf(_("%s: value too long\n"), args->name);
return false;
}
return true;
}
static void
print_fsprop(
struct xfs_da_args *args)
{
const char *p =
attr_name_to_fsprop_name((const char *)args->name);
if (p)
printf("%s=%.*s\n", p, args->valuelen, (char *)args->value);
else
fprintf(stderr, _("%s: not a fs property?\n"), args->name);
}
static int
attr_set_f(
int argc,
char **argv)
{
struct xfs_da_args args = {
.geo = mp->m_attr_geo,
.whichfork = XFS_ATTR_FORK,
.op_flags = XFS_DA_OP_OKNOENT,
};
char *sp;
char *name_from_file = NULL;
char *value_from_file = NULL;
bool free_name = false;
enum xfs_attr_update op = XFS_ATTRUPDATE_UPSERT;
bool fsprop = false;
int c;
int error;
if (cur_typ == NULL) {
dbprintf(_("no current type\n"));
return 0;
}
if (cur_typ->typnm != TYP_INODE) {
dbprintf(_("current type is not inode\n"));
return 0;
}
while ((c = getopt(argc, argv, "ruspCRnN:v:V:Z")) != EOF) {
switch (c) {
/* namespaces */
case 'Z':
fsprop = true;
fallthrough;
case 'r':
args.attr_filter &= ~LIBXFS_ATTR_NS;
args.attr_filter |= LIBXFS_ATTR_ROOT;
break;
case 'u':
args.attr_filter &= ~LIBXFS_ATTR_NS;
break;
case 's':
args.attr_filter &= ~LIBXFS_ATTR_NS;
args.attr_filter |= LIBXFS_ATTR_SECURE;
break;
case 'p':
args.attr_filter &= ~LIBXFS_ATTR_NS;
args.attr_filter |= XFS_ATTR_PARENT;
break;
/* modifiers */
case 'C':
op = XFS_ATTRUPDATE_CREATE;
break;
case 'R':
op = XFS_ATTRUPDATE_REPLACE;
break;
case 'N':
name_from_file = optarg;
break;
case 'n':
/*
* We never touch attr2 these days; leave this here to
* avoid breaking scripts.
*/
break;
/* value length */
case 'v':
if (value_from_file) {
dbprintf(_("already set value file\n"));
return 0;
}
args.valuelen = strtol(optarg, &sp, 0);
if (*sp != '\0' ||
args.valuelen < 0 || args.valuelen > 64 * 1024) {
dbprintf(_("bad attr_set valuelen %s\n"), optarg);
return 0;
}
break;
case 'V':
if (args.valuelen != 0) {
dbprintf(_("already set valuelen\n"));
return 0;
}
value_from_file = optarg;
break;
default:
dbprintf(_("bad option for attr_set command\n"));
return 0;
}
}
if (name_from_file) {
int namelen;
if (optind != argc) {
dbprintf(_("too many options for attr_set (no name needed)\n"));
return 0;
}
args.name = get_buf_from_file(name_from_file, MAXNAMELEN,
&namelen);
if (!args.name)
return 0;
free_name = true;
args.namelen = namelen;
} else {
if (optind != argc - 1 && optind != argc - 2) {
dbprintf(_("too few options for attr_set (no name given)\n"));
return 0;
}
args.name = (const unsigned char *)argv[optind];
if (!args.name) {
dbprintf(_("invalid name\n"));
return 0;
}
args.namelen = strlen(argv[optind]);
if (args.namelen >= MAXNAMELEN) {
dbprintf(_("name too long\n"));
goto out;
}
}
if (value_from_file) {
int valuelen;
args.value = get_buf_from_file(value_from_file,
XFS_XATTR_SIZE_MAX, &valuelen);
if (!args.value)
goto out;
args.valuelen = valuelen;
} else if (args.valuelen) {
args.value = memalign(getpagesize(), args.valuelen);
if (!args.value) {
dbprintf(_("cannot allocate buffer (%d)\n"),
args.valuelen);
goto out;
}
memset(args.value, 'v', args.valuelen);
} else if (optind == argc - 2) {
args.valuelen = strlen(argv[optind + 1]);
args.value = strdup(argv[optind + 1]);
if (!args.value) {
dbprintf(_("cannot allocate buffer (%d)\n"),
args.valuelen);
goto out;
}
}
if (fsprop) {
if (!fsprop_validate((const char *)args.name, args.value)) {
dbprintf(_("%s: invalid value \"%s\"\n"),
args.name, args.value);
goto out;
}
if (!adjust_fsprop_attr_name(&args, &free_name))
goto out;
}
if (libxfs_iget(mp, NULL, iocur_top->ino, 0, &args.dp)) {
dbprintf(_("failed to iget inode %llu\n"),
(unsigned long long)iocur_top->ino);
goto out;
}
args.owner = iocur_top->ino;
libxfs_attr_sethash(&args);
error = -libxfs_attr_set(&args, op, false);
if (error) {
dbprintf(_("failed to set attr %s on inode %llu: %s\n"),
args.name, (unsigned long long)iocur_top->ino,
strerror(error));
goto out;
}
if (fsprop)
print_fsprop(&args);
/* refresh with updated inode contents */
set_cur_inode(iocur_top->ino);
out:
if (args.dp)
libxfs_irele(args.dp);
if (args.value)
free(args.value);
if (free_name)
free((void *)args.name);
return 0;
}
static int
attr_remove_f(
int argc,
char **argv)
{
struct xfs_da_args args = {
.geo = mp->m_attr_geo,
.whichfork = XFS_ATTR_FORK,
.op_flags = XFS_DA_OP_OKNOENT,
};
char *name_from_file = NULL;
bool free_name = false;
bool fsprop = false;
int c;
int error;
if (cur_typ == NULL) {
dbprintf(_("no current type\n"));
return 0;
}
if (cur_typ->typnm != TYP_INODE) {
dbprintf(_("current type is not inode\n"));
return 0;
}
while ((c = getopt(argc, argv, "ruspnN:Z")) != EOF) {
switch (c) {
/* namespaces */
case 'Z':
fsprop = true;
fallthrough;
case 'r':
args.attr_filter &= ~LIBXFS_ATTR_NS;
args.attr_filter |= LIBXFS_ATTR_ROOT;
break;
case 'u':
args.attr_filter &= ~LIBXFS_ATTR_NS;
break;
case 's':
args.attr_filter &= ~LIBXFS_ATTR_NS;
args.attr_filter |= LIBXFS_ATTR_SECURE;
break;
case 'p':
args.attr_filter &= ~LIBXFS_ATTR_NS;
args.attr_filter |= XFS_ATTR_PARENT;
break;
case 'N':
name_from_file = optarg;
break;
case 'n':
/*
* We never touch attr2 these days; leave this here to
* avoid breaking scripts.
*/
break;
default:
dbprintf(_("bad option for attr_remove command\n"));
return 0;
}
}
if (name_from_file) {
int namelen;
if (optind != argc) {
dbprintf(_("too many options for attr_set (no name needed)\n"));
return 0;
}
args.name = get_buf_from_file(name_from_file, MAXNAMELEN,
&namelen);
if (!args.name)
return 0;
free_name = true;
args.namelen = namelen;
} else {
if (optind != argc - 1) {
dbprintf(_("too few options for attr_remove (no name given)\n"));
return 0;
}
args.name = (const unsigned char *)argv[optind];
if (!args.name) {
dbprintf(_("invalid name\n"));
return 0;
}
args.namelen = strlen(argv[optind]);
if (args.namelen >= MAXNAMELEN) {
dbprintf(_("name too long\n"));
return 0;
}
}
if (fsprop && !adjust_fsprop_attr_name(&args, &free_name))
goto out;
if (libxfs_iget(mp, NULL, iocur_top->ino, 0, &args.dp)) {
dbprintf(_("failed to iget inode %llu\n"),
(unsigned long long)iocur_top->ino);
goto out;
}
args.owner = iocur_top->ino;
libxfs_attr_sethash(&args);
error = -libxfs_attr_set(&args, XFS_ATTRUPDATE_REMOVE, false);
if (error) {
dbprintf(_("failed to remove attr %s from inode %llu: %s\n"),
(unsigned char *)args.name,
(unsigned long long)iocur_top->ino,
strerror(error));
goto out;
}
/* refresh with updated inode contents */
set_cur_inode(iocur_top->ino);
out:
if (args.dp)
libxfs_irele(args.dp);
if (free_name)
free((void *)args.name);
return 0;
}
static int
attr_get_f(
int argc,
char **argv)
{
struct xfs_da_args args = {
.geo = mp->m_attr_geo,
.whichfork = XFS_ATTR_FORK,
.op_flags = XFS_DA_OP_OKNOENT,
};
char *name_from_file = NULL;
bool free_name = false;
bool fsprop = false;
int c;
int error;
if (cur_typ == NULL) {
dbprintf(_("no current type\n"));
return 0;
}
if (cur_typ->typnm != TYP_INODE) {
dbprintf(_("current type is not inode\n"));
return 0;
}
while ((c = getopt(argc, argv, "ruspN:Z")) != EOF) {
switch (c) {
/* namespaces */
case 'Z':
fsprop = true;
fallthrough;
case 'r':
args.attr_filter &= ~LIBXFS_ATTR_NS;
args.attr_filter |= LIBXFS_ATTR_ROOT;
break;
case 'u':
args.attr_filter &= ~LIBXFS_ATTR_NS;
break;
case 's':
args.attr_filter &= ~LIBXFS_ATTR_NS;
args.attr_filter |= LIBXFS_ATTR_SECURE;
break;
case 'p':
args.attr_filter &= ~LIBXFS_ATTR_NS;
args.attr_filter |= XFS_ATTR_PARENT;
break;
case 'N':
name_from_file = optarg;
break;
default:
dbprintf(_("bad option for attr_get command\n"));
return 0;
}
}
if (name_from_file) {
int namelen;
if (optind != argc) {
dbprintf(_("too many options for attr_get (no name needed)\n"));
return 0;
}
args.name = get_buf_from_file(name_from_file, MAXNAMELEN,
&namelen);
if (!args.name)
return 0;
free_name = true;
args.namelen = namelen;
} else {
if (optind != argc - 1) {
dbprintf(_("too few options for attr_get (no name given)\n"));
return 0;
}
args.name = (const unsigned char *)argv[optind];
if (!args.name) {
dbprintf(_("invalid name\n"));
return 0;
}
args.namelen = strlen(argv[optind]);
if (args.namelen >= MAXNAMELEN) {
dbprintf(_("name too long\n"));
goto out;
}
}
if (libxfs_iget(mp, NULL, iocur_top->ino, 0, &args.dp)) {
dbprintf(_("failed to iget inode %llu\n"),
(unsigned long long)iocur_top->ino);
goto out;
}
if (fsprop && !adjust_fsprop_attr_name(&args, &free_name))
goto out;
args.owner = iocur_top->ino;
libxfs_attr_sethash(&args);
/*
* Look up attr value with a maximally long length and a null buffer
* to return the value and the correct length.
*/
args.valuelen = XATTR_SIZE_MAX;
error = -libxfs_attr_get(&args);
if (error) {
dbprintf(_("failed to get attr %s on inode %llu: %s\n"),
args.name, (unsigned long long)iocur_top->ino,
strerror(error));
goto out;
}
if (fsprop)
print_fsprop(&args);
else
printf("%.*s\n", args.valuelen, (char *)args.value);
out:
if (args.dp)
libxfs_irele(args.dp);
if (args.value)
free(args.value);
if (free_name)
free((void *)args.name);
return 0;
}
struct attrlist_ctx {
unsigned int attr_filter;
bool print_values;
bool fsprop;
};
static int
attrlist_print(
struct xfs_trans *tp,
struct xfs_inode *ip,
unsigned int attr_flags,
const unsigned char *name,
unsigned int namelen,
const void *value,
unsigned int valuelen,
void *priv)
{
struct attrlist_ctx *ctx = priv;
struct xfs_da_args args = {
.geo = mp->m_attr_geo,
.whichfork = XFS_ATTR_FORK,
.op_flags = XFS_DA_OP_OKNOENT,
.dp = ip,
.owner = ip->i_ino,
.trans = tp,
.attr_filter = attr_flags & XFS_ATTR_NSP_ONDISK_MASK,
.name = name,
.namelen = namelen,
};
char namebuf[MAXNAMELEN + 1];
const char *print_name = namebuf;
int error;
if ((attr_flags & XFS_ATTR_NSP_ONDISK_MASK) != ctx->attr_filter)
return 0;
/* Make sure the name is null terminated. */
memcpy(namebuf, name, namelen);
namebuf[MAXNAMELEN] = 0;
if (ctx->fsprop) {
const char *p = attr_name_to_fsprop_name(namebuf);
if (!p)
return 0;
namelen -= (p - namebuf);
print_name = p;
}
if (!ctx->print_values) {
printf("%.*s\n", namelen, print_name);
return 0;
}
if (value) {
printf("%.*s=%.*s\n", namelen, print_name, valuelen,
(char *)value);
return 0;
}
libxfs_attr_sethash(&args);
/*
* Look up attr value with a maximally long length and a null buffer
* to return the value and the correct length.
*/
args.valuelen = XATTR_SIZE_MAX;
error = -libxfs_attr_get(&args);
if (error) {
dbprintf(_("failed to get attr %s on inode %llu: %s\n"),
args.name, (unsigned long long)iocur_top->ino,
strerror(error));
return error;
}
printf("%.*s=%.*s\n", namelen, print_name, args.valuelen,
(char *)args.value);
kfree(args.value);
return 0;
}
static int
attr_list_f(
int argc,
char **argv)
{
struct attrlist_ctx ctx = { };
struct xfs_trans *tp;
struct xfs_inode *ip;
int c;
int error;
if (cur_typ == NULL) {
dbprintf(_("no current type\n"));
return 0;
}
if (cur_typ->typnm != TYP_INODE) {
dbprintf(_("current type is not inode\n"));
return 0;
}
while ((c = getopt(argc, argv, "ruspvZ")) != EOF) {
switch (c) {
/* namespaces */
case 'Z':
ctx.fsprop = true;
fallthrough;
case 'r':
ctx.attr_filter &= ~LIBXFS_ATTR_NS;
ctx.attr_filter |= LIBXFS_ATTR_ROOT;
break;
case 'u':
ctx.attr_filter &= ~LIBXFS_ATTR_NS;
break;
case 's':
ctx.attr_filter &= ~LIBXFS_ATTR_NS;
ctx.attr_filter |= LIBXFS_ATTR_SECURE;
break;
case 'p':
ctx.attr_filter &= ~LIBXFS_ATTR_NS;
ctx.attr_filter |= XFS_ATTR_PARENT;
break;
case 'v':
ctx.print_values = true;
break;
default:
dbprintf(_("bad option for attr_list command\n"));
return 0;
}
}
if (ctx.fsprop &&
(ctx.attr_filter & LIBXFS_ATTR_NS) != LIBXFS_ATTR_ROOT) {
dbprintf(_("fs properties must be ATTR_ROOT\n"));
return false;
}
if (optind != argc) {
dbprintf(_("too many options for attr_list (no name needed)\n"));
return 0;
}
error = -libxfs_trans_alloc_empty(mp, &tp);
if (error) {
dbprintf(_("failed to allocate empty transaction\n"));
return 0;
}
error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &ip);
if (error) {
dbprintf(_("failed to iget inode %llu: %s\n"),
(unsigned long long)iocur_top->ino,
strerror(error));
goto out_trans;
}
error = xattr_walk(tp, ip, attrlist_print, &ctx);
if (error) {
dbprintf(_("walking inode %llu xattrs: %s\n"),
(unsigned long long)iocur_top->ino,
strerror(error));
goto out_inode;
}
out_inode:
libxfs_irele(ip);
out_trans:
libxfs_trans_cancel(tp);
return 0;
}