mirror of
https://github.com/versity/scoutfs.git
synced 2026-01-06 20:16:25 +00:00
Merge pull request #125 from versity/zab/get_referring_entries
Zab/get referring entries
This commit is contained in:
114
kmod/src/dir.c
114
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
106
kmod/src/ioctl.c
106
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;
|
||||
|
||||
114
kmod/src/ioctl.h
114
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
|
||||
|
||||
@@ -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,
|
||||
|
||||
18
tests/golden/get-referring-entries
Normal file
18
tests/golden/get-referring-entries
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
99
tests/tests/get-referring-entries.sh
Normal file
99
tests/tests/get-referring-entries.sh
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
150
utils/src/get_referring_entries.c
Normal file
150
utils/src/get_referring_entries.c
Normal file
@@ -0,0 +1,150 @@
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <argp.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
Reference in New Issue
Block a user