mirror of
https://github.com/versity/scoutfs.git
synced 2026-05-01 10:25:43 +00:00
Compare commits
5 Commits
main
...
auke/totl-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85ecc136c7 | ||
|
|
1e5cc26bda | ||
|
|
778ccd247b | ||
|
|
d89d026eb1 | ||
|
|
5aac1dd741 |
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
217
kmod/src/xattr.c
217
kmod/src/xattr.c
@@ -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);
|
||||
|
||||
@@ -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
1
tests/.gitignore
vendored
@@ -12,3 +12,4 @@ src/o_tmpfile_umask
|
||||
src/o_tmpfile_linkat
|
||||
src/mmap_stress
|
||||
src/mmap_validate
|
||||
src/totl-delta-inject
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
10
tests/golden/totl-delta-inject
Normal file
10
tests/golden/totl-delta-inject
Normal 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
|
||||
23
tests/golden/totl-recompute
Normal file
23
tests/golden/totl-recompute
Normal 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
|
||||
@@ -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
|
||||
|
||||
121
tests/src/totl-delta-inject.c
Normal file
121
tests/src/totl-delta-inject.c
Normal 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;
|
||||
}
|
||||
43
tests/tests/totl-delta-inject.sh
Normal file
43
tests/tests/totl-delta-inject.sh
Normal 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
|
||||
81
tests/tests/totl-recompute.sh
Normal file
81
tests/tests/totl-recompute.sh
Normal 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
|
||||
129
utils/src/recompute_xattr_total.c
Normal file
129
utils/src/recompute_xattr_total.c
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user