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/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 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, 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 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); +}