From 0316c220261c437f243139b4ea1b33ea0c2fba26 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Thu, 8 Jun 2023 12:06:04 -0700 Subject: [PATCH 1/4] Extend scoutfs_dir_add_next_linkrefs Extend scoutfs_dir_add_next_linkref() to be able to return multiple backrefs under the lock for each call and have it take an argument to limit the number of backrefs that can be added and returned. Its return code changes a bit in that it returns 1 on success instead of 0 so we have to be a little careful with callers who were expecting 0. It still returns -ENOENT when no entries are found. We break up its tracepoint into one that records each entry added and one that records the result of each call. This will be used by an ioctl to give callers just the entries that point to an inode instead of assembling full paths from the root. Signed-off-by: Zach Brown --- kmod/src/dir.c | 114 +++++++++++++++++++++++---------------- kmod/src/dir.h | 7 +-- kmod/src/export.c | 10 ++-- kmod/src/scoutfs_trace.h | 52 ++++++++++++------ 4 files changed, 113 insertions(+), 70 deletions(-) diff --git a/kmod/src/dir.c b/kmod/src/dir.c index 242a6f5c..d3a6e031 100644 --- a/kmod/src/dir.c +++ b/kmod/src/dir.c @@ -1253,75 +1253,93 @@ int scoutfs_symlink_drop(struct super_block *sb, u64 ino, } /* - * Find the next link backref key for the given ino starting from the - * given dir inode and final entry position. If we find a backref item - * we add an allocated copy of it to the head of the caller's list. + * Find the next link backref items for the given ino starting from the + * given dir inode and final entry position. For each backref item we + * add an allocated copy of it to the head of the caller's list. * - * Returns 0 if we added an entry, -ENOENT if we didn't, and -errno for - * search errors. + * Callers who are building a path can add one entry for each parent. + * They're left with a list of entries from the root down in list order. + * + * Callers who are gathering multiple entries for one inode get the + * entries in the opposite order that their items are found. + * + * Returns +ve for number of entries added, -ENOENT if no entries were + * found, or -errno on error. It weirdly won't return 0, but early + * callers preferred -ENOENT so we use that for the case of no entries. * * Callers are comfortable with the race inherent to incrementally - * building up a path with individual locked backref item lookups. + * gathering backrefs across multiple lock acquisitions. */ -int scoutfs_dir_add_next_linkref(struct super_block *sb, u64 ino, - u64 dir_ino, u64 dir_pos, - struct list_head *list) +int scoutfs_dir_add_next_linkrefs(struct super_block *sb, u64 ino, u64 dir_ino, u64 dir_pos, + int count, struct list_head *list) { + struct scoutfs_link_backref_entry *prev_ent = NULL; struct scoutfs_link_backref_entry *ent = NULL; struct scoutfs_lock *lock = NULL; struct scoutfs_key last_key; struct scoutfs_key key; + int nr = 0; int len; int ret; - ent = kmalloc(offsetof(struct scoutfs_link_backref_entry, - dent.name[SCOUTFS_NAME_LEN]), GFP_KERNEL); - if (!ent) { - ret = -ENOMEM; - goto out; - } - - INIT_LIST_HEAD(&ent->head); - init_dirent_key(&key, SCOUTFS_LINK_BACKREF_TYPE, ino, dir_ino, dir_pos); - init_dirent_key(&last_key, SCOUTFS_LINK_BACKREF_TYPE, ino, U64_MAX, - U64_MAX); + init_dirent_key(&last_key, SCOUTFS_LINK_BACKREF_TYPE, ino, U64_MAX, U64_MAX); ret = scoutfs_lock_ino(sb, SCOUTFS_LOCK_READ, 0, ino, &lock); if (ret) goto out; - ret = scoutfs_item_next(sb, &key, &last_key, &ent->dent, - dirent_bytes(SCOUTFS_NAME_LEN), lock); - scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ); - lock = NULL; - if (ret < 0) - goto out; + while (nr < count) { + ent = kmalloc(offsetof(struct scoutfs_link_backref_entry, + dent.name[SCOUTFS_NAME_LEN]), GFP_NOFS); + if (!ent) { + ret = -ENOMEM; + goto out; + } - len = ret - sizeof(struct scoutfs_dirent); - if (len < 1 || len > SCOUTFS_NAME_LEN) { - scoutfs_corruption(sb, SC_DIRENT_BACKREF_NAME_LEN, - corrupt_dirent_backref_name_len, - "ino %llu dir_ino %llu pos %llu key "SK_FMT" len %d", - ino, dir_ino, dir_pos, SK_ARG(&key), len); - ret = -EIO; - goto out; + INIT_LIST_HEAD(&ent->head); + + ret = scoutfs_item_next(sb, &key, &last_key, &ent->dent, + dirent_bytes(SCOUTFS_NAME_LEN), lock); + if (ret < 0) { + if (ret == -ENOENT && prev_ent) + prev_ent->last = true; + goto out; + } + + len = ret - sizeof(struct scoutfs_dirent); + if (len < 1 || len > SCOUTFS_NAME_LEN) { + scoutfs_corruption(sb, SC_DIRENT_BACKREF_NAME_LEN, + corrupt_dirent_backref_name_len, + "ino %llu dir_ino %llu pos %llu key "SK_FMT" len %d", + ino, dir_ino, dir_pos, SK_ARG(&key), len); + ret = -EIO; + goto out; + } + + ent->dir_ino = le64_to_cpu(key.skd_major); + ent->dir_pos = le64_to_cpu(key.skd_minor); + ent->name_len = len; + ent->d_type = dentry_type(ent->dent.type); + ent->last = false; + + trace_scoutfs_dir_add_next_linkref_found(sb, ino, ent->dir_ino, ent->dir_pos, + ent->name_len); + + list_add(&ent->head, list); + prev_ent = ent; + ent = NULL; + nr++; + scoutfs_key_inc(&key); } - list_add(&ent->head, list); - ent->dir_ino = le64_to_cpu(key.skd_major); - ent->dir_pos = le64_to_cpu(key.skd_minor); - ent->name_len = len; ret = 0; out: - trace_scoutfs_dir_add_next_linkref(sb, ino, dir_ino, dir_pos, ret, - ent ? ent->dir_ino : 0, - ent ? ent->dir_pos : 0, - ent ? ent->name_len : 0); + scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ); + trace_scoutfs_dir_add_next_linkrefs(sb, ino, dir_ino, dir_pos, count, nr, ret); - if (ent && list_empty(&ent->head)) - kfree(ent); - return ret; + kfree(ent); + return nr ?: ret; } static u64 first_backref_dir_ino(struct list_head *list) @@ -1396,7 +1414,7 @@ retry: } /* get the next link name to the given inode */ - ret = scoutfs_dir_add_next_linkref(sb, ino, dir_ino, dir_pos, list); + ret = scoutfs_dir_add_next_linkrefs(sb, ino, dir_ino, dir_pos, 1, list); if (ret < 0) goto out; @@ -1404,7 +1422,7 @@ retry: par_ino = first_backref_dir_ino(list); while (par_ino != SCOUTFS_ROOT_INO) { - ret = scoutfs_dir_add_next_linkref(sb, par_ino, 0, 0, list); + ret = scoutfs_dir_add_next_linkrefs(sb, par_ino, 0, 0, 1, list); if (ret < 0) { if (ret == -ENOENT) { /* restart if there was no parent component */ @@ -1416,6 +1434,8 @@ retry: par_ino = first_backref_dir_ino(list); } + + ret = 0; out: if (ret < 0) scoutfs_dir_free_backref_path(sb, list); diff --git a/kmod/src/dir.h b/kmod/src/dir.h index 5ee155ac..9bd1f193 100644 --- a/kmod/src/dir.h +++ b/kmod/src/dir.h @@ -15,6 +15,8 @@ struct scoutfs_link_backref_entry { u64 dir_ino; u64 dir_pos; u16 name_len; + u8 d_type; + bool last; struct scoutfs_dirent dent; /* the full name is allocated and stored in dent.name[] */ }; @@ -24,9 +26,8 @@ int scoutfs_dir_get_backref_path(struct super_block *sb, u64 ino, u64 dir_ino, void scoutfs_dir_free_backref_path(struct super_block *sb, struct list_head *list); -int scoutfs_dir_add_next_linkref(struct super_block *sb, u64 ino, - u64 dir_ino, u64 dir_pos, - struct list_head *list); +int scoutfs_dir_add_next_linkrefs(struct super_block *sb, u64 ino, u64 dir_ino, u64 dir_pos, + int count, struct list_head *list); int scoutfs_symlink_drop(struct super_block *sb, u64 ino, struct scoutfs_lock *lock, u64 i_size); diff --git a/kmod/src/export.c b/kmod/src/export.c index 29b89bc3..1b2fc6e2 100644 --- a/kmod/src/export.c +++ b/kmod/src/export.c @@ -114,8 +114,8 @@ static struct dentry *scoutfs_get_parent(struct dentry *child) int ret; u64 ino; - ret = scoutfs_dir_add_next_linkref(sb, scoutfs_ino(inode), 0, 0, &list); - if (ret) + ret = scoutfs_dir_add_next_linkrefs(sb, scoutfs_ino(inode), 0, 0, 1, &list); + if (ret < 0) return ERR_PTR(ret); ent = list_first_entry(&list, struct scoutfs_link_backref_entry, head); @@ -138,9 +138,9 @@ static int scoutfs_get_name(struct dentry *parent, char *name, LIST_HEAD(list); int ret; - ret = scoutfs_dir_add_next_linkref(sb, scoutfs_ino(inode), dir_ino, - 0, &list); - if (ret) + ret = scoutfs_dir_add_next_linkrefs(sb, scoutfs_ino(inode), dir_ino, + 0, 1, &list); + if (ret < 0) return ret; ret = -ENOENT; diff --git a/kmod/src/scoutfs_trace.h b/kmod/src/scoutfs_trace.h index 17bb9a03..bc84cad5 100644 --- a/kmod/src/scoutfs_trace.h +++ b/kmod/src/scoutfs_trace.h @@ -817,22 +817,17 @@ TRACE_EVENT(scoutfs_advance_dirty_super, TP_printk(SCSBF" super seq now %llu", SCSB_TRACE_ARGS, __entry->seq) ); -TRACE_EVENT(scoutfs_dir_add_next_linkref, +TRACE_EVENT(scoutfs_dir_add_next_linkref_found, TP_PROTO(struct super_block *sb, __u64 ino, __u64 dir_ino, - __u64 dir_pos, int ret, __u64 found_dir_ino, - __u64 found_dir_pos, unsigned int name_len), + __u64 dir_pos, unsigned int name_len), - TP_ARGS(sb, ino, dir_ino, dir_pos, ret, found_dir_pos, found_dir_ino, - name_len), + TP_ARGS(sb, ino, dir_ino, dir_pos, name_len), TP_STRUCT__entry( SCSB_TRACE_FIELDS __field(__u64, ino) __field(__u64, dir_ino) __field(__u64, dir_pos) - __field(int, ret) - __field(__u64, found_dir_ino) - __field(__u64, found_dir_pos) __field(unsigned int, name_len) ), @@ -841,16 +836,43 @@ TRACE_EVENT(scoutfs_dir_add_next_linkref, __entry->ino = ino; __entry->dir_ino = dir_ino; __entry->dir_pos = dir_pos; - __entry->ret = ret; - __entry->found_dir_ino = dir_ino; - __entry->found_dir_pos = dir_pos; __entry->name_len = name_len; ), - TP_printk(SCSBF" ino %llu dir_ino %llu dir_pos %llu ret %d found_dir_ino %llu found_dir_pos %llu name_len %u", - SCSB_TRACE_ARGS, __entry->ino, __entry->dir_pos, - __entry->dir_ino, __entry->ret, __entry->found_dir_pos, - __entry->found_dir_ino, __entry->name_len) + TP_printk(SCSBF" ino %llu dir_ino %llu dir_pos %llu name_len %u", + SCSB_TRACE_ARGS, __entry->ino, __entry->dir_ino, + __entry->dir_pos, __entry->name_len) +); + +TRACE_EVENT(scoutfs_dir_add_next_linkrefs, + TP_PROTO(struct super_block *sb, __u64 ino, __u64 dir_ino, + __u64 dir_pos, int count, int nr, int ret), + + TP_ARGS(sb, ino, dir_ino, dir_pos, count, nr, ret), + + TP_STRUCT__entry( + SCSB_TRACE_FIELDS + __field(__u64, ino) + __field(__u64, dir_ino) + __field(__u64, dir_pos) + __field(int, count) + __field(int, nr) + __field(int, ret) + ), + + TP_fast_assign( + SCSB_TRACE_ASSIGN(sb); + __entry->ino = ino; + __entry->dir_ino = dir_ino; + __entry->dir_pos = dir_pos; + __entry->count = count; + __entry->nr = nr; + __entry->ret = ret; + ), + + TP_printk(SCSBF" ino %llu dir_ino %llu dir_pos %llu count %d nr %d ret %d", + SCSB_TRACE_ARGS, __entry->ino, __entry->dir_ino, + __entry->dir_pos, __entry->count, __entry->nr, __entry->ret) ); TRACE_EVENT(scoutfs_write_begin, From 707752a7bf5611a87d4128223ca4ef39a401fe76 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Thu, 8 Jun 2023 12:09:09 -0700 Subject: [PATCH 2/4] Add get_referring_entries ioctl Add an ioctl that gives the callers all entries that refer to an inode. It's like a backwards readdir. It's a light bit of translation between the internal _add_next_linkrefs() list of entries and the ioctl interface of a buffer of entry structs. Signed-off-by: Zach Brown --- kmod/src/ioctl.c | 106 +++++++++++++++++++++++++++++++++++++++++++ kmod/src/ioctl.h | 114 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index 1ae9da1f..3bc9c546 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -1398,6 +1398,110 @@ out: return ret ?: nr; } +/* + * Copy entries that point to an inode to the user's buffer. We copy to + * userspace from copies of the entries that are acquired under a lock + * so that we don't fault while holding cluster locks. It also gives us + * a chance to limit the amount of work under each lock hold. + */ +static long scoutfs_ioc_get_referring_entries(struct file *file, unsigned long arg) +{ + struct super_block *sb = file_inode(file)->i_sb; + struct scoutfs_ioctl_get_referring_entries gre; + struct scoutfs_link_backref_entry *bref = NULL; + struct scoutfs_link_backref_entry *bref_tmp; + struct scoutfs_ioctl_dirent __user *uent; + struct scoutfs_ioctl_dirent ent; + LIST_HEAD(list); + u64 copied; + int name_len; + int bytes; + long nr; + int ret; + + if (!capable(CAP_DAC_READ_SEARCH)) + return -EPERM; + + if (copy_from_user(&gre, (void __user *)arg, sizeof(gre))) + return -EFAULT; + + uent = (void __user *)(unsigned long)gre.entries_ptr; + copied = 0; + nr = 0; + + /* use entry as cursor between calls */ + ent.dir_ino = gre.dir_ino; + ent.dir_pos = gre.dir_pos; + + for (;;) { + ret = scoutfs_dir_add_next_linkrefs(sb, gre.ino, ent.dir_ino, ent.dir_pos, 1024, + &list); + if (ret < 0) { + if (ret == -ENOENT) + ret = 0; + goto out; + } + + /* _add_next adds each entry to the head, _reverse for key order */ + list_for_each_entry_safe_reverse(bref, bref_tmp, &list, head) { + list_del_init(&bref->head); + + name_len = bref->name_len; + bytes = ALIGN(offsetof(struct scoutfs_ioctl_dirent, name[name_len + 1]), + 16); + if (copied + bytes > gre.entries_bytes) { + ret = -EINVAL; + goto out; + } + + ent.dir_ino = bref->dir_ino; + ent.dir_pos = bref->dir_pos; + ent.ino = gre.ino; + ent.entry_bytes = bytes; + ent.flags = bref->last ? SCOUTFS_IOCTL_DIRENT_FLAG_LAST : 0; + ent.d_type = bref->d_type; + ent.name_len = name_len; + + if (copy_to_user(uent, &ent, sizeof(struct scoutfs_ioctl_dirent)) || + copy_to_user(&uent->name[0], bref->dent.name, name_len) || + put_user('\0', &uent->name[name_len])) { + ret = -EFAULT; + goto out; + } + + kfree(bref); + bref = NULL; + + uent = (void __user *)uent + bytes; + copied += bytes; + nr++; + + if (nr == LONG_MAX || (ent.flags & SCOUTFS_IOCTL_DIRENT_FLAG_LAST)) { + ret = 0; + goto out; + } + } + + /* advance cursor pos from last copied entry */ + if (++ent.dir_pos == 0) { + if (++ent.dir_ino == 0) { + ret = 0; + goto out; + } + } + } + + ret = 0; +out: + kfree(bref); + list_for_each_entry_safe(bref, bref_tmp, &list, head) { + list_del_init(&bref->head); + kfree(bref); + } + + return nr ?: ret; +} + long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { @@ -1433,6 +1537,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return scoutfs_ioc_read_xattr_totals(file, arg); case SCOUTFS_IOC_GET_ALLOCATED_INOS: return scoutfs_ioc_get_allocated_inos(file, arg); + case SCOUTFS_IOC_GET_REFERRING_ENTRIES: + return scoutfs_ioc_get_referring_entries(file, arg); } return -ENOTTY; diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index 459ee7fb..7be22dbb 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -559,4 +559,118 @@ struct scoutfs_ioctl_get_allocated_inos { #define SCOUTFS_IOC_GET_ALLOCATED_INOS \ _IOW(SCOUTFS_IOCTL_MAGIC, 16, struct scoutfs_ioctl_get_allocated_inos) +/* + * Get directory entries that refer to a specific inode. + * + * @ino: The target ino that we're finding referring entries to. + * Constant across all the calls that make up an iteration over all the + * inode's entries. + * + * @dir_ino: The inode number of a directory containing the entry to our + * inode to search from. If this parent directory contains no more + * entries to our inode then we'll search through other parent directory + * inodes in inode order. + * + * @dir_pos: The position in the dir_ino parent directory of the entry + * to our inode to search from. If there is no entry at this position + * then we'll search through other entry positions in increasing order. + * If we exhaust the parent directory then we'll search through + * additional parent directories in inode order. + * + * @entries_ptr: A pointer to the buffer where found entries will be + * stored. The pointer must be aligned to 16 bytes. + * + * @entries_bytes: The size of the buffer that will contain entries. + * + * To start iterating set the desired target ino, dir_ino to 0, dir_pos + * to 0, and set result_ptr and _bytes to a sufficiently large buffer. + * Each entry struct that's stored in the buffer adds some overhead so a + * large multiple of the largest possible name is a reasonable choice. + * (A few multiples of PATH_MAX perhaps.) + * + * Each call returns the total number of entries that were stored in the + * entries buffer. Zero is returned when the search was successful and + * no referring entries were found. The entries can be iterated over by + * advancing each starting struct offset by the total number of bytes in + * each entry. If the _LAST flag is set on an entry then there were no + * more entries referring to the inode at the time of the call and + * iteration can be stopped. + * + * To resume iteration set the next call's starting dir_ino and dir_pos + * to one past the last entry seen. Increment the last entry's dir_pos, + * and if it wrapped to 0, increment its dir_ino. + * + * This does not check that the caller has permission to read the + * entries found in each containing directory. It requires + * CAP_DAC_READ_SEARCH which bypasses path traversal permissions + * checking. + * + * Entries returned by a single call can reflect any combination of + * racing creation and removal of entries. Each entry existed at the + * time it was read though it may have changed in the time it took to + * return from the call. The set of entries returned may no longer + * reflect the current set of entries and may not have existed at the + * same time. + * + * This has no knowledge of the life cycle of the inode. It can return + * 0 when there are no referring entries because either the target inode + * doesn't exist, it is in the process of being deleted, or because it + * is still open while being unlinked. + * + * On success this returns the number of entries filled in the buffer. + * A return of 0 indicates that no entries referred to the inode. + * + * EINVAL is returned when there is a problem with the buffer. Either + * it was not aligned or it was not large enough for the first entry. + * + * Many other errnos indicate hard failure to find the next entry. + */ +struct scoutfs_ioctl_get_referring_entries { + __u64 ino; + __u64 dir_ino; + __u64 dir_pos; + __u64 entries_ptr; + __u64 entries_bytes; +}; + +/* + * @dir_ino: The inode of the directory containing the entry. + * + * @dir_pos: The readdir f_pos position of the entry within the + * directory. + * + * @ino: The inode number of the target of the entry. + * + * @flags: Flags associated with this entry. + * + * @d_type: Inode type as specified with DT_ enum values in readdir(3). + * + * @entry_bytes: The total bytes taken by the entry in memory, including + * the name and any alignment padding. The start of a following entry + * will be found after this number of bytes. + * + * @name_len: The number of bytes in the name not including the trailing + * null, ala strlen(3). + * + * @name: The null terminated name of the referring entry. In the + * struct definition this array is sized to naturally align the struct. + * That number of padded bytes are not necessarily found in the buffer + * returned by _get_referring_entries; + */ +struct scoutfs_ioctl_dirent { + __u64 dir_ino; + __u64 dir_pos; + __u64 ino; + __u16 entry_bytes; + __u8 flags; + __u8 d_type; + __u8 name_len; + __u8 name[3]; +}; + +#define SCOUTFS_IOCTL_DIRENT_FLAG_LAST (1 << 0) + +#define SCOUTFS_IOC_GET_REFERRING_ENTRIES \ + _IOW(SCOUTFS_IOCTL_MAGIC, 17, struct scoutfs_ioctl_get_referring_entries) + #endif From 2279e9657f73b526d4c9af34b20663c4dbc625f6 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Thu, 8 Jun 2023 12:20:01 -0700 Subject: [PATCH 3/4] Add get_referring_entries scoutfs command Add a cli command for the get_referring_entries ioctl. Signed-off-by: Zach Brown --- utils/man/scoutfs.8 | 23 +++++ utils/src/get_referring_entries.c | 150 ++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 utils/src/get_referring_entries.c diff --git a/utils/man/scoutfs.8 b/utils/man/scoutfs.8 index 8cfd2af1..60566c97 100644 --- a/utils/man/scoutfs.8 +++ b/utils/man/scoutfs.8 @@ -209,6 +209,29 @@ A path within a ScoutFS filesystem. .RE .PD +.TP +.BI "get-referring-entries [-p|--path PATH] INO" +.sp +Find directory entries that reference an inode number. +.sp +Display all the directory entries that refer to a given inode. Each +entry includes the inode number of the directory that contains it, the +d_off and d_type values for the entry as described by +.BR readdir (3) +, and the name of the entry. +.RS 1.0i +.PD 0 +.TP +.sp +.TP +.B "-p, --path PATH" +A path within a ScoutFS filesystem. +.TP +.B "INO" +The inode number of the target inode. +.RE +.PD + .TP .BI "ino-path INODE-NUM [-p|--path PATH]" .sp diff --git a/utils/src/get_referring_entries.c b/utils/src/get_referring_entries.c new file mode 100644 index 00000000..f0a1207c --- /dev/null +++ b/utils/src/get_referring_entries.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sparse.h" +#include "parse.h" +#include "util.h" +#include "format.h" +#include "ioctl.h" +#include "parse.h" +#include "cmd.h" + +struct gre_args { + char *path; + u64 ino; +}; + +static int do_get_referring_entries(struct gre_args *args) +{ + struct scoutfs_ioctl_get_referring_entries gre; + struct scoutfs_ioctl_dirent *dent; + unsigned int bytes; + void *buf; + int ret; + int fd; + + fd = get_path(args->path, O_RDONLY); + if (fd < 0) + return fd; + + bytes = PATH_MAX * 1024; + buf = malloc(bytes); + if (!buf) { + fprintf(stderr, "couldn't allocate %u byte buffer\n", bytes); + ret = -ENOMEM; + goto out; + } + + gre.ino = args->ino; + gre.dir_ino = 0; + gre.dir_pos = 0; + gre.entries_ptr = (intptr_t)buf; + gre.entries_bytes = bytes; + + for (;;) { + ret = ioctl(fd, SCOUTFS_IOC_GET_REFERRING_ENTRIES, &gre); + if (ret <= 0) { + if (ret < 0) { + ret = -errno; + fprintf(stderr, "ioctl failed: %s (%d)\n", strerror(errno), errno); + } + goto out; + } + + dent = buf; + while (ret-- > 0) { + printf("dir %llu pos %llu type %u name %s\n", + dent->dir_ino, dent->dir_pos, dent->d_type, dent->name); + + gre.dir_ino = dent->dir_ino; + gre.dir_pos = dent->dir_pos; + + if (dent->flags & SCOUTFS_IOCTL_DIRENT_FLAG_LAST) { + ret = 0; + goto out; + } + + dent = (void *)dent + dent->entry_bytes; + } + + if (++gre.dir_pos == 0) { + if (++gre.dir_ino == 0) { + ret = 0; + goto out; + } + } + } + +out: + close(fd); + free(buf); + + return ret; +}; + +static int parse_opt(int key, char *arg, struct argp_state *state) +{ + struct gre_args *args = state->input; + int ret; + + switch (key) { + case 'p': + args->path = strdup_or_error(state, arg); + break; + case ARGP_KEY_ARG: + if (args->ino) + argp_error(state, "more than one argument given"); + ret = parse_u64(arg, &args->ino); + if (ret) + argp_error(state, "inode parse error"); + break; + case ARGP_KEY_FINI: + if (!args->ino) { + argp_error(state, "must provide inode number"); + } + 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, + "INODE-NUM", + "Print directory entries that refer to inode number" +}; + +static int get_referring_entries_cmd(int argc, char **argv) +{ + struct gre_args args = {NULL}; + int ret; + + ret = argp_parse(&argp, argc, argv, 0, NULL, &args); + if (ret) + return ret; + + return do_get_referring_entries(&args); +} + + +static void __attribute__((constructor)) get_referring_entries_ctor(void) +{ + cmd_register_argp("get-referring-entries", &argp, GROUP_SEARCH, get_referring_entries_cmd); +} From 74c5fe1115c099ece561653d99cc9f4651a659b9 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Mon, 12 Jun 2023 14:16:26 -0700 Subject: [PATCH 4/4] Add get-referring-entries test Signed-off-by: Zach Brown --- tests/golden/get-referring-entries | 18 +++++ tests/sequence | 1 + tests/tests/get-referring-entries.sh | 99 ++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 tests/golden/get-referring-entries create mode 100644 tests/tests/get-referring-entries.sh diff --git a/tests/golden/get-referring-entries b/tests/golden/get-referring-entries new file mode 100644 index 00000000..6dcfad34 --- /dev/null +++ b/tests/golden/get-referring-entries @@ -0,0 +1,18 @@ +== root inode returns nothing +== crazy large unused inode does nothing +== basic entry +file +== rename +renamed +== hard link +file +link +== removal +== different dirs +== file types +type b name block +type c name char +type d name dir +type f name file +type l name symlink +== all name lengths work diff --git a/tests/sequence b/tests/sequence index 6638fae6..9e126fea 100644 --- a/tests/sequence +++ b/tests/sequence @@ -5,6 +5,7 @@ inode-items-updated.sh simple-inode-index.sh simple-staging.sh simple-release-extents.sh +get-referring-entries.sh fallocate.sh basic-truncate.sh data-prealloc.sh diff --git a/tests/tests/get-referring-entries.sh b/tests/tests/get-referring-entries.sh new file mode 100644 index 00000000..6bd17682 --- /dev/null +++ b/tests/tests/get-referring-entries.sh @@ -0,0 +1,99 @@ + +# +# Test _GET_REFERRING_ENTRIES ioctl via the get-referring-entries cli +# command +# + +# consistently print only entry names +filter_names() { + exec cut -d ' ' -f 8- | sort +} + +# print entries with type characters to match find. not happy with hard +# coding, but abi won't change much. +filter_types() { + exec cut -d ' ' -f 5- | \ + sed \ + -e 's/type 1 /type p /' \ + -e 's/type 2 /type c /' \ + -e 's/type 4 /type d /' \ + -e 's/type 6 /type b /' \ + -e 's/type 8 /type f /' \ + -e 's/type 10 /type l /' \ + -e 's/type 12 /type s /' \ + | \ + sort +} + +n_chars() { + local n="$1" + printf 'A%.0s' $(eval echo {1..\$n}) +} + +GRE="scoutfs get-referring-entries -p $T_M0" + +echo "== root inode returns nothing" +$GRE 1 + +echo "== crazy large unused inode does nothing" +$GRE 4611686018427387904 # 1 << 62 + +echo "== basic entry" +touch $T_D0/file +ino=$(stat -c '%i' $T_D0/file) +$GRE $ino | filter_names + +echo "== rename" +mv $T_D0/file $T_D0/renamed +$GRE $ino | filter_names + +echo "== hard link" +mv $T_D0/renamed $T_D0/file +ln $T_D0/file $T_D0/link +$GRE $ino | filter_names + +echo "== removal" +rm $T_D0/file $T_D0/link +$GRE $ino + +echo "== different dirs" +touch $T_D0/file +ino=$(stat -c '%i' $T_D0/file) +for i in $(seq 1 10); do + mkdir $T_D0/dir-$i + ln $T_D0/file $T_D0/dir-$i/file-$i +done +diff -u <(find $T_D0 -type f -printf '%f\n' | sort) <($GRE $ino | filter_names) +rm $T_D0/file + +echo "== file types" +mkdir $T_D0/dir +touch $T_D0/dir/file +mkdir $T_D0/dir/dir +ln -s $T_D0/dir/file $T_D0/dir/symlink +mknod $T_D0/dir/char c 1 3 # null +mknod $T_D0/dir/block b 7 0 # loop0 +for name in $(ls -UA $T_D0/dir | sort); do + ino=$(stat -c '%i' $T_D0/dir/$name) + $GRE $ino | filter_types +done +rm -rf $T_D0/dir + +echo "== all name lengths work" +mkdir $T_D0/dir +touch $T_D0/dir/file +ino=$(stat -c '%i' $T_D0/dir/file) +name="" +> $T_TMP.unsorted +for i in $(seq 1 255); do + name+="a" + echo "$name" >> $T_TMP.unsorted + ln $T_D0/dir/file $T_D0/dir/$name +done +sort $T_TMP.unsorted > $T_TMP.sorted +rm $T_D0/dir/file +$GRE $ino | filter_names > $T_TMP.gre +diff -u $T_TMP.sorted $T_TMP.gre +rm -rf $T_D0/dir + +t_pass