diff --git a/tests/.gitignore b/tests/.gitignore index 32ad161c..18878dab 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -12,3 +12,4 @@ src/o_tmpfile_umask src/o_tmpfile_linkat src/mmap_stress src/mmap_validate +src/totl-delta-inject diff --git a/tests/Makefile b/tests/Makefile index 3a2380dc..dd6b8409 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -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) diff --git a/tests/golden/totl-delta-inject b/tests/golden/totl-delta-inject new file mode 100644 index 00000000..c98f1976 --- /dev/null +++ b/tests/golden/totl-delta-inject @@ -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 diff --git a/tests/sequence b/tests/sequence index 3107975d..d2ac6a0d 100644 --- a/tests/sequence +++ b/tests/sequence @@ -29,6 +29,7 @@ totl-xattr-tag.sh basic-xattr-indx.sh quota.sh totl-merge-read.sh +totl-delta-inject.sh lock-refleak.sh lock-shrink-consistency.sh lock-shrink-read-race.sh diff --git a/tests/src/totl-delta-inject.c b/tests/src/totl-delta-inject.c new file mode 100644 index 00000000..10f7f68c --- /dev/null +++ b/tests/src/totl-delta-inject.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ioctl.h" + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s .. \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 ".." 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; +} diff --git a/tests/tests/totl-delta-inject.sh b/tests/tests/totl-delta-inject.sh new file mode 100644 index 00000000..772ec365 --- /dev/null +++ b/tests/tests/totl-delta-inject.sh @@ -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