mirror of
https://github.com/versity/scoutfs.git
synced 2026-06-09 21:22:36 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf56585559 |
@@ -1,52 +1,6 @@
|
||||
Versity ScoutFS Release Notes
|
||||
=============================
|
||||
|
||||
---
|
||||
v1.32
|
||||
\
|
||||
*June 2, 2026*
|
||||
|
||||
Fix writing POSIX ACLs over NFS mounts that export the scoutfs
|
||||
filesystem.
|
||||
|
||||
Add support for kernels in the RHEL 9.8 minor release.
|
||||
|
||||
Reduce unneeded block allocation when data\_prealloc\_contig\_only was
|
||||
set to 0. This will help achieve more efficient data space usage when
|
||||
writing small files.
|
||||
|
||||
---
|
||||
v1.31
|
||||
\
|
||||
*May 5, 2026*
|
||||
|
||||
Fix race between modifying quota rules and internal reading of the rules
|
||||
that tripped an assertion.
|
||||
|
||||
Fix a bug that could skip merging totl items under specific heavy write
|
||||
loads. This could lead to merged totl items incorrectly tracking the
|
||||
sum of all the contributing totl xattrs.
|
||||
|
||||
Fix many small low risk bugs in error paths that were found with code
|
||||
analysis and testing.
|
||||
|
||||
---
|
||||
v1.30
|
||||
\
|
||||
*Apr 21, 2026*
|
||||
|
||||
Fix a problem reading the accumulated totals of contributing .totl.
|
||||
xattrs when log merging is in progress. The problem would have readers
|
||||
of the totals calculate the sums incorrectly.
|
||||
|
||||
Fix a problem updating quota rules. There was a race where updates
|
||||
could be corrupted if they happened while a transaction was being
|
||||
written.
|
||||
|
||||
Fix a problem deleting files with .indx. xattrs. The internal indexing
|
||||
metadata wouldn't be properly deleted so the files would still claim to
|
||||
be present and visible in the index, though the file no longer existed.
|
||||
|
||||
---
|
||||
v1.29
|
||||
\
|
||||
|
||||
@@ -479,16 +479,6 @@ ifneq (,$(shell grep '^unsigned int stack_trace_save' include/linux/stacktrace.h
|
||||
ccflags-y += -DKC_STACK_TRACE_SAVE
|
||||
endif
|
||||
|
||||
#
|
||||
# v3.14-rc1-7-g4e34e719e457
|
||||
#
|
||||
# .set_acl callback added to struct inode_operations. Most kernels
|
||||
# we target have it, but el7 (3.10 base) does not, so detect.
|
||||
#
|
||||
ifneq (,$(shell grep 'int ..set_acl..struct' include/linux/fs.h))
|
||||
ccflags-y += -DKC_HAS_SET_ACL
|
||||
endif
|
||||
|
||||
#
|
||||
# v6.1-rc1-2-g138060ba92b3
|
||||
#
|
||||
@@ -506,12 +496,3 @@ endif
|
||||
ifneq (,$(shell grep 'struct posix_acl.*get_inode_acl' include/linux/fs.h))
|
||||
ccflags-y += -DKC_GET_INODE_ACL
|
||||
endif
|
||||
|
||||
#
|
||||
# v6.15-13744-g41cb08555c41
|
||||
#
|
||||
# from_timer renamed to timer_container_of.
|
||||
#
|
||||
ifneq (,$(shell grep 'define timer_container_of' include/linux/timer.h))
|
||||
ccflags-y += -DKC_TIMER_CONTAINER_OF
|
||||
endif
|
||||
|
||||
+2
-3
@@ -216,8 +216,7 @@ int scoutfs_set_acl(KC_VFS_NS_DEF
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
#else
|
||||
int scoutfs_set_acl(KC_VFS_NS_DEF
|
||||
struct inode *inode, struct posix_acl *acl, int type)
|
||||
int scoutfs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
|
||||
{
|
||||
#endif
|
||||
struct super_block *sb = inode->i_sb;
|
||||
@@ -310,7 +309,7 @@ int scoutfs_acl_set_xattr(struct dentry *dentry, const char *name, const void *v
|
||||
#ifdef KC_SET_ACL_DENTRY
|
||||
ret = scoutfs_set_acl(KC_VFS_INIT_NS dentry, acl, type);
|
||||
#else
|
||||
ret = scoutfs_set_acl(KC_VFS_INIT_NS dentry->d_inode, acl, type);
|
||||
ret = scoutfs_set_acl(dentry->d_inode, acl, type);
|
||||
#endif
|
||||
out:
|
||||
posix_acl_release(acl);
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@
|
||||
int scoutfs_set_acl(KC_VFS_NS_DEF
|
||||
struct dentry *dentry, struct posix_acl *acl, int type);
|
||||
#else
|
||||
int scoutfs_set_acl(KC_VFS_NS_DEF
|
||||
struct inode *inode, struct posix_acl *acl, int type);
|
||||
int scoutfs_set_acl(struct inode *inode, struct posix_acl *acl, int type);
|
||||
#endif
|
||||
#ifdef KC_GET_INODE_ACL
|
||||
struct posix_acl *scoutfs_get_acl(struct inode *inode, int type, bool rcu);
|
||||
|
||||
+3
-13
@@ -218,7 +218,6 @@ static void block_free_work(struct work_struct *work)
|
||||
|
||||
llist_for_each_entry_safe(bp, tmp, deleted, free_node) {
|
||||
block_free(sb, bp);
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,6 +467,9 @@ static int block_submit_bio(struct super_block *sb, struct block_private *bp,
|
||||
sector_t sector;
|
||||
int ret = 0;
|
||||
|
||||
if (scoutfs_forcing_unmount(sb))
|
||||
return -ENOLINK;
|
||||
|
||||
sector = bp->bl.blkno << (SCOUTFS_BLOCK_LG_SHIFT - 9);
|
||||
|
||||
WARN_ON_ONCE(bp->bl.blkno == U64_MAX);
|
||||
@@ -478,17 +480,6 @@ static int block_submit_bio(struct super_block *sb, struct block_private *bp,
|
||||
set_bit(BLOCK_BIT_IO_BUSY, &bp->bits);
|
||||
block_get(bp);
|
||||
|
||||
/*
|
||||
* A second thread may already be waiting on this block's completion
|
||||
* after this thread won the race to submit the block. We exit through
|
||||
* the block_end_io error path which sets BLOCK_BIT_ERROR and assures
|
||||
* that other callers in the waitq get woken up.
|
||||
*/
|
||||
if (scoutfs_forcing_unmount(sb)) {
|
||||
ret = -ENOLINK;
|
||||
goto end_io;
|
||||
}
|
||||
|
||||
blk_start_plug(&plug);
|
||||
|
||||
for (off = 0; off < SCOUTFS_BLOCK_LG_SIZE; off += PAGE_SIZE) {
|
||||
@@ -526,7 +517,6 @@ static int block_submit_bio(struct super_block *sb, struct block_private *bp,
|
||||
|
||||
blk_finish_plug(&plug);
|
||||
|
||||
end_io:
|
||||
/* let racing end_io know we're done */
|
||||
block_end_io(sb, opf, bp, ret);
|
||||
|
||||
|
||||
@@ -2183,8 +2183,6 @@ static int merge_read_item(struct super_block *sb, struct scoutfs_key *key, u64
|
||||
if (ret > 0) {
|
||||
if (ret == SCOUTFS_DELTA_COMBINED) {
|
||||
scoutfs_inc_counter(sb, btree_merge_delta_combined);
|
||||
if (seq > found->seq)
|
||||
found->seq = seq;
|
||||
} else if (ret == SCOUTFS_DELTA_COMBINED_NULL) {
|
||||
scoutfs_inc_counter(sb, btree_merge_delta_null);
|
||||
free_mitem(rng, found);
|
||||
@@ -2488,14 +2486,6 @@ int scoutfs_btree_merge(struct super_block *sb,
|
||||
mitem = next_mitem(mitem);
|
||||
free_mitem(&rng, tmp);
|
||||
}
|
||||
|
||||
if (mitem && walk_val_len == 0 &&
|
||||
!(walk_flags & (BTW_INSERT | BTW_DELETE)) &&
|
||||
scoutfs_trigger(sb, LOG_MERGE_FORCE_PARTIAL)) {
|
||||
ret = -ERANGE;
|
||||
*next_ret = mitem->key;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
+1
-9
@@ -422,8 +422,6 @@ static int alloc_block(struct super_block *sb, struct inode *inode,
|
||||
|
||||
mutex_lock(&datinf->mutex);
|
||||
|
||||
scoutfs_inode_get_onoff(inode, &online, &offline);
|
||||
|
||||
/* default to single allocation at the written block */
|
||||
start = iblock;
|
||||
count = 1;
|
||||
@@ -446,6 +444,7 @@ static int alloc_block(struct super_block *sb, struct inode *inode,
|
||||
* the preallocation size to the number of online
|
||||
* blocks.
|
||||
*/
|
||||
scoutfs_inode_get_onoff(inode, &online, &offline);
|
||||
if (iblock > 1 && iblock == online) {
|
||||
ret = scoutfs_ext_next(sb, &data_ext_ops, &args,
|
||||
iblock, 1, &found);
|
||||
@@ -487,13 +486,6 @@ static int alloc_block(struct super_block *sb, struct inode *inode,
|
||||
/* trim count by next extent after iblock */
|
||||
if (found.len && found.start > start && found.start < start + count)
|
||||
count = (found.start - start);
|
||||
|
||||
/*
|
||||
* Ramp the aligned region size up proportionally with
|
||||
* the file's online block count rather than jumping to
|
||||
* the full prealloc size.
|
||||
*/
|
||||
count = max_t(u64, 1, min(count, online));
|
||||
}
|
||||
|
||||
/* overall prealloc limit */
|
||||
|
||||
+1
-1
@@ -2063,7 +2063,7 @@ const struct inode_operations scoutfs_dir_iops = {
|
||||
#else
|
||||
.get_acl = scoutfs_get_acl,
|
||||
#endif
|
||||
#ifdef KC_HAS_SET_ACL
|
||||
#ifdef KC_SET_ACL_DENTRY
|
||||
.set_acl = scoutfs_set_acl,
|
||||
#endif
|
||||
.symlink = scoutfs_symlink,
|
||||
|
||||
+1
-1
@@ -222,7 +222,7 @@ static struct attribute *fence_attrs[] = {
|
||||
|
||||
static void fence_timeout(struct timer_list *timer)
|
||||
{
|
||||
struct pending_fence *fence = timer_container_of(fence, timer, timer);
|
||||
struct pending_fence *fence = from_timer(fence, timer, timer);
|
||||
struct super_block *sb = fence->sb;
|
||||
DECLARE_FENCE_INFO(sb, fi);
|
||||
|
||||
|
||||
+7
-9
@@ -239,9 +239,9 @@ static int forest_read_items(struct super_block *sb, struct scoutfs_key *key, u6
|
||||
* to reset their state and retry with a newer version of the btrees.
|
||||
*/
|
||||
int scoutfs_forest_read_items_roots(struct super_block *sb, struct scoutfs_net_roots *roots,
|
||||
u64 merge_input_seq, struct scoutfs_key *key,
|
||||
struct scoutfs_key *bloom_key, struct scoutfs_key *start,
|
||||
struct scoutfs_key *end, scoutfs_forest_item_cb cb, void *arg)
|
||||
struct scoutfs_key *key, struct scoutfs_key *bloom_key,
|
||||
struct scoutfs_key *start, struct scoutfs_key *end,
|
||||
scoutfs_forest_item_cb cb, void *arg)
|
||||
{
|
||||
struct forest_read_items_data rid = {
|
||||
.cb = cb,
|
||||
@@ -317,17 +317,15 @@ int scoutfs_forest_read_items_roots(struct super_block *sb, struct scoutfs_net_r
|
||||
|
||||
scoutfs_inc_counter(sb, forest_bloom_pass);
|
||||
|
||||
if ((le64_to_cpu(lt.flags) & SCOUTFS_LOG_TREES_FINALIZED) &&
|
||||
(merge_input_seq == 0 ||
|
||||
le64_to_cpu(lt.finalize_seq) < merge_input_seq))
|
||||
rid.fic |= FIC_MERGE_INPUT;
|
||||
if ((le64_to_cpu(lt.flags) & SCOUTFS_LOG_TREES_FINALIZED))
|
||||
rid.fic |= FIC_FINALIZED;
|
||||
|
||||
ret = scoutfs_btree_read_items(sb, <.item_root, key, start,
|
||||
end, forest_read_items, &rid);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
rid.fic &= ~FIC_MERGE_INPUT;
|
||||
rid.fic &= ~FIC_FINALIZED;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
@@ -347,7 +345,7 @@ int scoutfs_forest_read_items(struct super_block *sb,
|
||||
|
||||
ret = scoutfs_client_get_roots(sb, &roots);
|
||||
if (ret == 0)
|
||||
ret = scoutfs_forest_read_items_roots(sb, &roots, 0, key, bloom_key, start, end,
|
||||
ret = scoutfs_forest_read_items_roots(sb, &roots, key, bloom_key, start, end,
|
||||
cb, arg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
+4
-4
@@ -11,7 +11,7 @@ struct scoutfs_lock;
|
||||
/* caller gives an item to the callback */
|
||||
enum {
|
||||
FIC_FS_ROOT = (1 << 0),
|
||||
FIC_MERGE_INPUT = (1 << 1),
|
||||
FIC_FINALIZED = (1 << 1),
|
||||
};
|
||||
typedef int (*scoutfs_forest_item_cb)(struct super_block *sb, struct scoutfs_key *key, u64 seq,
|
||||
u8 flags, void *val, int val_len, int fic, void *arg);
|
||||
@@ -25,9 +25,9 @@ int scoutfs_forest_read_items(struct super_block *sb,
|
||||
struct scoutfs_key *end,
|
||||
scoutfs_forest_item_cb cb, void *arg);
|
||||
int scoutfs_forest_read_items_roots(struct super_block *sb, struct scoutfs_net_roots *roots,
|
||||
u64 merge_input_seq, struct scoutfs_key *key,
|
||||
struct scoutfs_key *bloom_key, struct scoutfs_key *start,
|
||||
struct scoutfs_key *end, scoutfs_forest_item_cb cb, void *arg);
|
||||
struct scoutfs_key *key, struct scoutfs_key *bloom_key,
|
||||
struct scoutfs_key *start, struct scoutfs_key *end,
|
||||
scoutfs_forest_item_cb cb, void *arg);
|
||||
int scoutfs_forest_set_bloom_bits(struct super_block *sb,
|
||||
struct scoutfs_lock *lock);
|
||||
void scoutfs_forest_set_max_seq(struct super_block *sb, u64 max_seq);
|
||||
|
||||
+2
-3
@@ -154,7 +154,7 @@ static const struct inode_operations scoutfs_file_iops = {
|
||||
#else
|
||||
.get_acl = scoutfs_get_acl,
|
||||
#endif
|
||||
#ifdef KC_HAS_SET_ACL
|
||||
#ifdef KC_SET_ACL_DENTRY
|
||||
.set_acl = scoutfs_set_acl,
|
||||
#endif
|
||||
.fiemap = scoutfs_data_fiemap,
|
||||
@@ -174,7 +174,7 @@ static const struct inode_operations scoutfs_special_iops = {
|
||||
#else
|
||||
.get_acl = scoutfs_get_acl,
|
||||
#endif
|
||||
#ifdef KC_HAS_SET_ACL
|
||||
#ifdef KC_SET_ACL_DENTRY
|
||||
.set_acl = scoutfs_set_acl,
|
||||
#endif
|
||||
};
|
||||
@@ -549,7 +549,6 @@ retry:
|
||||
goto out;
|
||||
if (scoutfs_data_wait_found(&dw)) {
|
||||
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE);
|
||||
lock = NULL;
|
||||
|
||||
/* XXX callee locks instead? */
|
||||
inode_unlock(inode);
|
||||
|
||||
@@ -1739,43 +1739,6 @@ 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, true);
|
||||
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;
|
||||
}
|
||||
|
||||
long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
@@ -1827,8 +1790,6 @@ 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);
|
||||
}
|
||||
|
||||
return -ENOTTY;
|
||||
|
||||
@@ -876,17 +876,4 @@ 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)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -489,9 +489,4 @@ static inline void stack_trace_print(unsigned long *entries, unsigned int nr_ent
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef KC_TIMER_CONTAINER_OF
|
||||
#define timer_container_of(var, callback_timer, timer_fieldname) \
|
||||
from_timer(var, callback_timer, timer_fieldname)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -813,7 +813,6 @@ int scoutfs_lock_invalidate_request(struct super_block *sb, u64 net_id,
|
||||
|
||||
out:
|
||||
if (!lock) {
|
||||
kfree(ireq);
|
||||
ret = scoutfs_client_lock_response(sb, net_id, nl);
|
||||
BUG_ON(ret); /* lock server doesn't fence timed out client requests */
|
||||
}
|
||||
|
||||
+2
-2
@@ -525,7 +525,7 @@ static int process_response(struct scoutfs_net_connection *conn,
|
||||
struct super_block *sb = conn->sb;
|
||||
struct message_send *msend;
|
||||
scoutfs_net_response_t resp_func = NULL;
|
||||
void *resp_data = NULL;
|
||||
void *resp_data;
|
||||
|
||||
spin_lock(&conn->lock);
|
||||
|
||||
@@ -804,7 +804,7 @@ static void scoutfs_net_recv_worker(struct work_struct *work)
|
||||
if (invalid_message(conn, nh)) {
|
||||
scoutfs_inc_counter(sb, net_recv_invalid_message);
|
||||
ret = -EBADMSG;
|
||||
goto out;
|
||||
break;
|
||||
}
|
||||
|
||||
data_len = le16_to_cpu(nh->data_len);
|
||||
|
||||
+15
-25
@@ -34,7 +34,6 @@
|
||||
#include "totl.h"
|
||||
#include "util.h"
|
||||
#include "quota.h"
|
||||
#include "trans.h"
|
||||
#include "counters.h"
|
||||
#include "scoutfs_trace.h"
|
||||
|
||||
@@ -1087,10 +1086,6 @@ int scoutfs_quota_mod_rule(struct super_block *sb, bool is_add,
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = scoutfs_hold_trans(sb, true);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
down_write(&qtinf->rwsem);
|
||||
|
||||
if (is_add) {
|
||||
@@ -1100,31 +1095,28 @@ int scoutfs_quota_mod_rule(struct super_block *sb, bool is_add,
|
||||
else if (ret == 0)
|
||||
ret = -EEXIST;
|
||||
if (ret < 0)
|
||||
goto release;
|
||||
goto unlock;
|
||||
|
||||
rule_to_rule_val(&rv, &rule);
|
||||
ret = scoutfs_item_create(sb, &key, &rv, sizeof(rv), lock);
|
||||
if (ret < 0)
|
||||
goto release;
|
||||
goto unlock;
|
||||
|
||||
} else {
|
||||
ret = find_rule(sb, &rule, &key, lock) ?:
|
||||
scoutfs_item_delete(sb, &key, lock);
|
||||
if (ret < 0)
|
||||
goto release;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
wait_event(qtinf->waitq, !ruleset_is_busy(qtinf));
|
||||
scoutfs_quota_invalidate(sb);
|
||||
ret = 0;
|
||||
|
||||
release:
|
||||
unlock:
|
||||
up_write(&qtinf->rwsem);
|
||||
scoutfs_release_trans(sb);
|
||||
|
||||
out:
|
||||
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE);
|
||||
|
||||
out:
|
||||
if (is_add)
|
||||
trace_scoutfs_quota_add_rule(sb, &rule, ret);
|
||||
else
|
||||
@@ -1143,17 +1135,12 @@ void scoutfs_quota_get_lock_range(struct scoutfs_key *start, struct scoutfs_key
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark the cached ruleset invalid and free the previous one once readers
|
||||
* drain. Called from cluster lock invalidation and from quota rule
|
||||
* modification.
|
||||
*
|
||||
* Cluster lock invalidation runs only after the lock layer has drained
|
||||
* local READ users. Since EBUSY is set only while a reader holds READ,
|
||||
* the reader has already published by the time we run.
|
||||
*
|
||||
* Quota rule modification waits on the waitq for any in-flight reader
|
||||
* to publish before calling here, so the next check rebuilds against
|
||||
* the newly written rules rather than the reader's stale result.
|
||||
* This is called during cluster lock invalidation to indicate that the
|
||||
* ruleset is no longer protected by cluster locking and might have been
|
||||
* modified. We mark the ruleset invalid and free it once all readers
|
||||
* drain. The next check will acquire the cluster lock and read the
|
||||
* rules. Because this is called during invalidation this is serialized
|
||||
* with write holders of cluster locks so we can never see -EBUSY here.
|
||||
*/
|
||||
void scoutfs_quota_invalidate(struct super_block *sb)
|
||||
{
|
||||
@@ -1167,10 +1154,13 @@ void scoutfs_quota_invalidate(struct super_block *sb)
|
||||
|
||||
spin_lock(&qtinf->lock);
|
||||
rs = rcu_dereference_protected(qtinf->ruleset, lockdep_is_held(&qtinf->lock));
|
||||
if (rs == ERR_PTR(-ENOENT) || !IS_ERR(rs))
|
||||
if (rs != ERR_PTR(-EINVAL))
|
||||
rcu_assign_pointer(qtinf->ruleset, ERR_PTR(-EINVAL));
|
||||
spin_unlock(&qtinf->lock);
|
||||
|
||||
/* cluster locking should have prevented this */
|
||||
BUG_ON(rs == ERR_PTR(-EBUSY));
|
||||
|
||||
if (!IS_ERR(rs))
|
||||
call_rcu(&rs->rcu, free_ruleset_rcu);
|
||||
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ static int recov_finished(struct recov_info *recinf)
|
||||
|
||||
static void timer_callback(struct timer_list *timer)
|
||||
{
|
||||
struct recov_info *recinf = timer_container_of(recinf, timer, timer);
|
||||
struct recov_info *recinf = from_timer(recinf, timer, timer);
|
||||
|
||||
recinf->timeout_fn(recinf->sb);
|
||||
}
|
||||
|
||||
+2
-1
@@ -1077,7 +1077,8 @@ static int next_log_merge_range(struct super_block *sb, struct scoutfs_btree_roo
|
||||
struct scoutfs_key key;
|
||||
int ret;
|
||||
|
||||
init_log_merge_key(&key, SCOUTFS_LOG_MERGE_RANGE_ZONE, 0, 0);
|
||||
key = *start;
|
||||
key.sk_zone = SCOUTFS_LOG_MERGE_RANGE_ZONE;
|
||||
scoutfs_key_set_ones(&rng->start);
|
||||
|
||||
do {
|
||||
|
||||
+17
-25
@@ -30,11 +30,6 @@ void scoutfs_totl_merge_init(struct scoutfs_totl_merging *merg)
|
||||
memset(merg, 0, sizeof(struct scoutfs_totl_merging));
|
||||
}
|
||||
|
||||
/*
|
||||
* bin the incoming merge inputs so that we can resolve delta items
|
||||
* properly. Finalized logs that are merge inputs are kept separately
|
||||
* from those that are not.
|
||||
*/
|
||||
void scoutfs_totl_merge_contribute(struct scoutfs_totl_merging *merg,
|
||||
u64 seq, u8 flags, void *val, int val_len, int fic)
|
||||
{
|
||||
@@ -44,10 +39,10 @@ void scoutfs_totl_merge_contribute(struct scoutfs_totl_merging *merg,
|
||||
merg->fs_seq = seq;
|
||||
merg->fs_total = le64_to_cpu(tval->total);
|
||||
merg->fs_count = le64_to_cpu(tval->count);
|
||||
} else if (fic & FIC_MERGE_INPUT) {
|
||||
merg->inp_seq = seq;
|
||||
merg->inp_total += le64_to_cpu(tval->total);
|
||||
merg->inp_count += le64_to_cpu(tval->count);
|
||||
} else if (fic & FIC_FINALIZED) {
|
||||
merg->fin_seq = seq;
|
||||
merg->fin_total += le64_to_cpu(tval->total);
|
||||
merg->fin_count += le64_to_cpu(tval->count);
|
||||
} else {
|
||||
merg->log_seq = seq;
|
||||
merg->log_total += le64_to_cpu(tval->total);
|
||||
@@ -58,18 +53,15 @@ void scoutfs_totl_merge_contribute(struct scoutfs_totl_merging *merg,
|
||||
/*
|
||||
* .totl. item merging has to be careful because the log btree merging
|
||||
* code can write partial results to the fs_root. This means that a
|
||||
* reader can see both cases where merge input deltas should be applied
|
||||
* to the old fs items and where they have already been applied to the
|
||||
* partially merged fs items.
|
||||
*
|
||||
* Only finalized log trees that are inputs to the current merge cycle
|
||||
* are tracked in the inp_ bucket. Finalized trees that aren't merge
|
||||
* inputs and active log trees are always applied unconditionally since
|
||||
* they cannot be in fs_root.
|
||||
* reader can see both cases where new finalized logs should be applied
|
||||
* to the old fs items and where old finalized logs have already been
|
||||
* applied to the partially merged fs items. Currently active logged
|
||||
* items are always applied on top of all cases.
|
||||
*
|
||||
* These cases are differentiated with a combination of sequence numbers
|
||||
* in items and the count of contributing xattrs. This lets us
|
||||
* recognize all cases, including when merge inputs were merged and
|
||||
* in items, the count of contributing xattrs, and a flag
|
||||
* differentiating finalized and active logged items. This lets us
|
||||
* recognize all cases, including when finalized logs were merged and
|
||||
* deleted the fs item.
|
||||
*/
|
||||
void scoutfs_totl_merge_resolve(struct scoutfs_totl_merging *merg, __u64 *total, __u64 *count)
|
||||
@@ -83,14 +75,14 @@ void scoutfs_totl_merge_resolve(struct scoutfs_totl_merging *merg, __u64 *total,
|
||||
*count = merg->fs_count;
|
||||
}
|
||||
|
||||
/* apply merge input deltas if they're newer or creating */
|
||||
if (((merg->fs_seq != 0) && (merg->inp_seq > merg->fs_seq)) ||
|
||||
((merg->fs_seq == 0) && (merg->inp_count > 0))) {
|
||||
*total += merg->inp_total;
|
||||
*count += merg->inp_count;
|
||||
/* apply finalized logs if they're newer or creating */
|
||||
if (((merg->fs_seq != 0) && (merg->fin_seq > merg->fs_seq)) ||
|
||||
((merg->fs_seq == 0) && (merg->fin_count > 0))) {
|
||||
*total += merg->fin_total;
|
||||
*count += merg->fin_count;
|
||||
}
|
||||
|
||||
/* always apply non-input finalized and active logs */
|
||||
/* always apply active logs which must be newer than fs and finalized */
|
||||
if (merg->log_seq > 0) {
|
||||
*total += merg->log_total;
|
||||
*count += merg->log_count;
|
||||
|
||||
+3
-3
@@ -7,9 +7,9 @@ struct scoutfs_totl_merging {
|
||||
u64 fs_seq;
|
||||
u64 fs_total;
|
||||
u64 fs_count;
|
||||
u64 inp_seq;
|
||||
u64 inp_total;
|
||||
s64 inp_count;
|
||||
u64 fin_seq;
|
||||
u64 fin_total;
|
||||
s64 fin_count;
|
||||
u64 log_seq;
|
||||
u64 log_total;
|
||||
s64 log_count;
|
||||
|
||||
@@ -46,7 +46,6 @@ static char *names[] = {
|
||||
[SCOUTFS_TRIGGER_SRCH_MERGE_STOP_SAFE] = "srch_merge_stop_safe",
|
||||
[SCOUTFS_TRIGGER_STATFS_LOCK_PURGE] = "statfs_lock_purge",
|
||||
[SCOUTFS_TRIGGER_RECLAIM_SKIP_FINALIZE] = "reclaim_skip_finalize",
|
||||
[SCOUTFS_TRIGGER_LOG_MERGE_FORCE_PARTIAL] = "log_merge_force_partial",
|
||||
};
|
||||
|
||||
bool scoutfs_trigger_test_and_clear(struct super_block *sb, unsigned int t)
|
||||
|
||||
@@ -9,7 +9,6 @@ enum scoutfs_trigger {
|
||||
SCOUTFS_TRIGGER_SRCH_MERGE_STOP_SAFE,
|
||||
SCOUTFS_TRIGGER_STATFS_LOCK_PURGE,
|
||||
SCOUTFS_TRIGGER_RECLAIM_SKIP_FINALIZE,
|
||||
SCOUTFS_TRIGGER_LOG_MERGE_FORCE_PARTIAL,
|
||||
SCOUTFS_TRIGGER_NR,
|
||||
};
|
||||
|
||||
|
||||
+27
-73
@@ -30,6 +30,7 @@
|
||||
#include "counters.h"
|
||||
#include "scoutfs_trace.h"
|
||||
#include "wkic.h"
|
||||
#include "msg.h"
|
||||
|
||||
/*
|
||||
* This weaker item cache differs from the core item cache in item.c:
|
||||
@@ -95,7 +96,6 @@ struct wkic_info {
|
||||
/* block reading slow path */
|
||||
struct mutex roots_mutex;
|
||||
struct scoutfs_net_roots roots;
|
||||
u64 merge_input_seq;
|
||||
u64 roots_read_seq;
|
||||
ktime_t roots_expire;
|
||||
|
||||
@@ -747,6 +747,16 @@ static void fill_page_items(struct super_block *sb, struct wkic_page *wpage, str
|
||||
rb_erase(&witem->node, root);
|
||||
kfree(witem);
|
||||
continue;
|
||||
} else if (tval->count == 0) {
|
||||
/*
|
||||
* BUG: there are no contributing items but count != 0,
|
||||
* which shouldn't happen - we've gone off kilt.
|
||||
*/
|
||||
scoutfs_err(sb, "non-zero value for zero count totl "SK_FMT", dropping item",
|
||||
SK_ARG(&witem->key));
|
||||
rb_erase(&witem->node, root);
|
||||
kfree(witem);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,79 +816,29 @@ static void free_page_list(struct super_block *sb, struct list_head *list)
|
||||
* read_seq number so that we can compare the age of the items in cached
|
||||
* pages. Only one request to refresh the roots is in progress at a
|
||||
* time. This is the slow path that's only used when the cache isn't
|
||||
* populated and the roots aren't cached.
|
||||
*
|
||||
* We read roots directly from the on-disk superblock rather than
|
||||
* requesting them from the server so that we can also read the
|
||||
* log_merge btree from the same superblock. The merge status item
|
||||
* seq tells us which finalized log trees are inputs to the current
|
||||
* merge, which is needed to correctly resolve totl delta items.
|
||||
* populated and the roots aren't cached. The root request is fast
|
||||
* enough, especially compared to the resulting item reading IO, that we
|
||||
* don't mind hiding it behind a trivial mutex.
|
||||
*/
|
||||
static int refresh_roots(struct super_block *sb, struct wkic_info *winf)
|
||||
{
|
||||
struct scoutfs_super_block *super;
|
||||
struct scoutfs_log_merge_status *stat;
|
||||
SCOUTFS_BTREE_ITEM_REF(iref);
|
||||
struct scoutfs_key key;
|
||||
int ret;
|
||||
|
||||
super = kmalloc(sizeof(*super), GFP_NOFS);
|
||||
if (!super)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = scoutfs_read_super(sb, super);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
winf->roots = (struct scoutfs_net_roots){
|
||||
.fs_root = super->fs_root,
|
||||
.logs_root = super->logs_root,
|
||||
.srch_root = super->srch_root,
|
||||
};
|
||||
|
||||
winf->merge_input_seq = 0;
|
||||
if (super->log_merge.ref.blkno) {
|
||||
scoutfs_key_set_zeros(&key);
|
||||
key.sk_zone = SCOUTFS_LOG_MERGE_STATUS_ZONE;
|
||||
ret = scoutfs_btree_lookup(sb, &super->log_merge, &key, &iref);
|
||||
if (ret == 0) {
|
||||
if (iref.val_len == sizeof(*stat)) {
|
||||
stat = iref.val;
|
||||
winf->merge_input_seq = le64_to_cpu(stat->seq);
|
||||
} else {
|
||||
ret = -EUCLEAN;
|
||||
}
|
||||
scoutfs_btree_put_iref(&iref);
|
||||
} else if (ret == -ENOENT) {
|
||||
ret = 0;
|
||||
}
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
winf->roots_read_seq++;
|
||||
winf->roots_expire = ktime_add_ms(ktime_get_raw(), WKIC_CACHE_LIFETIME_MS);
|
||||
out:
|
||||
kfree(super);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_roots(struct super_block *sb, struct wkic_info *winf,
|
||||
struct scoutfs_net_roots *roots_ret, u64 *merge_input_seq,
|
||||
u64 *read_seq, bool force_new)
|
||||
struct scoutfs_net_roots *roots_ret, u64 *read_seq, bool force_new)
|
||||
{
|
||||
struct scoutfs_net_roots roots;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&winf->roots_mutex);
|
||||
|
||||
if (force_new || ktime_before(winf->roots_expire, ktime_get_raw())) {
|
||||
ret = refresh_roots(sb, winf);
|
||||
ret = scoutfs_client_get_roots(sb, &roots);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
winf->roots = roots;
|
||||
winf->roots_read_seq++;
|
||||
winf->roots_expire = ktime_add_ms(ktime_get_raw(), WKIC_CACHE_LIFETIME_MS);
|
||||
}
|
||||
|
||||
*roots_ret = winf->roots;
|
||||
*merge_input_seq = winf->merge_input_seq;
|
||||
*read_seq = winf->roots_read_seq;
|
||||
ret = 0;
|
||||
out:
|
||||
@@ -921,30 +881,24 @@ static int insert_read_pages(struct super_block *sb, struct wkic_info *winf,
|
||||
struct scoutfs_key end;
|
||||
struct wkic_page *wpage;
|
||||
LIST_HEAD(pages);
|
||||
u64 merge_input_seq;
|
||||
u64 read_seq = 0;
|
||||
u64 read_seq;
|
||||
int ret;
|
||||
|
||||
ret = 0;
|
||||
retry_stale:
|
||||
ret = get_roots(sb, winf, &roots, &merge_input_seq, &read_seq, ret == -ESTALE);
|
||||
ret = get_roots(sb, winf, &roots, &read_seq, ret == -ESTALE);
|
||||
if (ret < 0)
|
||||
goto check_stale;
|
||||
goto out;
|
||||
|
||||
start = *range_start;
|
||||
end = *range_end;
|
||||
ret = scoutfs_forest_read_items_roots(sb, &roots, merge_input_seq, key, range_start,
|
||||
&start, &end, read_items_cb, &root);
|
||||
ret = scoutfs_forest_read_items_roots(sb, &roots, key, range_start, &start, &end,
|
||||
read_items_cb, &root);
|
||||
trace_scoutfs_wkic_read_items(sb, key, &start, &end);
|
||||
check_stale:
|
||||
ret = scoutfs_block_check_stale(sb, ret, &saved, &roots.fs_root.ref, &roots.logs_root.ref);
|
||||
if (ret < 0) {
|
||||
if (ret == -ESTALE) {
|
||||
/* not safe to retry due to delta items, must restart clean */
|
||||
free_item_tree(&root);
|
||||
root = RB_ROOT;
|
||||
if (ret == -ESTALE)
|
||||
goto retry_stale;
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
@@ -1265,7 +1265,6 @@ int scoutfs_xattr_drop(struct super_block *sb, u64 ino,
|
||||
ret = parse_indx_key(&tag_key, xat->name, xat->name_len, ino);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
scoutfs_xattr_set_indx_key_xid(&tag_key, le64_to_cpu(key.skx_id));
|
||||
}
|
||||
|
||||
if ((tgs.totl || tgs.indx) && locked_zone != tag_key.sk_zone) {
|
||||
|
||||
@@ -12,4 +12,3 @@ src/o_tmpfile_umask
|
||||
src/o_tmpfile_linkat
|
||||
src/mmap_stress
|
||||
src/mmap_validate
|
||||
src/totl-delta-inject
|
||||
|
||||
+1
-2
@@ -15,8 +15,7 @@ BIN := src/createmany \
|
||||
src/o_tmpfile_umask \
|
||||
src/o_tmpfile_linkat \
|
||||
src/mmap_stress \
|
||||
src/mmap_validate \
|
||||
src/totl-delta-inject
|
||||
src/mmap_validate
|
||||
|
||||
DEPS := $(wildcard src/*.d)
|
||||
|
||||
|
||||
+8
-64
@@ -20,6 +20,9 @@ t_filter_fs()
|
||||
# [ 2687.691366] BUG: KASAN: stack-out-of-bounds in get_reg+0x1bc/0x230
|
||||
# ...
|
||||
# [ 2687.706220] ==================================================================
|
||||
# [ 2687.707284] Disabling lock debugging due to kernel taint
|
||||
#
|
||||
# That final lock debugging message may not be included.
|
||||
#
|
||||
ignore_harmless_unwind_kasan_stack_oob()
|
||||
{
|
||||
@@ -43,6 +46,10 @@ awk '
|
||||
saved=""
|
||||
}
|
||||
( in_soob == 2 && $0 ~ /==================================================================/ ) {
|
||||
in_soob = 3
|
||||
soob_nr = NR
|
||||
}
|
||||
( in_soob == 3 && NR > soob_nr && $0 !~ /Disabling lock debugging/ ) {
|
||||
in_soob = 0
|
||||
}
|
||||
( !in_soob ) { print $0 }
|
||||
@@ -54,58 +61,6 @@ awk '
|
||||
'
|
||||
}
|
||||
|
||||
#
|
||||
# in el97+, XFS can generate a spurious lockdep circular dependency
|
||||
# warning about reclaim. Fixed upstream in e.g. v5.7-rc4-129-g6dcde60efd94
|
||||
#
|
||||
ignore_harmless_xfs_lockdep_warning()
|
||||
{
|
||||
awk '
|
||||
BEGIN {
|
||||
in_block = 0
|
||||
block_nr = 0
|
||||
buf = ""
|
||||
}
|
||||
( !in_block && $0 ~ /======================================================/ ) {
|
||||
in_block = 1
|
||||
block_nr = NR
|
||||
buf = $0 "\n"
|
||||
next
|
||||
}
|
||||
( in_block == 1 && NR == (block_nr + 1) ) {
|
||||
if (match($0, /WARNING: possible circular locking dependency detected/) != 0) {
|
||||
in_block = 2
|
||||
buf = buf $0 "\n"
|
||||
} else {
|
||||
in_block = 0
|
||||
printf "%s", buf
|
||||
print $0
|
||||
buf = ""
|
||||
}
|
||||
next
|
||||
}
|
||||
( in_block == 2 ) {
|
||||
buf = buf $0 "\n"
|
||||
if ($0 ~ /<\/TASK>/) {
|
||||
if (buf ~ /xfs_(nondir_|dir_)?ilock_class/ && buf ~ /fs_reclaim/) {
|
||||
# known xfs lockdep false positive, discard
|
||||
} else {
|
||||
printf "%s", buf
|
||||
}
|
||||
in_block = 0
|
||||
buf = ""
|
||||
}
|
||||
next
|
||||
}
|
||||
{ print $0 }
|
||||
END {
|
||||
if (buf) {
|
||||
printf "%s", buf
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
#
|
||||
# Filter out expected messages. Putting messages here implies that
|
||||
# tests aren't relying on messages to discover failures.. they're
|
||||
@@ -171,13 +126,6 @@ t_filter_dmesg()
|
||||
# orphan log trees reclaim is handled, not an error
|
||||
re="$re|scoutfs .* reclaiming orphan log trees"
|
||||
|
||||
# nfs can emit a whole range of messages we can ignore
|
||||
re="$re|Installing knfsd .*"
|
||||
re="$re|nfsd: .*"
|
||||
re="$re|NFSD: .*"
|
||||
re="$re|RPC: .*"
|
||||
re="$re|FS-Cache: .*"
|
||||
|
||||
# fencing tests force unmounts and trigger timeouts
|
||||
re="$re|scoutfs .* forcing unmount"
|
||||
re="$re|scoutfs .* reconnect timed out"
|
||||
@@ -228,10 +176,6 @@ t_filter_dmesg()
|
||||
# creating block devices may trigger this
|
||||
re="$re|block device autoloading is deprecated and will be removed."
|
||||
|
||||
# lockdep or kasan warnings can cause this
|
||||
re="$re|Disabling lock debugging due to kernel taint"
|
||||
|
||||
egrep -v "($re)" | \
|
||||
ignore_harmless_unwind_kasan_stack_oob | \
|
||||
ignore_harmless_xfs_lockdep_warning
|
||||
ignore_harmless_unwind_kasan_stack_oob
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
== write via NFS, read both sides
|
||||
== POSIX ACL set via NFS, read both sides
|
||||
user::rw-
|
||||
user:22222:rw-
|
||||
group::r--
|
||||
mask::rw-
|
||||
other::r--
|
||||
|
||||
user::rw-
|
||||
user:22222:rw-
|
||||
group::r--
|
||||
mask::rw-
|
||||
other::r--
|
||||
|
||||
== POSIX ACL set on scoutfs, read via NFS
|
||||
user::rw-
|
||||
user:22222:rw-
|
||||
group::r--
|
||||
group:44444:r--
|
||||
mask::rw-
|
||||
other::r--
|
||||
|
||||
== default ACL inheritance via NFS
|
||||
user::rw-
|
||||
user:22222:rwx #effective:rw-
|
||||
group::r-x #effective:r--
|
||||
mask::rw-
|
||||
other::r--
|
||||
|
||||
== NFS read demand-stages a released file
|
||||
1
|
||||
== cleanup
|
||||
@@ -1,54 +0,0 @@
|
||||
== testing invalid read-xattr-index arguments
|
||||
bad index position entry argument 'bad', it must be in the form "a.b.ino" where each value can be prefixed by '0' for octal or '0x' for hex
|
||||
scoutfs: read-xattr-index failed: Invalid argument (22)
|
||||
bad index position entry argument '1.2', it must be in the form "a.b.ino" where each value can be prefixed by '0' for octal or '0x' for hex
|
||||
scoutfs: read-xattr-index failed: Invalid argument (22)
|
||||
initial major index position '256' must be between 0 and 255, inclusive.
|
||||
scoutfs: read-xattr-index failed: Invalid argument (22)
|
||||
first index position 1.2.3 must be less than last index position 0.0.0
|
||||
scoutfs: read-xattr-index failed: Invalid argument (22)
|
||||
first index position 1.2.0 must be less than last index position 1.1.2
|
||||
scoutfs: read-xattr-index failed: Invalid argument (22)
|
||||
first index position 2.2.2 must be less than last index position 2.2.1
|
||||
scoutfs: read-xattr-index failed: Invalid argument (22)
|
||||
== testing invalid names
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/invalid: Numerical result out of range
|
||||
== testing boundary values
|
||||
0.0 found
|
||||
255.max found
|
||||
== indx xattr must have no value
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/noval: Invalid argument
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/noval: Invalid argument
|
||||
== set indx xattr and verify index entry
|
||||
found
|
||||
== setting same indx xattr again is a no-op
|
||||
found
|
||||
== removing non-existent indx xattr succeeds
|
||||
setfattr: /mnt/test/test/basic-xattr-indx/file: No such attribute
|
||||
still found
|
||||
== explicit xattr removal cleans up index entry
|
||||
== file deletion cleans up index entry
|
||||
found before delete
|
||||
== multiple indx xattrs on one file cleaned up by deletion
|
||||
entries before delete: 2
|
||||
entries after delete: 0
|
||||
== partial removal leaves other entries
|
||||
300 found
|
||||
== multiple files at same index position
|
||||
files at same position: 2
|
||||
surviving file found
|
||||
== cross-mount visibility
|
||||
found on mount 1
|
||||
== duplicate position deduplication
|
||||
entries for same position: 1
|
||||
@@ -8,10 +8,10 @@
|
||||
/mnt/test/test/data-prealloc/file-1: extents: 32
|
||||
/mnt/test/test/data-prealloc/file-2: extents: 32
|
||||
== any writes to region prealloc get full extents
|
||||
/mnt/test/test/data-prealloc/file-1: extents: 8
|
||||
/mnt/test/test/data-prealloc/file-2: extents: 8
|
||||
/mnt/test/test/data-prealloc/file-1: extents: 8
|
||||
/mnt/test/test/data-prealloc/file-2: extents: 8
|
||||
/mnt/test/test/data-prealloc/file-1: extents: 4
|
||||
/mnt/test/test/data-prealloc/file-2: extents: 4
|
||||
/mnt/test/test/data-prealloc/file-1: extents: 4
|
||||
/mnt/test/test/data-prealloc/file-2: extents: 4
|
||||
== streaming offline writes get full extents either way
|
||||
/mnt/test/test/data-prealloc/file-1: extents: 4
|
||||
/mnt/test/test/data-prealloc/file-2: extents: 4
|
||||
@@ -20,8 +20,8 @@
|
||||
== goofy preallocation amounts work
|
||||
/mnt/test/test/data-prealloc/file-1: extents: 6
|
||||
/mnt/test/test/data-prealloc/file-2: extents: 6
|
||||
/mnt/test/test/data-prealloc/file-1: extents: 10
|
||||
/mnt/test/test/data-prealloc/file-2: extents: 10
|
||||
/mnt/test/test/data-prealloc/file-1: extents: 6
|
||||
/mnt/test/test/data-prealloc/file-2: extents: 6
|
||||
/mnt/test/test/data-prealloc/file-1: extents: 3
|
||||
/mnt/test/test/data-prealloc/file-2: extents: 3
|
||||
== block writes into region allocs hole
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
== setup
|
||||
== concurrent quota mod and check across mounts
|
||||
== verify quota rules are consistent after race
|
||||
== verify file creation still works under quota
|
||||
file visible on mount 1
|
||||
== cleanup
|
||||
@@ -1,10 +0,0 @@
|
||||
== 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
|
||||
@@ -1,3 +0,0 @@
|
||||
== setup
|
||||
expected 4681
|
||||
== cleanup
|
||||
+2
-2
@@ -694,8 +694,8 @@ for t in $tests; do
|
||||
if [ "$sts" == "$T_PASS_STATUS" ]; then
|
||||
dmesg | t_filter_dmesg > "$T_TMPDIR/dmesg.after"
|
||||
diff --old-line-format="" --unchanged-line-format="" \
|
||||
"$T_TMPDIR/dmesg.before" "$T_TMPDIR/dmesg.after" | \
|
||||
grep -v '^$' > "$T_TMPDIR/dmesg.new"
|
||||
"$T_TMPDIR/dmesg.before" "$T_TMPDIR/dmesg.after" > \
|
||||
"$T_TMPDIR/dmesg.new"
|
||||
|
||||
if [ -s "$T_TMPDIR/dmesg.new" ]; then
|
||||
message="unexpected messages in dmesg"
|
||||
|
||||
@@ -3,7 +3,6 @@ basic-block-counts.sh
|
||||
basic-bad-mounts.sh
|
||||
basic-posix-acl.sh
|
||||
basic-acl-consistency.sh
|
||||
basic-nfs.sh
|
||||
inode-items-updated.sh
|
||||
simple-inode-index.sh
|
||||
simple-staging.sh
|
||||
@@ -27,11 +26,7 @@ srch-basic-functionality.sh
|
||||
simple-xattr-unit.sh
|
||||
retention-basic.sh
|
||||
totl-xattr-tag.sh
|
||||
basic-xattr-indx.sh
|
||||
quota.sh
|
||||
totl-merge-read.sh
|
||||
quota-invalidate-race.sh
|
||||
totl-delta-inject.sh
|
||||
lock-refleak.sh
|
||||
lock-shrink-consistency.sh
|
||||
lock-shrink-read-race.sh
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
#
|
||||
# Test basic scoutfs-nfs interactions:
|
||||
# - read/write
|
||||
# - stage/release and data wait
|
||||
# - nfs setacl/getacl mapping
|
||||
#
|
||||
|
||||
t_require_commands scoutfs setfacl getfacl exportfs mount.nfs umount \
|
||||
stat dd cmp systemctl
|
||||
|
||||
systemctl start nfs-server >> "$T_TMPDIR/nfs.log" 2>&1 || \
|
||||
t_skip "nfs-server not available"
|
||||
|
||||
# Keep file creation modes deterministic for the ACL golden output.
|
||||
umask 022
|
||||
|
||||
EXPORT_OPTS="rw,async,no_root_squash,no_subtree_check,fsid=42"
|
||||
NFS_MNT="$T_TMP.nfs"
|
||||
NFS_DIR="$NFS_MNT/test/basic-nfs"
|
||||
|
||||
filter() { sed "s@$T_TMPDIR@T_TMPDIR@g" | t_filter_fs; }
|
||||
gf() { getfacl -n --omit-header "$@" 2>/dev/null; }
|
||||
|
||||
teardown_nfs()
|
||||
{
|
||||
(
|
||||
umount "$NFS_MNT"
|
||||
exportfs -u "127.0.0.1:$T_M0"
|
||||
exportfs -f
|
||||
systemctl stop nfs-server
|
||||
rmdir "$NFS_MNT"
|
||||
) >> "$T_TMPDIR/nfs.log" 2>&1
|
||||
}
|
||||
trap teardown_nfs EXIT
|
||||
|
||||
exportfs -u "127.0.0.1:$T_M0" >> "$T_TMPDIR/nfs.log" 2>&1 || true
|
||||
t_quiet mkdir -p "$NFS_MNT"
|
||||
exportfs -o "$EXPORT_OPTS" "127.0.0.1:$T_M0" >> "$T_TMPDIR/nfs.log" 2>&1
|
||||
mount.nfs -o vers=3,noac,actimeo=0 "127.0.0.1:$T_M0" "$NFS_MNT" >> "$T_TMPDIR/nfs.log" 2>&1
|
||||
|
||||
test -d "$NFS_DIR" || t_fail "test dir $NFS_DIR not visible over NFS"
|
||||
|
||||
echo "== write via NFS, read both sides"
|
||||
dd if=/dev/urandom bs=4096 count=1 of="$T_TMP.data" status=none
|
||||
cp "$T_TMP.data" "$NFS_DIR/file"
|
||||
cmp "$T_TMP.data" "$T_D0/file"
|
||||
cmp "$T_TMP.data" "$NFS_DIR/file"
|
||||
|
||||
echo "== POSIX ACL set via NFS, read both sides"
|
||||
setfacl -m u:22222:rw "$NFS_DIR/file" 2>&1 | filter
|
||||
gf "$NFS_DIR/file"
|
||||
gf "$T_D0/file"
|
||||
|
||||
echo "== POSIX ACL set on scoutfs, read via NFS"
|
||||
setfacl -m g:44444:r "$T_D0/file" 2>&1 | filter
|
||||
gf "$NFS_DIR/file"
|
||||
|
||||
echo "== default ACL inheritance via NFS"
|
||||
mkdir "$NFS_DIR/d"
|
||||
setfacl -d -m u:22222:rwx "$NFS_DIR/d" 2>&1 | filter
|
||||
touch "$NFS_DIR/d/child"
|
||||
gf "$NFS_DIR/d/child"
|
||||
|
||||
echo "== NFS read demand-stages a released file"
|
||||
dd if=/dev/urandom bs=4096 count=1 of="$T_TMP.big" status=none
|
||||
cp "$T_TMP.big" "$T_D0/big"
|
||||
sync
|
||||
vers=$(scoutfs stat -s data_version "$T_D0/big")
|
||||
t_quiet scoutfs release "$T_D0/big" -V "$vers" -o 0 -l 4K
|
||||
|
||||
# NFS read against the offline file blocks in scoutfs_read waiting
|
||||
# for the data to come back online.
|
||||
cat "$NFS_DIR/big" > "$T_TMP.read" &
|
||||
read_pid=$!
|
||||
sleep 1
|
||||
scoutfs data-waiting -B 0 -I 0 -p "$T_D0" | wc -l
|
||||
|
||||
t_quiet scoutfs stage "$T_TMP.big" "$T_D0/big" -V "$vers" -o 0 -l 4096
|
||||
wait "$read_pid"
|
||||
cmp "$T_TMP.big" "$T_TMP.read"
|
||||
|
||||
echo "== cleanup"
|
||||
rm -f "$T_D0/file" "$T_D0/big"
|
||||
rm -rf "$T_D0/d"
|
||||
|
||||
t_pass
|
||||
@@ -1,143 +0,0 @@
|
||||
#
|
||||
# Test basic .indx. xattr tag functionality and index entry lifecycle
|
||||
#
|
||||
|
||||
t_require_commands touch rm setfattr scoutfs stat
|
||||
t_require_mounts 2
|
||||
|
||||
# query index from a specific mount, default mount 0
|
||||
read_xattr_index()
|
||||
{
|
||||
local nr="${1:-0}"
|
||||
local mnt="$(eval echo \$T_M$nr)"
|
||||
shift
|
||||
|
||||
sync
|
||||
echo 1 > $(t_debugfs_path $nr)/drop_weak_item_cache
|
||||
scoutfs read-xattr-index -p "$mnt" "$@"
|
||||
}
|
||||
|
||||
MAJOR=5
|
||||
MINOR=100
|
||||
|
||||
echo "== testing invalid read-xattr-index arguments"
|
||||
scoutfs read-xattr-index -p "$T_M0" bad 2>&1
|
||||
scoutfs read-xattr-index -p "$T_M0" 1.2 2>&1
|
||||
scoutfs read-xattr-index -p "$T_M0" 1.2.3 256.0.0 2>&1
|
||||
scoutfs read-xattr-index -p "$T_M0" 1.2.3 0.0.0 2>&1
|
||||
scoutfs read-xattr-index -p "$T_M0" 1.2.0 1.1.2 2>&1
|
||||
scoutfs read-xattr-index -p "$T_M0" 2.2.2 2.2.1 2>&1
|
||||
|
||||
echo "== testing invalid names"
|
||||
touch "$T_D0/invalid"
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test.. "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test..$MINOR "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR. "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test.256.$MINOR "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test.abc.$MINOR "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.abc "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test.-1.$MINOR "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.-1 "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test.18446744073709551616.$MINOR "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.$(printf 'x%.0s' $(seq 1 240)).$MAJOR.$MINOR "$T_D0/invalid" 2>&1 | t_filter_fs
|
||||
rm -f "$T_D0/invalid"
|
||||
|
||||
echo "== testing boundary values"
|
||||
touch "$T_D0/boundary"
|
||||
INO=$(stat -c "%i" "$T_D0/boundary")
|
||||
setfattr -n scoutfs.hide.indx.test.0.0 "$T_D0/boundary"
|
||||
read_xattr_index 0 0.0.0 0.0.-1 | awk '($3 == "'$INO'") {print "0.0 found"}'
|
||||
setfattr -x scoutfs.hide.indx.test.0.0 "$T_D0/boundary"
|
||||
setfattr -n scoutfs.hide.indx.test.255.18446744073709551615 "$T_D0/boundary"
|
||||
read_xattr_index 0 255.0.0 255.-1.-1 | awk '($3 == "'$INO'") {print "255.max found"}'
|
||||
setfattr -x scoutfs.hide.indx.test.255.18446744073709551615 "$T_D0/boundary"
|
||||
rm -f "$T_D0/boundary"
|
||||
|
||||
echo "== indx xattr must have no value"
|
||||
touch "$T_D0/noval"
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.$MINOR -v "" "$T_D0/noval" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.$MINOR -v 0 "$T_D0/noval" 2>&1 | t_filter_fs
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.$MINOR -v 1 "$T_D0/noval" 2>&1 | t_filter_fs
|
||||
rm -f "$T_D0/noval"
|
||||
|
||||
echo "== set indx xattr and verify index entry"
|
||||
touch "$T_D0/file"
|
||||
INO=$(stat -c "%i" "$T_D0/file")
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.$MINOR "$T_D0/file"
|
||||
read_xattr_index 0 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'") {print "found"}'
|
||||
|
||||
echo "== setting same indx xattr again is a no-op"
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.$MINOR "$T_D0/file"
|
||||
read_xattr_index 0 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'") {print "found"}'
|
||||
|
||||
echo "== removing non-existent indx xattr succeeds"
|
||||
setfattr -x scoutfs.hide.indx.nonexistent.$MAJOR.999 "$T_D0/file" 2>&1 | t_filter_fs
|
||||
read_xattr_index 0 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'") {print "still found"}'
|
||||
|
||||
echo "== explicit xattr removal cleans up index entry"
|
||||
setfattr -x scoutfs.hide.indx.test.$MAJOR.$MINOR "$T_D0/file"
|
||||
read_xattr_index 0 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'") {print "found orphan"}'
|
||||
rm -f "$T_D0/file"
|
||||
|
||||
echo "== file deletion cleans up index entry"
|
||||
touch "$T_D0/file2"
|
||||
INO=$(stat -c "%i" "$T_D0/file2")
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.$MINOR "$T_D0/file2"
|
||||
read_xattr_index 0 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'") {print "found before delete"}'
|
||||
rm -f "$T_D0/file2"
|
||||
read_xattr_index 0 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'") {print "found orphan after delete"}'
|
||||
|
||||
echo "== multiple indx xattrs on one file cleaned up by deletion"
|
||||
touch "$T_D0/file3"
|
||||
INO=$(stat -c "%i" "$T_D0/file3")
|
||||
setfattr -n scoutfs.hide.indx.a.$MAJOR.200 "$T_D0/file3"
|
||||
setfattr -n scoutfs.hide.indx.b.$MAJOR.300 "$T_D0/file3"
|
||||
BEFORE=$(read_xattr_index 0 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'")' | wc -l)
|
||||
echo "entries before delete: $BEFORE"
|
||||
rm -f "$T_D0/file3"
|
||||
AFTER=$(read_xattr_index 0 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'")' | wc -l)
|
||||
echo "entries after delete: $AFTER"
|
||||
|
||||
echo "== partial removal leaves other entries"
|
||||
touch "$T_D0/partial"
|
||||
INO=$(stat -c "%i" "$T_D0/partial")
|
||||
setfattr -n scoutfs.hide.indx.a.$MAJOR.200 "$T_D0/partial"
|
||||
setfattr -n scoutfs.hide.indx.b.$MAJOR.300 "$T_D0/partial"
|
||||
setfattr -x scoutfs.hide.indx.a.$MAJOR.200 "$T_D0/partial"
|
||||
read_xattr_index 0 $MAJOR.200.0 $MAJOR.200.-1 | awk '($3 == "'$INO'") {print "200 found"}'
|
||||
read_xattr_index 0 $MAJOR.300.0 $MAJOR.300.-1 | awk '($3 == "'$INO'") {print "300 found"}'
|
||||
rm -f "$T_D0/partial"
|
||||
|
||||
echo "== multiple files at same index position"
|
||||
touch "$T_D0/multi_a" "$T_D0/multi_b"
|
||||
INO_A=$(stat -c "%i" "$T_D0/multi_a")
|
||||
INO_B=$(stat -c "%i" "$T_D0/multi_b")
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.$MINOR "$T_D0/multi_a"
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.$MINOR "$T_D0/multi_b"
|
||||
COUNT=$(read_xattr_index 0 $MAJOR.$MINOR.0 $MAJOR.$MINOR.-1 | wc -l)
|
||||
echo "files at same position: $COUNT"
|
||||
rm -f "$T_D0/multi_a"
|
||||
read_xattr_index 0 $MAJOR.$MINOR.0 $MAJOR.$MINOR.-1 | awk '($3 == "'$INO_A'") {print "deleted file still found"}'
|
||||
read_xattr_index 0 $MAJOR.$MINOR.0 $MAJOR.$MINOR.-1 | awk '($3 == "'$INO_B'") {print "surviving file found"}'
|
||||
rm -f "$T_D0/multi_b"
|
||||
|
||||
echo "== cross-mount visibility"
|
||||
touch "$T_D0/file4"
|
||||
INO=$(stat -c "%i" "$T_D0/file4")
|
||||
setfattr -n scoutfs.hide.indx.test.$MAJOR.$MINOR "$T_D0/file4"
|
||||
read_xattr_index 1 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'") {print "found on mount 1"}'
|
||||
rm -f "$T_D0/file4"
|
||||
read_xattr_index 1 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'") {print "found orphan on mount 1"}'
|
||||
|
||||
echo "== duplicate position deduplication"
|
||||
touch "$T_D0/file5"
|
||||
INO=$(stat -c "%i" "$T_D0/file5")
|
||||
setfattr -n scoutfs.hide.indx.aa.$MAJOR.$MINOR "$T_D0/file5"
|
||||
setfattr -n scoutfs.hide.indx.bb.$MAJOR.$MINOR "$T_D0/file5"
|
||||
COUNT=$(read_xattr_index 0 $MAJOR.0.0 $MAJOR.-1.-1 | awk '($3 == "'$INO'")' | wc -l)
|
||||
echo "entries for same position: $COUNT"
|
||||
rm -f "$T_D0/file5"
|
||||
|
||||
t_pass
|
||||
@@ -1,70 +0,0 @@
|
||||
#
|
||||
# Regression for the BUG_ON in scoutfs_quota_invalidate when a concurrent
|
||||
# ruleset read on one mount races with a quota rule modification.
|
||||
#
|
||||
|
||||
t_require_mounts 2
|
||||
|
||||
TEST_UID=22222
|
||||
SET_UID="--ruid=$TEST_UID --euid=$TEST_UID"
|
||||
|
||||
echo "== setup"
|
||||
mkdir -p "$T_D0/dir"
|
||||
chown --quiet $TEST_UID "$T_D0/dir"
|
||||
|
||||
# totl xattr gives quota checks something to consult
|
||||
setfattr -n scoutfs.totl.test.1.1.1 -v 1 "$T_D0/dir"
|
||||
|
||||
echo "== concurrent quota mod and check across mounts"
|
||||
|
||||
(
|
||||
for i in $(seq 1 20); do
|
||||
scoutfs quota-add -p "$T_M0" \
|
||||
-r "1 1,L,- 1,L,- $i,L,- I 999999 -" 2>/dev/null
|
||||
scoutfs quota-del -p "$T_M0" \
|
||||
-r "1 1,L,- 1,L,- $i,L,- I 999999 -" 2>/dev/null
|
||||
done
|
||||
) &
|
||||
MOD_PID=$!
|
||||
|
||||
# same mount as the mod: races local read against invalidate
|
||||
(
|
||||
for i in $(seq 1 50); do
|
||||
setpriv $SET_UID touch "$T_D0/dir/race0_$i" 2>/dev/null
|
||||
rm -f "$T_D0/dir/race0_$i"
|
||||
done
|
||||
) &
|
||||
CHECK0_PID=$!
|
||||
|
||||
# other mount: drives cross-node lock traffic
|
||||
(
|
||||
for i in $(seq 1 50); do
|
||||
setpriv $SET_UID touch "$T_D1/dir/race1_$i" 2>/dev/null
|
||||
rm -f "$T_D1/dir/race1_$i"
|
||||
done
|
||||
) &
|
||||
CHECK1_PID=$!
|
||||
|
||||
t_quiet wait $MOD_PID
|
||||
t_quiet wait $CHECK0_PID
|
||||
t_quiet wait $CHECK1_PID
|
||||
|
||||
echo "== verify quota rules are consistent after race"
|
||||
scoutfs quota-wipe -p "$T_M0"
|
||||
scoutfs quota-list -p "$T_M0"
|
||||
|
||||
echo "== verify file creation still works under quota"
|
||||
scoutfs quota-add -p "$T_M0" -r "1 1,L,- 1,L,- 1,L,- I 999999 -"
|
||||
sync
|
||||
echo 1 > $(t_debugfs_path)/drop_weak_item_cache
|
||||
echo 1 > $(t_debugfs_path)/drop_quota_check_cache
|
||||
setpriv $SET_UID touch "$T_D0/dir/verify_file"
|
||||
test -f "$T_D1/dir/verify_file" && echo "file visible on mount 1"
|
||||
rm -f "$T_D0/dir/verify_file"
|
||||
scoutfs quota-wipe -p "$T_M0"
|
||||
|
||||
echo "== cleanup"
|
||||
setfattr -x scoutfs.totl.test.1.1.1 "$T_D0/dir"
|
||||
rm -rf "$T_D0/dir"
|
||||
|
||||
t_pass
|
||||
@@ -1,43 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
@@ -1,50 +0,0 @@
|
||||
#
|
||||
# Test that merge_read_item() correctly updates the sequence number when
|
||||
# combining delta items from multiple finalized log trees. Each mount
|
||||
# sets a totl value in its own 3-bit lane (powers of 8) so that any
|
||||
# double-counting overflows the lane and is caught by: or(v, exp) != exp.
|
||||
#
|
||||
|
||||
t_require_commands setfattr scoutfs
|
||||
t_require_mounts 5
|
||||
|
||||
echo "== setup"
|
||||
for nr in $(t_fs_nrs); do
|
||||
d=$(eval echo \$T_D$nr)
|
||||
for i in $(seq 1 2500); do : > "$d/f$nr$i"; done
|
||||
done
|
||||
sync
|
||||
t_force_log_merge
|
||||
|
||||
vals=(1 8 64 512 4096)
|
||||
expected=4681
|
||||
n=0
|
||||
for nr in $(t_fs_nrs); do
|
||||
d=$(eval echo \$T_D$nr)
|
||||
v=${vals[$((n++))]}
|
||||
for i in $(seq 1 2500); do
|
||||
setfattr -n "scoutfs.totl.t.$i.0.0" -v $v "$d/f$nr$i"
|
||||
done
|
||||
done
|
||||
|
||||
t_trigger_arm_silent log_merge_force_partial $(t_server_nr)
|
||||
|
||||
bad="$T_TMPDIR/bad"
|
||||
for nr in $(t_fs_nrs); do
|
||||
( while true; do
|
||||
echo 1 > "$(t_debugfs_path $nr)/drop_weak_item_cache"
|
||||
scoutfs read-xattr-totals -p "$(eval echo \$T_M$nr)" | \
|
||||
awk -F'[ =,]+' -v e=$expected 'or($2+0,e) != e'
|
||||
done ) >> "$bad" &
|
||||
done
|
||||
|
||||
echo "expected $expected"
|
||||
t_force_log_merge
|
||||
t_silent_kill $(jobs -p)
|
||||
test -s "$bad" && echo "double-counted:" && cat "$bad"
|
||||
|
||||
echo "== cleanup"
|
||||
for nr in $(t_fs_nrs); do
|
||||
find "$(eval echo \$T_D$nr)" -name "f$nr*" -delete
|
||||
done
|
||||
t_pass
|
||||
Reference in New Issue
Block a user