diff --git a/kmod/src/dir.c b/kmod/src/dir.c index 0cf55a13..16dce5df 100644 --- a/kmod/src/dir.c +++ b/kmod/src/dir.c @@ -188,6 +188,40 @@ static struct scoutfs_key_buf *alloc_dirent_key(struct super_block *sb, return key; } +static void init_link_backref_key(struct scoutfs_key_buf *key, + struct scoutfs_link_backref_key *lbrkey, + u64 ino, u64 dir_ino, + char *name, unsigned name_len) +{ + lbrkey->type = SCOUTFS_LINK_BACKREF_KEY; + lbrkey->ino = cpu_to_be64(ino); + lbrkey->dir_ino = cpu_to_be64(dir_ino); + if (name_len) + memcpy(lbrkey->name, name, name_len); + + scoutfs_key_init(key, lbrkey, offsetof(struct scoutfs_link_backref_key, + name[name_len])); +} + +static struct scoutfs_key_buf *alloc_link_backref_key(struct super_block *sb, + u64 ino, u64 dir_ino, + char *name, + unsigned name_len) +{ + struct scoutfs_link_backref_key *lbkey; + struct scoutfs_key_buf *key; + + key = scoutfs_key_alloc(sb, offsetof(struct scoutfs_link_backref_key, + name[name_len])); + if (key) { + lbkey = key->data; + init_link_backref_key(key, lbkey, ino, dir_ino, + name, name_len); + } + + return key; +} + static struct dentry *scoutfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { @@ -331,34 +365,6 @@ static int scoutfs_readdir(struct file *file, void *dirent, filldir_t filldir) return ret; } -#if 0 -static void set_lref_key(struct scoutfs_key *key, u64 ino, u64 ctr) -{ - scoutfs_set_key(key, ino, SCOUTFS_LINK_BACKREF_KEY, ctr); -} - -static int update_lref_item(struct super_block *sb, struct scoutfs_key *key, - u64 dir_ino, u64 dir_off, bool update) -{ - struct scoutfs_btree_root *meta = SCOUTFS_META(sb); - struct scoutfs_link_backref lref; - struct scoutfs_btree_val val; - int ret; - - lref.ino = cpu_to_le64(dir_ino); - lref.offset = cpu_to_le64(dir_off); - - scoutfs_btree_init_val(&val, &lref, sizeof(lref)); - - if (update) - ret = scoutfs_btree_update(sb, meta, key, &val); - else - ret = scoutfs_btree_insert(sb, meta, key, &val); - - return ret; -} -#endif - static int add_entry_items(struct inode *dir, struct dentry *dentry, struct inode *inode) { @@ -366,6 +372,7 @@ static int add_entry_items(struct inode *dir, struct dentry *dentry, struct dentry_info *di = dentry->d_fsdata; struct super_block *sb = dir->i_sb; struct scoutfs_key_buf *ent_key = NULL; + struct scoutfs_key_buf *lb_key = NULL; struct scoutfs_key_buf *del_keys[3]; struct scoutfs_key_buf rdir_key; struct scoutfs_readdir_key rkey; @@ -415,14 +422,20 @@ static int add_entry_items(struct inode *dir, struct dentry *dentry, goto out; del_keys[del++] = &rdir_key; -#if 0 - /* backref item for inode to path resolution */ - lrkey.type = SCOUTFS_LINK_BACKREF_KEY; - lrey.ino = cpu_to_le64(scoutfs_ino(inode)); - lrey.dir = cpu_to_le64(scoutfs_ino(dir)); - scoutfs_kvec_init(key, &lrkey, sizeof(lrkey), - dentry->d_name.name, dentry->d_name.len); -#endif + /* link backref item for inode to path resolution */ + lb_key = alloc_link_backref_key(sb, scoutfs_ino(inode), + scoutfs_ino(dir), + (void *)dentry->d_name.name, + dentry->d_name.len); + if (!lb_key) { + ret = -ENOMEM; + goto out; + } + + ret = scoutfs_item_create(sb, lb_key, NULL); + if (ret) + goto out; + del_keys[del++] = lb_key; update_dentry_info(dentry, &dent); ret = 0; @@ -434,6 +447,7 @@ out: } scoutfs_key_free(sb, ent_key); + scoutfs_key_free(sb, lb_key); return ret; } @@ -543,7 +557,7 @@ static int scoutfs_unlink(struct inode *dir, struct dentry *dentry) struct super_block *sb = dir->i_sb; struct inode *inode = dentry->d_inode; struct timespec ts = current_kernel_time(); - struct scoutfs_key_buf *keys[2] = {NULL,}; + struct scoutfs_key_buf *keys[3] = {NULL,}; struct scoutfs_key_buf rdir_key; struct scoutfs_readdir_key rkey; int ret = 0; @@ -569,6 +583,15 @@ static int scoutfs_unlink(struct inode *dir, struct dentry *dentry) init_readdir_key(&rdir_key, &rkey, dir, dentry_info_pos(dentry)); keys[1] = &rdir_key; + keys[2] = alloc_link_backref_key(sb, scoutfs_ino(inode), + scoutfs_ino(dir), + (void *)dentry->d_name.name, + dentry->d_name.len); + if (!keys[2]) { + ret = -ENOMEM; + goto out; + } + ret = scoutfs_item_delete_many(sb, keys, ARRAY_SIZE(keys)); if (ret) goto out; @@ -600,6 +623,7 @@ static int scoutfs_unlink(struct inode *dir, struct dentry *dentry) out: scoutfs_key_free(sb, keys[0]); + scoutfs_key_free(sb, keys[2]); scoutfs_release_trans(sb); return ret; } @@ -804,194 +828,154 @@ int scoutfs_symlink_drop(struct super_block *sb, u64 ino) } /* - * Store the null terminated path component that links to the inode at - * the given counter in the callers buffer. + * Find the next link backref key for the given ino starting from the + * given dir inode and null terminated name. If we find a backref item + * we add an allocated copy of it to the head of the caller's list. * - * This is implemented by searching for link backrefs on the inode - * starting from the given counter. Those contain references to the - * parent directory and dirent key offset that contain the link to the - * inode. - * - * The caller holds no locks that protect components in the path. We - * search the link backref to find the parent dir then acquire it's - * i_mutex to make sure that its entries and backrefs are stable. If - * the next backref points to a different dir after we acquire the lock - * we bounce off and retry. - * - * Backref counters are never reused and rename only modifies the - * existing backref counter under the dir's mutex. + * Returns 0 if we added an entry, -ENOENT if we didn't, and -errno for + * search errors. */ -static int append_linkref_name(struct super_block *sb, u64 *dir_ino, u64 ino, - u64 *ctr, char *path, unsigned int bytes) +static int add_next_linkref(struct super_block *sb, u64 ino, + u64 dir_ino, char *name, unsigned int name_len, + struct list_head *list) { - struct scoutfs_btree_root *meta = SCOUTFS_META(sb); - struct scoutfs_link_backref lref; - struct scoutfs_btree_val val; - struct scoutfs_dirent dent; - struct inode *inode = NULL; - struct scoutfs_key first; - struct scoutfs_key last; - struct scoutfs_key key; - u64 retried = 0; - u64 off; + struct scoutfs_link_backref_key last_lbkey; + struct scoutfs_link_backref_entry *ent; + struct scoutfs_key_buf last; + struct scoutfs_key_buf key; int len; int ret; -retry: - scoutfs_set_key(&first, ino, SCOUTFS_LINK_BACKREF_KEY, *ctr); - scoutfs_set_key(&last, ino, SCOUTFS_LINK_BACKREF_KEY, ~0ULL); + ent = kmalloc(offsetof(struct scoutfs_link_backref_entry, + lbkey.name[SCOUTFS_NAME_LEN + 1]), GFP_KERNEL); + if (!ent) + return -ENOMEM; - scoutfs_btree_init_val(&val, &lref, sizeof(lref)); - val.check_size_eq = 1; + INIT_LIST_HEAD(&ent->head); - ret = scoutfs_btree_next(sb, meta, &first, &last, &key, &val); - if (ret < 0) { - if (ret == -ENOENT) - ret = 0; + /* put search key in ent */ + init_link_backref_key(&key, &ent->lbkey, ino, dir_ino, name, name_len); + /* we actually have room for a full backref item */ + scoutfs_key_init_buf_len(&key, key.data, key.key_len, + offsetof(struct scoutfs_link_backref_key, + name[SCOUTFS_NAME_LEN + 1])); + + /* small last key to avoid full name copy, XXX enforce no U64_MAX ino */ + init_link_backref_key(&last, &last_lbkey, ino, U64_MAX, NULL, 0); + + /* next backref key is now in ent */ + ret = scoutfs_item_next(sb, &key, &last, NULL); + trace_printk("ino %llu dir_ino %llu ret %d key_len %u\n", + ino, dir_ino, ret, key.key_len); + if (ret < 0) goto out; - } - *dir_ino = le64_to_cpu(lref.ino), - off = le64_to_cpu(lref.offset); - *ctr = scoutfs_key_offset(&key); - trace_printk("ino %llu ctr %llu dir_ino %llu off %llu\n", - ino, *ctr, *dir_ino, off); - - /* XXX corruption, should never be key == U64_MAX */ - if (*ctr == U64_MAX) { + len = (int)key.key_len - sizeof(struct scoutfs_link_backref_key); + /* XXX corruption */ + if (len < 1 || len > SCOUTFS_NAME_LEN) { ret = -EIO; goto out; } - /* XXX should verify ino and offset, too */ + ent->name_len = len; + list_add(&ent->head, list); + ret = 0; +out: + if (list_empty(&ent->head)) + kfree(ent); + return ret; +} - if (inode && scoutfs_ino(inode) != *dir_ino) { - mutex_unlock(&inode->i_mutex); - iput(inode); - inode = NULL; +static u64 first_backref_dir_ino(struct list_head *list) +{ + struct scoutfs_link_backref_entry *ent; + + ent = list_first_entry(list, struct scoutfs_link_backref_entry, head); + return be64_to_cpu(ent->lbkey.dir_ino); +} + +void scoutfs_dir_free_backref_path(struct super_block *sb, + struct list_head *list) +{ + struct scoutfs_link_backref_entry *ent; + struct scoutfs_link_backref_entry *pos; + + list_for_each_entry_safe(ent, pos, list, head) { + list_del_init(&ent->head); + kfree(ent); } +} - if (!inode) { - inode = scoutfs_iget(sb, *dir_ino); - if (IS_ERR(inode)) { - ret = PTR_ERR(inode); - inode = NULL; - if (ret == -ENOENT && retried != *dir_ino) { - retried = *dir_ino; +/* + * Give the caller the next path from the root to the inode by walking + * backref items from the dir and name position, putting the backref keys + * we find in the caller's list. + * + * Return 0 if we found a path, -ENOENT if we didn't, and -errno on error. + * + * If parents get unlinked while we're searching we can fail to make it + * up to the root. We restart the search in that case. Parent dirs + * couldn't have been unlinked while they still had entries and we won't + * see links to the inode that have been unlinked. + * + * XXX Each path component traversal is consistent but that doesn't mean + * that the total traversed path is consistent. If renames hit dirs + * that have been visited and then dirs to be visited we can return a + * path that was never present in the system: + * + * path to inode mv performed built up path + * ---- + * a/b/c/d/e/f + * d/e/f + * mv a/b/c/d/e a/b/c/ + * a/b/c/e/f + * mv a/b/c a/ + * a/c/e/f + * a/c/d/e/f + * + * XXX We'll protect against this by sampling the seq before the + * traversal and restarting if we saw backref items whose seq was + * greater than the start point. It's not precise in that it doesn't + * also capture the rename of a dir that we already traversed but it + * lets us complete the traversal in one pass that very rarely restarts. + * + * XXX and worry about traversing entirely dirty backref items with + * equal seqs that have seen crazy modification? seems like we have to + * sync if we see our dirty seq. + */ +int scoutfs_dir_get_backref_path(struct super_block *sb, u64 ino, u64 dir_ino, + char *name, u16 name_len, + struct list_head *list) +{ + u64 par_ino; + int ret; + +retry: + /* get the next link name to the given inode */ + ret = add_next_linkref(sb, ino, dir_ino, name, name_len, list); + if (ret < 0) + goto out; + + /* then get the names of all the parent dirs */ + par_ino = first_backref_dir_ino(list); + while (par_ino != SCOUTFS_ROOT_INO) { + + ret = add_next_linkref(sb, par_ino, 0, NULL, 0, list); + if (ret < 0) { + if (ret == -ENOENT) { + /* restart if there was no parent component */ + scoutfs_dir_free_backref_path(sb, list); goto retry; } goto out; } - mutex_lock(&inode->i_mutex); - goto retry; + par_ino = first_backref_dir_ino(list); } - - scoutfs_set_key(&key, *dir_ino, SCOUTFS_DIRENT_KEY, off); - scoutfs_btree_init_val(&val, &dent, sizeof(dent), path, bytes - 1); - val.check_size_lte = 1; - - ret = scoutfs_btree_lookup(sb, meta, &key, &val); - if (ret < 0) { - /* XXX corruption, should always have dirent for backref */ - if (ret == -ENOENT) - ret = -EIO; - else if (ret == -EOVERFLOW) - ret = -ENAMETOOLONG; - goto out; - } - - /* XXX corruption */ - if (ret <= sizeof(dent)) { - ret = -EIO; - goto out; - } - - len = ret - sizeof(dent); /* just name len, no null term */ - - /* XXX corruption */ - if (len > SCOUTFS_NAME_LEN || le64_to_cpu(dent.ino) != ino) { - ret = -EIO; - goto out; - } - - trace_printk("dent ino %llu len %d\n", le64_to_cpu(dent.ino), len); - - (*ctr)++; - path[len] = '\0'; - ret = len + 1; out: - if (inode) { - mutex_unlock(&inode->i_mutex); - iput(inode); - } - - return ret; -} - -/* - * Fill the caller's buffer with the null terminated path components - * from the target inode to the root. These will be in the opposite - * order of a typical slash delimited path. The caller's ctr gives the - * specific link to start from. - * - * This is racing with modification of components in the path. We can - * traverse a partial path only to find that it's been blown away - * entirely. If we see a component go missing we retry. The removal of - * the final link to the inode should prevent repeatedly traversing - * paths that no longer exist. - * - * Returns > 0 and *ctr is updated if a full path from the link to the - * root dir was filled, 0 if no name past *ctr was found, or -errno on - * errors. - */ -int scoutfs_dir_get_ino_path(struct super_block *sb, u64 ino, u64 *ctr, - char *path, unsigned int bytes) -{ - u64 final_ctr; - u64 par_ctr; - u64 par_ino; - int ret; - int nr; - - /* update for kvec items */ - return -EINVAL; - - if (*ctr == U64_MAX) - return 0; - -retry: - final_ctr = *ctr; - ret = 0; - - /* get the next link name to the given inode */ - nr = append_linkref_name(sb, &par_ino, ino, &final_ctr, path, bytes); - if (nr <= 0) { - ret = nr; - goto out; - } - ret += nr; - - /* then get the names of all the parent dirs */ - while (par_ino != SCOUTFS_ROOT_INO) { - par_ctr = 0; - nr = append_linkref_name(sb, &par_ino, par_ino, &par_ctr, - path + ret, bytes - ret); - if (nr < 0) { - ret = nr; - goto out; - } - - /* restart if there was no parent component */ - if (nr == 0) - goto retry; - - ret += nr; - } - -out: - *ctr = final_ctr; + if (ret < 0) + scoutfs_dir_free_backref_path(sb, list); return ret; } diff --git a/kmod/src/dir.h b/kmod/src/dir.h index 1221846e..273d1f54 100644 --- a/kmod/src/dir.h +++ b/kmod/src/dir.h @@ -7,14 +7,17 @@ extern const struct file_operations scoutfs_dir_fops; extern const struct inode_operations scoutfs_dir_iops; extern const struct inode_operations scoutfs_symlink_iops; -struct scoutfs_path_component { +struct scoutfs_link_backref_entry { struct list_head head; - unsigned int len; - char name[SCOUTFS_NAME_LEN]; + u16 name_len; + struct scoutfs_link_backref_key lbkey; }; -int scoutfs_dir_get_ino_path(struct super_block *sb, u64 ino, u64 *ctr, - char *path, unsigned int bytes); +int scoutfs_dir_get_backref_path(struct super_block *sb, u64 target_ino, + u64 dir_ino, char *name, u16 name_len, + struct list_head *list); +void scoutfs_dir_free_backref_path(struct super_block *sb, + struct list_head *list); int scoutfs_symlink_drop(struct super_block *sb, u64 ino); diff --git a/kmod/src/format.h b/kmod/src/format.h index 4a6b15bf..0d5edcfc 100644 --- a/kmod/src/format.h +++ b/kmod/src/format.h @@ -265,6 +265,14 @@ struct scoutfs_readdir_key { __be64 pos; } __packed; +/* value is empty */ +struct scoutfs_link_backref_key { + __u8 type; + __be64 ino; + __be64 dir_ino; + __u8 name[0]; +} __packed; + /* no value */ struct scoutfs_orphan_key { __u8 type; @@ -479,17 +487,8 @@ struct scoutfs_extent { #define SCOUTFS_EXTENT_FLAG_OFFLINE (1 << 0) -/* - * link backrefs give us a way to find all the hard links that refer - * to a target inode. They're stored at an offset determined by an - * advancing counter in their inode. - */ -struct scoutfs_link_backref { - __le64 ino; - __le64 offset; -} __packed; - +/* ino_path can search for backref items with a null term */ #define SCOUTFS_MAX_KEY_SIZE \ - offsetof(struct scoutfs_dirent_key, name[SCOUTFS_NAME_LEN]) + offsetof(struct scoutfs_link_backref_key, name[SCOUTFS_NAME_LEN + 1]) #endif diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index 4c1b039a..ff409346 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -106,90 +106,116 @@ static long scoutfs_ioc_inodes_since(struct file *file, unsigned long arg, return ret; } +struct ino_path_cursor { + __u64 dir_ino; + __u8 name[SCOUTFS_NAME_LEN + 1]; +} __packed; + /* - * Fill the caller's buffer with one of the paths from the on-disk root - * directory to the target inode. + * see the definition of scoutfs_ioctl_ino_path for ioctl semantics. * - * Userspace provides a u64 counter used to chose which path to return. - * It should be initialized to zero to start iterating. After each path - * it is set to the next counter to search from. - * - * This only walks back through full hard links. None of the returned - * paths will reflect symlinks to components in the path. - * - * This doesn't ensure that the caller has permissions to traverse the - * returned paths to the inode. It requires CAP_DAC_READ_SEARCH which - * bypasses permissions checking. - * - * ENAMETOOLONG is returned when the next path from the given counter - * doesn't fit in the buffer. Providing a buffer of PATH_MAX should - * succeed. - * - * This call is not serialized with any modification (create, rename, - * unlink) of the path components. It will return all the paths that - * were stable both before and after the call. It may or may not return - * paths which are created or unlinked during the call. - * - * The number of bytes in the path, including the null terminator, are - * returned when a path is found. 0 is returned when there are no more - * paths to the link from the given counter. -errno is returned on - * errors. - * - * XXX - * - can dir renaming trick us into returning garbage paths? seems likely. + * The null termination of the cursor name is a trick to skip past the + * last name we read without having to try and "increment" the name. + * Adding a null sorts the cursor after the non-null name and before all + * the next names because the item names aren't null terminated. */ static long scoutfs_ioc_ino_path(struct file *file, unsigned long arg) { struct super_block *sb = file_inode(file)->i_sb; - struct scoutfs_ioctl_ino_path __user *uargs = (void __user *)arg; + struct scoutfs_ioctl_ino_path __user *uargs; + struct scoutfs_link_backref_entry *ent; + struct ino_path_cursor __user *ucurs; struct scoutfs_ioctl_ino_path args; - unsigned int bytes; char __user *upath; - char *comp; - char *path; + LIST_HEAD(list); + u64 dir_ino; + u16 name_len; + char term; + char *name; int ret; - int len; + + BUILD_BUG_ON(SCOUTFS_IOC_INO_PATH_CURSOR_BYTES != + sizeof(struct ino_path_cursor)); if (!capable(CAP_DAC_READ_SEARCH)) return -EPERM; + uargs = (void __user *)arg; if (copy_from_user(&args, uargs, sizeof(args))) return -EFAULT; - if (args.path_bytes <= 1) + if (args.cursor_bytes != sizeof(struct ino_path_cursor)) return -EINVAL; - bytes = min_t(unsigned int, args.path_bytes, PATH_MAX); - path = kmalloc(bytes, GFP_KERNEL); - if (path == NULL) + ucurs = (void __user *)(unsigned long)args.cursor_ptr; + upath = (void __user *)(unsigned long)args.path_ptr; + + if (get_user(dir_ino, &ucurs->dir_ino)) + return -EFAULT; + + /* alloc/copy the small cursor name, requires and includes null */ + name_len = strnlen_user(ucurs->name, sizeof(ucurs->name)); + if (name_len < 1 || name_len > sizeof(ucurs->name)) + return -EINVAL; + + name = kmalloc(name_len, GFP_KERNEL); + if (!name) return -ENOMEM; - /* positive ret is len of all components including null terminators */ - ret = scoutfs_dir_get_ino_path(sb, args.ino, &args.ctr, path, bytes); - if (ret <= 0) + if (copy_from_user(name, ucurs->name, name_len)) { + ret = -EFAULT; goto out; + } - /* reverse the components from backref order to path/ order */ - comp = path; - upath = (void __user *)((unsigned long)args.path_ptr + ret); - while (comp < (path + ret)) { - len = strlen(comp); - if (comp != path) - comp[len] = '/'; - len++; + ret = scoutfs_dir_get_backref_path(sb, args.ino, dir_ino, name, + name_len, &list); + if (ret < 0) { + if (ret == -ENOENT) + ret = 0; + goto out; + } - upath -= len; - if (copy_to_user(upath, comp, len)) { + ret = 0; + list_for_each_entry(ent, &list, head) { + if (ret + ent->name_len + 1 > args.path_bytes) { + ret = -ENAMETOOLONG; + goto out; + } + + if (copy_to_user(upath, ent->lbkey.name, ent->name_len)) { + ret = -EFAULT; + goto out; + } + + upath += ent->name_len; + ret += ent->name_len; + + if (ent->head.next == &list) + term = '\0'; + else + term = '/'; + + if (put_user(term, upath)) { ret = -EFAULT; break; } - comp += len; + + upath++; + ret++; } - if (ret > 0 && put_user(args.ctr, &uargs->ctr)) + /* copy the last entry into the cursor */ + ent = list_last_entry(&list, struct scoutfs_link_backref_entry, head); + + if (put_user(be64_to_cpu(ent->lbkey.dir_ino), &ucurs->dir_ino) || + copy_to_user(ucurs->name, ent->lbkey.name, ent->name_len) || + put_user('\0', &ucurs->name[ent->name_len])) { ret = -EFAULT; + } + out: - kfree(path); + scoutfs_dir_free_backref_path(sb, &list); + kfree(name); return ret; } diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index d39c6272..e95a7ba2 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -26,14 +26,56 @@ struct scoutfs_ioctl_inodes_since { #define SCOUTFS_IOC_INODES_SINCE _IOW(SCOUTFS_IOCTL_MAGIC, 1, \ struct scoutfs_ioctl_inodes_since) -/* returns bytes of path buffer set starting at _off, including null */ +/* + * Fill the path buffer with the next path to the target inode. An + * iteration cursor is stored in the cursor buffer which advances + * through the paths to the inode at each call. + * + * @ino: The target ino that we're finding paths to. Constant across + * all the calls that make up an iteration over all the inode's paths. + * + * @cursor_ptr: A pointer to the buffer that will hold the iteration + * cursor. It must be initialized to 0 before iterating. Each call + * modifies it to skip past the result of that call. + * + * @cusur_bytes: The length of the cursor buffer. Must be + * SCOUTFS_IOC_INO_PATH_CURSOR_BYTES. + * + * @path_ptr: The buffer to store each found path. + * + * @path_bytes: The size of the buffer that will the found path + * including null termination. (PATH_MAX is a solid choice.) + * + * This only walks back through full hard links. None of the returned + * paths will reflect symlinks to components in the path. + * + * This doesn't ensure that the caller has permissions to traverse the + * returned paths to the inode. It requires CAP_DAC_READ_SEARCH which + * bypasses permissions checking. + * + * ENAMETOOLONG is returned when the next path found from the cursor + * doesn't fit in the path buffer. + * + * This call is not serialized with any modification (create, rename, + * unlink) of the path components. It will return all the paths that + * were stable both before and after the call. It may or may not return + * paths which are created or unlinked during the call. + * + * The number of bytes in the path, including the null terminator, are + * returned when a path is found. 0 is returned when there are no more + * paths to the link to the inode from the cursor. + */ struct scoutfs_ioctl_ino_path { __u64 ino; - __u64 ctr; /* init to 0, set to next */ + __u64 cursor_ptr; __u64 path_ptr; - __u16 path_bytes; /* total buffer space, including null term */ + __u16 cursor_bytes; + __u16 path_bytes; } __packed; +#define SCOUTFS_IOC_INO_PATH_CURSOR_BYTES \ + (sizeof(u64) + SCOUTFS_NAME_LEN + 1) + /* Get a single path from the root to the given inode number */ #define SCOUTFS_IOC_INO_PATH _IOW(SCOUTFS_IOCTL_MAGIC, 2, \ struct scoutfs_ioctl_ino_path)