Compare commits

..

5 Commits

Author SHA1 Message Date
Auke Kok
85ecc136c7 Add test exercising the totl recompute ioctl.
Basic validation for the recompute-xattr-total ioctl through the
totl-delta-inject ioctl. Takes a few different corrective actions that
are plausible - simple drifted values, and a stray orphan totl.

The test validates that the kmod dmesg output matches what was
expected since that's our proof that repair happened, but the actual
warnings are filtered from logs during test to not fail our test
case otherwise.

Signed-off-by: Auke Kok <auke.kok@versity.com>
2026-04-30 16:14:38 -07:00
Auke Kok
1e5cc26bda Add scoutfs recompute-xattr-total subcommand.
Wire SCOUTFS_IOC_RECOMPUTE_TOTL into the scoutfs util. Needs the
path to the mountpoint the A.B.C totl key.

Signed-off-by: Auke Kok <auke.kok@versity.com>
2026-04-30 16:14:38 -07:00
Auke Kok
778ccd247b Add SCOUTFS_IOC_RECOMPUTE_TOTL ioctl.
Walk every contributing xattr for a given totl key, sum the values
and keep count. Then, read the merged value via wkic, and apply a
corrective delta.  If a delta is found, calls scoutfs_warning()
so that system logs capture the correction values.

Signed-off-by: Auke Kok <auke.kok@versity.com>
2026-04-30 16:14:38 -07:00
Auke Kok
d89d026eb1 Add test exercising the totl delta inject ioctl.
Skews a totl twice, restore it, and intersperse setfattr/unlink to
exercise both injected and naturally-produced deltas.

Signed-off-by: Auke Kok <auke.kok@versity.com>
2026-04-30 16:14:37 -07:00
Auke Kok
5aac1dd741 Add SCOUTFS_IOC_INJECT_TOTL_DELTA ioctl.
Inject a signed (total, count) delta at a totl key.  No validity
checking.  Requires CAP_SYS_ADMIN.

Signed-off-by: Auke Kok <auke.kok@versity.com>
2026-04-30 16:14:37 -07:00
14 changed files with 714 additions and 1 deletions

View File

@@ -1739,6 +1739,58 @@ out:
return ret;
}
static long scoutfs_ioc_inject_totl_delta(struct file *file, unsigned long arg)
{
struct super_block *sb = file_inode(file)->i_sb;
struct scoutfs_ioctl_inject_totl_delta __user *uitd = (void __user *)arg;
struct scoutfs_ioctl_inject_totl_delta itd;
struct scoutfs_xattr_totl_val tval;
struct scoutfs_lock *lock = NULL;
struct scoutfs_key key;
int ret;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (copy_from_user(&itd, uitd, sizeof(itd)))
return -EFAULT;
scoutfs_xattr_init_totl_key(&key, itd.name);
tval.total = cpu_to_le64((u64)itd.total);
tval.count = cpu_to_le64((u64)itd.count);
ret = scoutfs_lock_xattr_totl(sb, SCOUTFS_LOCK_WRITE_ONLY, 0, &lock);
if (ret < 0)
goto out;
ret = scoutfs_hold_trans(sb, false);
if (ret < 0)
goto unlock;
ret = scoutfs_item_delta(sb, &key, &tval, sizeof(tval), lock);
scoutfs_release_trans(sb);
unlock:
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE_ONLY);
out:
return ret;
}
static long scoutfs_ioc_recompute_totl(struct file *file, unsigned long arg)
{
struct super_block *sb = file_inode(file)->i_sb;
struct scoutfs_ioctl_recompute_totl __user *urt = (void __user *)arg;
struct scoutfs_ioctl_recompute_totl rt;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (copy_from_user(&rt, urt, sizeof(rt)))
return -EFAULT;
return scoutfs_xattr_recompute_totl(sb, rt.name);
}
long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
@@ -1790,6 +1842,10 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
return scoutfs_ioc_read_xattr_index(file, arg);
case SCOUTFS_IOC_PUNCH_OFFLINE:
return scoutfs_ioc_punch_offline(file, arg);
case SCOUTFS_IOC_INJECT_TOTL_DELTA:
return scoutfs_ioc_inject_totl_delta(file, arg);
case SCOUTFS_IOC_RECOMPUTE_TOTL:
return scoutfs_ioc_recompute_totl(file, arg);
}
return -ENOTTY;

View File

@@ -876,4 +876,29 @@ struct scoutfs_ioctl_punch_offline {
#define SCOUTFS_IOC_PUNCH_OFFLINE \
_IOW(SCOUTFS_IOCTL_MAGIC, 24, struct scoutfs_ioctl_punch_offline)
/*
* Inject a signed (total, count) delta at the totl key @name (a, b, c
* match the trailing dotted u64s of a totl xattr name).
*/
struct scoutfs_ioctl_inject_totl_delta {
__u64 name[SCOUTFS_IOCTL_XATTR_TOTAL_NAME_NR];
__s64 total;
__s64 count;
};
#define SCOUTFS_IOC_INJECT_TOTL_DELTA \
_IOW(SCOUTFS_IOCTL_MAGIC, 25, struct scoutfs_ioctl_inject_totl_delta)
/*
* Recompute the totl item @name identifies from a walk of every
* contributing xattr and apply a corrective delta. Should fsync()
* and avoid fsetattr() while operation is ongoing.
*/
struct scoutfs_ioctl_recompute_totl {
__u64 name[SCOUTFS_IOCTL_XATTR_TOTAL_NAME_NR];
};
#define SCOUTFS_IOC_RECOMPUTE_TOTL \
_IOW(SCOUTFS_IOCTL_MAGIC, 26, struct scoutfs_ioctl_recompute_totl)
#endif

View File

@@ -26,6 +26,9 @@
#include "trans.h"
#include "xattr.h"
#include "lock.h"
#include "totl.h"
#include "wkic.h"
#include "msg.h"
#include "hash.h"
#include "acl.h"
#include "scoutfs_trace.h"
@@ -633,6 +636,220 @@ int scoutfs_xattr_combine_totl(void *dst, int dst_len, void *src, int src_len)
return SCOUTFS_DELTA_COMBINED;
}
struct recompute_totl_current {
u64 total;
u64 count;
};
static int recompute_totl_current_cb(struct scoutfs_key *key, void *val, unsigned int val_len,
void *cb_arg)
{
struct recompute_totl_current *cur = cb_arg;
struct scoutfs_xattr_totl_val *tval = val;
if (val_len != sizeof(*tval))
return -EIO;
cur->total = le64_to_cpu(tval->total);
cur->count = le64_to_cpu(tval->count);
return 0;
}
/*
* Recompute the totl item @target_name identifies by walking every
* xattr under per-inode-group READ locks, then apply a delta that
* aligns the wkic-read merged value with the recomputed sum. Caller
* should fsync() and avoid fsetattr() while operation is ongoing.
*/
int scoutfs_xattr_recompute_totl(struct super_block *sb, u64 *target_name)
{
struct scoutfs_xattr_prefix_tags tgs;
struct recompute_totl_current cur;
struct scoutfs_xattr_totl_val tval;
struct scoutfs_xattr *xat = NULL;
struct scoutfs_lock *tag_lock = NULL;
struct scoutfs_lock *lock = NULL;
struct scoutfs_key range_start;
struct scoutfs_key range_end;
struct scoutfs_key target_key;
struct scoutfs_key bounded_last;
struct scoutfs_key next_key;
struct scoutfs_key key;
struct scoutfs_key last;
unsigned int xat_bytes;
unsigned int val_len;
bool release = false;
s64 sum_total = 0;
s64 sum_count = 0;
s64 delta_total;
s64 delta_count;
void *value;
u64 trail[3];
u64 next_ino;
u64 total;
int ret;
xat_bytes = sizeof(struct scoutfs_xattr) + SCOUTFS_XATTR_MAX_NAME_LEN +
SCOUTFS_XATTR_MAX_TOTL_U64;
xat = kmalloc(xat_bytes, GFP_NOFS);
if (!xat)
return -ENOMEM;
scoutfs_xattr_init_totl_key(&target_key, target_name);
init_xattr_key(&key, 0, 0, 0);
init_xattr_key(&last, U64_MAX, U32_MAX, U64_MAX);
last.skx_part = U8_MAX;
while (scoutfs_key_compare(&key, &last) <= 0) {
if (lock == NULL) {
ret = scoutfs_lock_ino(sb, SCOUTFS_LOCK_READ, 0,
le64_to_cpu(key.skx_ino), &lock);
if (ret < 0)
goto out;
}
bounded_last = scoutfs_key_compare(&lock->end, &last) < 0 ? lock->end : last;
ret = scoutfs_item_next(sb, &key, &bounded_last, xat, xat_bytes, lock);
if (ret == -ENOENT) {
/* skip past empty regions, similar to scoutfs_ioc_walk_inodes() */
key = lock->end;
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
lock = NULL;
scoutfs_key_inc(&key);
if (scoutfs_key_compare(&key, &last) > 0)
break;
ret = scoutfs_forest_next_hint(sb, &key, &next_key);
if (ret == -ENOENT || (ret == 0 &&
scoutfs_key_compare(&next_key, &last) > 0)) {
ret = 0;
break;
}
if (ret < 0)
goto out;
key = next_key;
continue;
}
if (ret < 0)
goto out;
if (key.sk_type < SCOUTFS_XATTR_TYPE) {
init_xattr_key(&key, le64_to_cpu(key.skx_ino), 0, 0);
continue;
}
if (key.sk_type > SCOUTFS_XATTR_TYPE) {
next_ino = le64_to_cpu(key.skx_ino) + 1;
if (next_ino == 0)
break;
init_xattr_key(&key, next_ino, 0, 0);
continue;
}
if (key.skx_part != 0) {
scoutfs_key_inc(&key);
continue;
}
if (ret < sizeof(struct scoutfs_xattr) ||
ret < offsetof(struct scoutfs_xattr, name[xat->name_len])) {
ret = -EIO;
goto out;
}
/* tag/name parse + value extract similar to scoutfs_xattr_drop_inode_items() */
if (scoutfs_xattr_parse_tags(xat->name, xat->name_len, &tgs) != 0 || !tgs.totl) {
scoutfs_key_inc(&key);
continue;
}
if (parse_dotted_u64s(trail, ARRAY_SIZE(trail), xat->name, xat->name_len) < 0) {
scoutfs_key_inc(&key);
continue;
}
if (trail[0] != target_name[0] || trail[1] != target_name[1] ||
trail[2] != target_name[2]) {
scoutfs_key_inc(&key);
continue;
}
value = &xat->name[xat->name_len];
val_len = ret - offsetof(struct scoutfs_xattr, name[xat->name_len]);
if (val_len != le16_to_cpu(xat->val_len)) {
ret = -EIO;
goto out;
}
ret = parse_totl_u64(value, val_len, &total);
if (ret < 0)
goto out;
sum_total += (s64)total;
sum_count += 1;
scoutfs_key_inc(&key);
}
if (lock) {
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
lock = NULL;
}
scoutfs_totl_set_range(&range_start, &range_end);
do {
memset(&cur, 0, sizeof(cur));
ret = scoutfs_wkic_iterate_stable(sb, &target_key, &target_key,
&range_start, &range_end,
recompute_totl_current_cb, &cur);
} while (ret == -ESTALE);
if (ret < 0)
goto out;
delta_total = sum_total - (s64)cur.total;
delta_count = sum_count - (s64)cur.count;
if (delta_total == 0 && delta_count == 0) {
ret = 0;
goto out;
}
scoutfs_warn(sb, "totl recompute applying delta total=%lld count=%lld to key "SK_FMT" (was total=%lld count=%lld, recomputed total=%lld count=%lld)",
delta_total, delta_count, SK_ARG(&target_key),
(s64)cur.total, (s64)cur.count, sum_total, sum_count);
tval.total = cpu_to_le64((u64)delta_total);
tval.count = cpu_to_le64((u64)delta_count);
/* same lock + trans + item_delta shape as scoutfs_ioc_inject_totl_delta() */
ret = scoutfs_lock_xattr_totl(sb, SCOUTFS_LOCK_WRITE_ONLY, 0, &tag_lock);
if (ret < 0)
goto out;
ret = scoutfs_hold_trans(sb, false);
if (ret < 0)
goto unlock_tag;
release = true;
ret = scoutfs_item_delta(sb, &target_key, &tval, sizeof(tval), tag_lock);
scoutfs_release_trans(sb);
release = false;
unlock_tag:
scoutfs_unlock(sb, tag_lock, SCOUTFS_LOCK_WRITE_ONLY);
out:
if (release)
scoutfs_release_trans(sb);
if (lock)
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
kfree(xat);
return ret;
}
void scoutfs_xattr_indx_get_range(struct scoutfs_key *start, struct scoutfs_key *end)
{
scoutfs_key_set_zeros(start);

View File

@@ -30,6 +30,7 @@ int scoutfs_xattr_parse_tags(const char *name, unsigned int name_len,
void scoutfs_xattr_init_totl_key(struct scoutfs_key *key, u64 *name);
int scoutfs_xattr_combine_totl(void *dst, int dst_len, void *src, int src_len);
int scoutfs_xattr_recompute_totl(struct super_block *sb, u64 *target_name);
void scoutfs_xattr_indx_get_range(struct scoutfs_key *start, struct scoutfs_key *end);
void scoutfs_xattr_init_indx_key(struct scoutfs_key *key, u8 major, u64 minor, u64 ino, u64 xid);

1
tests/.gitignore vendored
View File

@@ -12,3 +12,4 @@ src/o_tmpfile_umask
src/o_tmpfile_linkat
src/mmap_stress
src/mmap_validate
src/totl-delta-inject

View File

@@ -15,7 +15,8 @@ BIN := src/createmany \
src/o_tmpfile_umask \
src/o_tmpfile_linkat \
src/mmap_stress \
src/mmap_validate
src/mmap_validate \
src/totl-delta-inject
DEPS := $(wildcard src/*.d)

View File

@@ -171,6 +171,9 @@ t_filter_dmesg()
# orphan log trees reclaim is handled, not an error
re="$re|scoutfs .* reclaiming orphan log trees"
# recompute-xattr-total logs every corrective delta it applies
re="$re|scoutfs .* warning: totl recompute applying delta .*"
# fencing tests force unmounts and trigger timeouts
re="$re|scoutfs .* forcing unmount"
re="$re|scoutfs .* reconnect timed out"

View File

@@ -0,0 +1,10 @@
== setup three files contributing to totl 8888.0.0
== merge baseline into fs_root
8888.0.0 = 42, 3
== inject (+128, +2) unbalances totl 8888.0.0
8888.0.0 = 170, 5
== unlink f3 (value 32) produces a -32/-1 delta
8888.0.0 = 138, 4
== inject (-128, -2) restores accounting for the remaining files
8888.0.0 = 10, 2
== cleanup

View File

@@ -0,0 +1,23 @@
== setup three files contributing to totl 8888.0.0
== merge baseline into fs_root
8888.0.0 = 42, 3
== recompute on a clean baseline is a no-op
8888.0.0 = 42, 3
== inject (+128, +2) corrupts totl 8888.0.0
8888.0.0 = 170, 5
== recompute restores 8888.0.0
totl recompute applying delta total=-128 count=-2 to key 12.8888.0.0.0.0 (was total=170 count=5, recomputed total=42 count=3)
8888.0.0 = 42, 3
== inject (-50, +1) corrupts totl 8888.0.0 again
8888.0.0 = -8, 4
== recompute restores 8888.0.0
totl recompute applying delta total=50 count=-1 to key 12.8888.0.0.0.0 (was total=-8 count=4, recomputed total=42 count=3)
8888.0.0 = 42, 3
== recompute on an unused key is a no-op
8888.0.0 = 42, 3
== inject (+7, +1) at unused key 8888.1.1 then recompute clears it
8888.0.0 = 42, 3
8888.1.1 = 7, 1
totl recompute applying delta total=-7 count=-1 to key 12.8888.0.1.1.0 (was total=7 count=1, recomputed total=0 count=0)
8888.0.0 = 42, 3
== cleanup

View File

@@ -29,6 +29,8 @@ totl-xattr-tag.sh
basic-xattr-indx.sh
quota.sh
totl-merge-read.sh
totl-delta-inject.sh
totl-recompute.sh
lock-refleak.sh
lock-shrink-consistency.sh
lock-shrink-read-race.sh

View File

@@ -0,0 +1,121 @@
/*
* Test helper that calls SCOUTFS_IOC_INJECT_TOTL_DELTA to seed
* arbitrary totl deltas.
*
* Copyright (C) 2026 Versity Software, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License v2 as published by the Free Software Foundation.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include "ioctl.h"
static void usage(const char *prog)
{
fprintf(stderr,
"Usage: %s <mountpoint> <a>.<b>.<c> <total> <count>\n",
prog);
exit(2);
}
static int parse_s64(const char *s, int64_t *out)
{
char *end;
int64_t v;
errno = 0;
v = strtoll(s, &end, 0);
if (errno || *end != '\0' || end == s)
return -1;
*out = v;
return 0;
}
/*
* Parse "<a>.<b>.<c>" into abc[0..2] (skxt_a, skxt_b, skxt_c). Each
* component must be a non-empty unsigned base-0 integer.
*/
static int parse_dotted_name(const char *s, uint64_t abc[3])
{
const char *p = s;
char *end;
int i;
for (i = 0; i < 3; i++) {
if (*p == '\0' || *p == '.')
return -1;
errno = 0;
abc[i] = strtoull(p, &end, 0);
if (errno || end == p)
return -1;
if (i < 2) {
if (*end != '.')
return -1;
p = end + 1;
} else {
if (*end != '\0')
return -1;
}
}
return 0;
}
int main(int argc, char **argv)
{
struct scoutfs_ioctl_inject_totl_delta itd = { 0 };
uint64_t abc[3];
int64_t total, count;
int fd;
int ret;
if (argc != 5)
usage(argv[0]);
if (parse_dotted_name(argv[2], abc) ||
parse_s64(argv[3], &total) ||
parse_s64(argv[4], &count)) {
fprintf(stderr, "could not parse arguments\n");
usage(argv[0]);
}
itd.name[0] = abc[0];
itd.name[1] = abc[1];
itd.name[2] = abc[2];
itd.total = total;
itd.count = count;
fd = open(argv[1], O_RDONLY | O_DIRECTORY);
if (fd < 0) {
fprintf(stderr, "open(%s): %s\n", argv[1], strerror(errno));
return 1;
}
ret = ioctl(fd, SCOUTFS_IOC_INJECT_TOTL_DELTA, &itd);
if (ret < 0) {
fprintf(stderr,
"INJECT_TOTL_DELTA(%" PRIu64 ".%" PRIu64 ".%" PRIu64
", total=%" PRId64 ", count=%" PRId64 "): %s\n",
abc[0], abc[1], abc[2], total, count, strerror(errno));
close(fd);
return 1;
}
close(fd);
return 0;
}

View File

@@ -0,0 +1,43 @@
#
# Exercise the SCOUTFS_IOC_INJECT_TOTL_DELTA ioctl that injects totl
# deltas directly via totl-delta-inject(1).
#
t_require_commands setfattr scoutfs sync rm touch totl-delta-inject
# force a log merge then read-xattr-totals filtered to our own keys
read_totals()
{
t_force_log_merge
sync
echo 1 > $(t_debugfs_path)/drop_weak_item_cache
scoutfs read-xattr-totals -p "$T_M0" | \
grep -E '^8888\.' || true
}
echo "== setup three files contributing to totl 8888.0.0"
touch "$T_D0/f1" "$T_D0/f2" "$T_D0/f3"
setfattr -n scoutfs.totl.inj.8888.0.0 -v 2 "$T_D0/f1"
setfattr -n scoutfs.totl.inj.8888.0.0 -v 8 "$T_D0/f2"
setfattr -n scoutfs.totl.inj.8888.0.0 -v 32 "$T_D0/f3"
echo "== merge baseline into fs_root"
read_totals
echo "== inject (+128, +2) unbalances totl 8888.0.0"
totl-delta-inject "$T_M0" 8888.0.0 128 2
read_totals
echo "== unlink f3 (value 32) produces a -32/-1 delta"
rm -f "$T_D0/f3"
read_totals
echo "== inject (-128, -2) restores accounting for the remaining files"
totl-delta-inject "$T_M0" 8888.0.0 -128 -2
read_totals
echo "== cleanup"
rm -f "$T_D0/f1" "$T_D0/f2"
read_totals
t_pass

View File

@@ -0,0 +1,81 @@
#
# Exercise scoutfs recompute-xattr-total. Uses totl-delta-inject(1) to seed
# corruption.
#
t_require_commands setfattr scoutfs sync rm touch totl-delta-inject
# force a log merge then read-xattr-totals filtered to our own keys
read_totals()
{
t_force_log_merge
sync
echo 1 > $(t_debugfs_path)/drop_weak_item_cache
scoutfs read-xattr-totals -p "$T_M0" | \
grep -E '^8888\.' || true
}
# Print recompute warnings emitted since the last snapshot, with the
# volatile timestamp/fsid/rid prefix stripped, for golden comparison.
DMESG_SNAP="$T_TMPDIR/dmesg.recompute.snap"
dmesg > "$DMESG_SNAP"
recompute_warn()
{
local cur="$T_TMPDIR/dmesg.recompute.cur"
dmesg > "$cur"
diff "$DMESG_SNAP" "$cur" | \
sed -n 's/^> \[ *[0-9.]\+\] scoutfs [^ ]\+ warning: \(totl recompute applying delta .*\)$/\1/p'
mv "$cur" "$DMESG_SNAP"
}
echo "== setup three files contributing to totl 8888.0.0"
touch "$T_D0/f1" "$T_D0/f2" "$T_D0/f3"
setfattr -n scoutfs.totl.inj.8888.0.0 -v 2 "$T_D0/f1"
setfattr -n scoutfs.totl.inj.8888.0.0 -v 8 "$T_D0/f2"
setfattr -n scoutfs.totl.inj.8888.0.0 -v 32 "$T_D0/f3"
echo "== merge baseline into fs_root"
read_totals
echo "== recompute on a clean baseline is a no-op"
scoutfs recompute-xattr-total -p "$T_M0" 8888.0.0
recompute_warn
read_totals
echo "== inject (+128, +2) corrupts totl 8888.0.0"
totl-delta-inject "$T_M0" 8888.0.0 128 2
read_totals
echo "== recompute restores 8888.0.0"
scoutfs recompute-xattr-total -p "$T_M0" 8888.0.0
recompute_warn
read_totals
echo "== inject (-50, +1) corrupts totl 8888.0.0 again"
totl-delta-inject "$T_M0" 8888.0.0 -50 1
read_totals
echo "== recompute restores 8888.0.0"
scoutfs recompute-xattr-total -p "$T_M0" 8888.0.0
recompute_warn
read_totals
echo "== recompute on an unused key is a no-op"
scoutfs recompute-xattr-total -p "$T_M0" 8888.1.1
recompute_warn
read_totals
echo "== inject (+7, +1) at unused key 8888.1.1 then recompute clears it"
totl-delta-inject "$T_M0" 8888.1.1 7 1
read_totals
scoutfs recompute-xattr-total -p "$T_M0" 8888.1.1
recompute_warn
read_totals
echo "== cleanup"
rm -f "$T_D0/f1" "$T_D0/f2" "$T_D0/f3"
read_totals
t_pass

View File

@@ -0,0 +1,129 @@
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <argp.h>
#include "sparse.h"
#include "parse.h"
#include "util.h"
#include "format.h"
#include "ioctl.h"
#include "cmd.h"
struct recompute_xattr_total_args {
char *path;
u64 name[3];
unsigned name_set:1;
};
/* parse "<a>.<b>.<c>" into name[0..2] */
static int parse_dotted_name(const char *s, u64 name[3])
{
const char *p = s;
char *end;
int i;
for (i = 0; i < 3; i++) {
if (*p == '\0' || *p == '.')
return -1;
errno = 0;
name[i] = strtoull(p, &end, 0);
if (errno || end == p)
return -1;
if (i < 2) {
if (*end != '.')
return -1;
p = end + 1;
} else {
if (*end != '\0')
return -1;
}
}
return 0;
}
static int do_recompute_xattr_total(struct recompute_xattr_total_args *args)
{
struct scoutfs_ioctl_recompute_totl rt;
int ret;
int fd;
fd = get_path(args->path, O_RDONLY);
if (fd < 0)
return fd;
memset(&rt, 0, sizeof(rt));
rt.name[0] = args->name[0];
rt.name[1] = args->name[1];
rt.name[2] = args->name[2];
ret = ioctl(fd, SCOUTFS_IOC_RECOMPUTE_TOTL, &rt);
if (ret < 0) {
ret = -errno;
fprintf(stderr, "recompute_xattr_total ioctl failed: %s (%d)\n",
strerror(errno), errno);
}
close(fd);
return ret;
}
static int parse_opt(int key, char *arg, struct argp_state *state)
{
struct recompute_xattr_total_args *args = state->input;
switch (key) {
case 'p':
args->path = strdup_or_error(state, arg);
break;
case ARGP_KEY_ARG:
if (args->name_set)
argp_error(state, "more than one argument given");
if (parse_dotted_name(arg, args->name))
argp_error(state, "totl key parse error");
args->name_set = 1;
break;
case ARGP_KEY_FINI:
if (!args->name_set)
argp_error(state, "must provide totl key");
break;
default:
break;
}
return 0;
}
static struct argp_option options[] = {
{ "path", 'p', "PATH", 0, "Path to ScoutFS filesystem"},
{ NULL }
};
static struct argp argp = {
options,
parse_opt,
"A.B.C",
"Recompute a .totl. xattr key from its contributing xattrs and repair drift"
};
static int recompute_xattr_total_cmd(int argc, char **argv)
{
struct recompute_xattr_total_args args = { 0 };
int ret;
ret = argp_parse(&argp, argc, argv, 0, NULL, &args);
if (ret)
return ret;
return do_recompute_xattr_total(&args);
}
static void __attribute__((constructor)) recompute_xattr_total_ctor(void)
{
cmd_register_argp("recompute-xattr-total", &argp, GROUP_INFO, recompute_xattr_total_cmd);
}