mirror of
https://github.com/versity/scoutfs.git
synced 2026-07-29 19:43:13 +00:00
scoutfs: add symlink support
Symlinks are easily implemented by storing the target path in btree items. Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
+165
@@ -560,6 +560,170 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Full a buffer with the null terminated symlink, point nd at it, and
|
||||
* return it so put_link can free it once the vfs is done.
|
||||
*
|
||||
* We chose to pay the runtime cost of per-call allocation and copy
|
||||
* overhead instead of wiring up symlinks to the page cache, storing
|
||||
* each small link in a full page, and later having to reclaim them.
|
||||
*/
|
||||
static void *scoutfs_follow_link(struct dentry *dentry, struct nameidata *nd)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
|
||||
loff_t size = i_size_read(inode);
|
||||
struct scoutfs_key first;
|
||||
struct scoutfs_key last;
|
||||
char *path;
|
||||
int off;
|
||||
int ret;
|
||||
int k;
|
||||
|
||||
/* XXX corruption */
|
||||
if (size == 0 || size > SCOUTFS_SYMLINK_MAX_SIZE)
|
||||
return ERR_PTR(-EIO);
|
||||
|
||||
/* unlikely, but possible I suppose */
|
||||
if (size > PATH_MAX)
|
||||
return ERR_PTR(-ENAMETOOLONG);
|
||||
|
||||
path = kmalloc(size, GFP_NOFS);
|
||||
if (!path)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
scoutfs_set_key(&first, scoutfs_ino(inode), SCOUTFS_SYMLINK_KEY, 0);
|
||||
scoutfs_set_key(&last, scoutfs_ino(inode), SCOUTFS_SYMLINK_KEY, ~0ULL);
|
||||
|
||||
off = 0;
|
||||
k = 0;
|
||||
while ((ret = scoutfs_btree_next(sb, &first, &last, &curs)) > 0) {
|
||||
if (scoutfs_key_offset(curs.key) != k ||
|
||||
off + curs.val_len > size) {
|
||||
/* XXX corruption */
|
||||
scoutfs_btree_release(&curs);
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(path + off, curs.val, curs.val_len);
|
||||
|
||||
off += curs.val_len;
|
||||
k++;
|
||||
}
|
||||
|
||||
/* XXX corruption */
|
||||
if (ret == 0 && (off != size || path[off - 1] != '\0'))
|
||||
ret = -EIO;
|
||||
|
||||
if (ret) {
|
||||
kfree(path);
|
||||
path = ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static void scoutfs_put_link(struct dentry *dentry, struct nameidata *nd,
|
||||
void *cookie)
|
||||
{
|
||||
if (!IS_ERR_OR_NULL(cookie))
|
||||
kfree(cookie);
|
||||
}
|
||||
|
||||
const struct inode_operations scoutfs_symlink_iops = {
|
||||
.readlink = generic_readlink,
|
||||
.follow_link = scoutfs_follow_link,
|
||||
.put_link = scoutfs_put_link,
|
||||
.setxattr = scoutfs_setxattr,
|
||||
.getxattr = scoutfs_getxattr,
|
||||
.listxattr = scoutfs_listxattr,
|
||||
.removexattr = scoutfs_removexattr,
|
||||
};
|
||||
|
||||
/*
|
||||
* Symlink target paths can be annoyingly huge. We don't want large
|
||||
* items gumming up the btree so we store relatively rare large paths in
|
||||
* multiple items.
|
||||
*/
|
||||
static int scoutfs_symlink(struct inode *dir, struct dentry *dentry,
|
||||
const char *symname)
|
||||
{
|
||||
struct super_block *sb = dir->i_sb;
|
||||
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
|
||||
struct inode *inode = NULL;
|
||||
struct scoutfs_key key;
|
||||
struct dentry_info *di;
|
||||
const int name_len = strlen(symname) + 1;
|
||||
int off;
|
||||
int bytes;
|
||||
int ret;
|
||||
int k = 0;
|
||||
|
||||
/* path_max includes null as does our value for nd_set_link */
|
||||
if (name_len > PATH_MAX || name_len > SCOUTFS_SYMLINK_MAX_SIZE)
|
||||
return -ENAMETOOLONG;
|
||||
|
||||
di = alloc_dentry_info(dentry);
|
||||
if (IS_ERR(di))
|
||||
return PTR_ERR(di);
|
||||
|
||||
ret = scoutfs_hold_trans(sb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
inode = scoutfs_new_inode(sb, dir, S_IFLNK|S_IRWXUGO, 0);
|
||||
if (IS_ERR(inode)) {
|
||||
ret = PTR_ERR(inode);
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (k = 0, off = 0; off < name_len; off += bytes, k++) {
|
||||
scoutfs_set_key(&key, scoutfs_ino(inode), SCOUTFS_SYMLINK_KEY,
|
||||
k);
|
||||
bytes = min(name_len, SCOUTFS_MAX_ITEM_LEN);
|
||||
|
||||
ret = scoutfs_btree_insert(sb, &key, bytes, &curs);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
memcpy(curs.val, symname + off, bytes);
|
||||
scoutfs_btree_release(&curs);
|
||||
}
|
||||
|
||||
ret = add_entry_items(dir, dentry, inode);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
i_size_write(dir, i_size_read(dir) + dentry->d_name.len);
|
||||
dir->i_mtime = dir->i_ctime = CURRENT_TIME;
|
||||
|
||||
inode->i_ctime = dir->i_mtime;
|
||||
i_size_write(inode, name_len);
|
||||
|
||||
scoutfs_update_inode_item(inode);
|
||||
scoutfs_update_inode_item(dir);
|
||||
|
||||
insert_inode_hash(inode);
|
||||
/* XXX need to set i_op/fop before here for sec callbacks */
|
||||
d_instantiate(dentry, inode);
|
||||
out:
|
||||
if (ret < 0) {
|
||||
if (!IS_ERR_OR_NULL(inode))
|
||||
iput(inode);
|
||||
|
||||
while (k--) {
|
||||
scoutfs_set_key(&key, scoutfs_ino(inode),
|
||||
SCOUTFS_SYMLINK_KEY, k);
|
||||
scoutfs_btree_delete(sb, &key);
|
||||
}
|
||||
}
|
||||
|
||||
scoutfs_release_trans(sb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an allocated path component to the callers list which links to
|
||||
* the target inode at a counter past the given counter.
|
||||
@@ -770,6 +934,7 @@ const struct inode_operations scoutfs_dir_iops = {
|
||||
.getxattr = scoutfs_getxattr,
|
||||
.listxattr = scoutfs_listxattr,
|
||||
.removexattr = scoutfs_removexattr,
|
||||
.symlink = scoutfs_symlink,
|
||||
};
|
||||
|
||||
void scoutfs_dir_exit(void)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
extern const struct file_operations scoutfs_dir_fops;
|
||||
extern const struct inode_operations scoutfs_dir_iops;
|
||||
extern const struct inode_operations scoutfs_symlink_iops;
|
||||
|
||||
int scoutfs_dir_init(void);
|
||||
void scoutfs_dir_exit(void);
|
||||
|
||||
+5
-1
@@ -104,7 +104,8 @@ struct scoutfs_key {
|
||||
#define SCOUTFS_XATTR_VAL_HASH_KEY 4
|
||||
#define SCOUTFS_DIRENT_KEY 5
|
||||
#define SCOUTFS_LINK_BACKREF_KEY 6
|
||||
#define SCOUTFS_BMAP_KEY 7
|
||||
#define SCOUTFS_SYMLINK_KEY 7
|
||||
#define SCOUTFS_BMAP_KEY 8
|
||||
|
||||
#define SCOUTFS_MAX_ITEM_LEN 512
|
||||
|
||||
@@ -191,6 +192,9 @@ struct scoutfs_inode {
|
||||
|
||||
#define SCOUTFS_ROOT_INO 1
|
||||
|
||||
/* like the block size, a reasonable min PATH_MAX across platforms */
|
||||
#define SCOUTFS_SYMLINK_MAX_SIZE 4096
|
||||
|
||||
/*
|
||||
* Dirents are stored in items with an offset of the hash of their name.
|
||||
* Colliding names are packed into the value.
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@ static void set_inode_ops(struct inode *inode)
|
||||
inode->i_fop = &scoutfs_dir_fops;
|
||||
break;
|
||||
case S_IFLNK:
|
||||
// inode->i_op = &scoutfs_symlink_iops;
|
||||
inode->i_op = &scoutfs_symlink_iops;
|
||||
break;
|
||||
default:
|
||||
// inode->i_op = &scoutfs_special_iops;
|
||||
|
||||
Reference in New Issue
Block a user