mirror of
https://github.com/versity/scoutfs.git
synced 2026-01-08 21:03:12 +00:00
scoutfs: remove unlinked inode items
Wire up the inode callbacks that let us remove all the persistent items associated with an unlinked inode as its final reference is dropped. This is the first part of full truncate and orphan inode support. Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
@@ -724,6 +724,35 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete all the symlink items. There should only ever be a handful of
|
||||
* these that contain the target path of the symlink.
|
||||
*/
|
||||
int scoutfs_symlink_drop(struct super_block *sb, u64 ino)
|
||||
{
|
||||
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
|
||||
struct scoutfs_key first;
|
||||
struct scoutfs_key last;
|
||||
struct scoutfs_key key;
|
||||
int ret;
|
||||
|
||||
scoutfs_set_key(&first, ino, SCOUTFS_SYMLINK_KEY, 0);
|
||||
scoutfs_set_key(&last, ino, SCOUTFS_SYMLINK_KEY, ~0ULL);
|
||||
|
||||
while ((ret = scoutfs_btree_next(sb, &first, &last, &curs)) > 0) {
|
||||
key = *curs.key;
|
||||
first = *curs.key;
|
||||
scoutfs_inc_key(&first);
|
||||
scoutfs_btree_release(&curs);
|
||||
|
||||
ret = scoutfs_btree_delete(sb, &key);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an allocated path component to the callers list which links to
|
||||
* the target inode at a counter past the given counter.
|
||||
|
||||
@@ -20,4 +20,6 @@ int scoutfs_dir_next_path(struct super_block *sb, u64 ino, u64 *ctr,
|
||||
struct list_head *list);
|
||||
void scoutfs_dir_free_path(struct list_head *list);
|
||||
|
||||
int scoutfs_symlink_drop(struct super_block *sb, u64 ino);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -177,6 +177,95 @@ static void return_file_block(struct super_block *sb, u64 blkno)
|
||||
spin_unlock(&sbi->file_alloc_lock);
|
||||
}
|
||||
|
||||
static bool bmap_has_blocks(struct scoutfs_block_map *bmap)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < SCOUTFS_BLOCK_MAP_COUNT; i++) {
|
||||
if (bmap->blkno[i])
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free mapped blocks whose entire contents are past the new specified
|
||||
* size. The caller holds a transaction. If we truncate all the blocks
|
||||
* in a mapping item then we remove the item.
|
||||
*
|
||||
* This is the low level block allocation and bmap item manipulation.
|
||||
* Callers manage higher order truncation and orphan cleanup.
|
||||
*
|
||||
* XXX what to do about leaving items past i_size?
|
||||
* XXX probably should be a range
|
||||
*/
|
||||
int scoutfs_truncate_block_items(struct super_block *sb, u64 ino, u64 size)
|
||||
{
|
||||
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
|
||||
struct scoutfs_block_map *bmap;
|
||||
struct scoutfs_key first;
|
||||
struct scoutfs_key last;
|
||||
struct scoutfs_key key;
|
||||
bool delete;
|
||||
u64 iblock;
|
||||
u64 blkno;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
iblock = DIV_ROUND_UP(size, SCOUTFS_BLOCK_SIZE);
|
||||
i = iblock & SCOUTFS_BLOCK_MAP_MASK;
|
||||
|
||||
scoutfs_set_key(&first, ino, SCOUTFS_BMAP_KEY,
|
||||
iblock & ~(u64)SCOUTFS_BLOCK_MAP_MASK);
|
||||
scoutfs_set_key(&last, ino, SCOUTFS_BMAP_KEY, ~0ULL);
|
||||
|
||||
trace_printk("iblock %llu i %d\n", iblock, i);
|
||||
|
||||
while ((ret = scoutfs_btree_next(sb, &first, &last, &curs)) > 0) {
|
||||
key = *curs.key;
|
||||
first = *curs.key;
|
||||
scoutfs_inc_key(&first);
|
||||
scoutfs_btree_release(&curs);
|
||||
|
||||
ret = scoutfs_btree_update(sb, &key, &curs);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
/* XXX check sanity */
|
||||
bmap = curs.val;
|
||||
|
||||
for (; i < SCOUTFS_BLOCK_MAP_COUNT; i++) {
|
||||
blkno = le64_to_cpu(bmap->blkno[i]);
|
||||
if (blkno == 0)
|
||||
continue;
|
||||
|
||||
ret = scoutfs_buddy_free(sb, blkno, 0);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
bmap->blkno[i] = 0;
|
||||
}
|
||||
delete = !bmap_has_blocks(bmap);
|
||||
|
||||
scoutfs_btree_release(&curs);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
i = 0;
|
||||
|
||||
if (delete) {
|
||||
ret = scoutfs_btree_delete(sb, &key);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
/* XXX sync transaction if it's enormous */
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The caller ensures that this is serialized against all other callers
|
||||
* and writers.
|
||||
|
||||
@@ -5,5 +5,6 @@ extern const struct address_space_operations scoutfs_file_aops;
|
||||
extern const struct file_operations scoutfs_file_fops;
|
||||
|
||||
void scoutfs_filerw_free_alloc(struct super_block *sb);
|
||||
int scoutfs_truncate_block_items(struct super_block *sb, u64 ino, u64 size);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/xattr.h>
|
||||
#include <linux/mm.h>
|
||||
|
||||
#include "format.h"
|
||||
#include "super.h"
|
||||
@@ -25,6 +26,7 @@
|
||||
#include "filerw.h"
|
||||
#include "scoutfs_trace.h"
|
||||
#include "xattr.h"
|
||||
#include "trans.h"
|
||||
|
||||
/*
|
||||
* XXX
|
||||
@@ -347,6 +349,83 @@ struct inode *scoutfs_new_inode(struct super_block *sb, struct inode *dir,
|
||||
return inode;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove all the items associated with a given inode.
|
||||
*/
|
||||
static void drop_inode_items(struct super_block *sb, u64 ino)
|
||||
{
|
||||
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
|
||||
struct scoutfs_inode *sinode;
|
||||
struct scoutfs_key key;
|
||||
bool release = false;
|
||||
umode_t mode;
|
||||
int ret;
|
||||
|
||||
/* sample the inode mode */
|
||||
scoutfs_set_key(&key, ino, SCOUTFS_INODE_KEY, 0);
|
||||
ret = scoutfs_btree_lookup(sb, &key, &curs);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
sinode = curs.val;
|
||||
mode = le32_to_cpu(sinode->mode);
|
||||
scoutfs_btree_release(&curs);
|
||||
|
||||
ret = scoutfs_hold_trans(sb);
|
||||
if (ret)
|
||||
goto out;
|
||||
release = true;
|
||||
|
||||
ret = scoutfs_xattr_drop(sb, ino);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (S_ISLNK(mode))
|
||||
ret = scoutfs_symlink_drop(sb, ino);
|
||||
else if (S_ISREG(mode))
|
||||
ret = scoutfs_truncate_block_items(sb, ino, 0);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = scoutfs_btree_delete(sb, &key);
|
||||
out:
|
||||
if (ret)
|
||||
trace_printk("drop items failed ret %d ino %llu\n", ret, ino);
|
||||
if (release)
|
||||
scoutfs_release_trans(sb);
|
||||
}
|
||||
|
||||
/*
|
||||
* iput_final has already written out the dirty pages to the inode
|
||||
* before we get here. We're left with a clean inode that we have to
|
||||
* tear down. If there are no more links to the inode then we also
|
||||
* remove all its persistent structures.
|
||||
*/
|
||||
void scoutfs_evict_inode(struct inode *inode)
|
||||
{
|
||||
trace_printk("ino %llu nlink %d bad %d\n",
|
||||
scoutfs_ino(inode), inode->i_nlink, is_bad_inode(inode));
|
||||
|
||||
if (is_bad_inode(inode))
|
||||
goto clear;
|
||||
|
||||
truncate_inode_pages_final(&inode->i_data);
|
||||
|
||||
if (inode->i_nlink == 0)
|
||||
drop_inode_items(inode->i_sb, scoutfs_ino(inode));
|
||||
clear:
|
||||
clear_inode(inode);
|
||||
}
|
||||
|
||||
int scoutfs_drop_inode(struct inode *inode)
|
||||
{
|
||||
int ret = generic_drop_inode(inode);
|
||||
|
||||
trace_printk("ret %d nlink %d unhashed %d\n",
|
||||
ret, inode->i_nlink, inode_unhashed(inode));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void scoutfs_inode_exit(void)
|
||||
{
|
||||
if (scoutfs_inode_cachep) {
|
||||
|
||||
@@ -23,6 +23,8 @@ static inline u64 scoutfs_ino(struct inode *inode)
|
||||
|
||||
struct inode *scoutfs_alloc_inode(struct super_block *sb);
|
||||
void scoutfs_destroy_inode(struct inode *inode);
|
||||
int scoutfs_drop_inode(struct inode *inode);
|
||||
void scoutfs_evict_inode(struct inode *inode);
|
||||
|
||||
struct inode *scoutfs_iget(struct super_block *sb, u64 ino);
|
||||
int scoutfs_dirty_inode_item(struct inode *inode);
|
||||
|
||||
@@ -74,6 +74,8 @@ static int scoutfs_statfs(struct dentry *dentry, struct kstatfs *kst)
|
||||
|
||||
static const struct super_operations scoutfs_super_ops = {
|
||||
.alloc_inode = scoutfs_alloc_inode,
|
||||
.drop_inode = scoutfs_drop_inode,
|
||||
.evict_inode = scoutfs_evict_inode,
|
||||
.destroy_inode = scoutfs_destroy_inode,
|
||||
.sync_fs = scoutfs_sync_fs,
|
||||
.statfs = scoutfs_statfs,
|
||||
|
||||
@@ -487,3 +487,70 @@ ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
|
||||
|
||||
return ret < 0 ? ret : total;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete all the xattr items associted with this inode. The caller
|
||||
* holds a transaction.
|
||||
*
|
||||
* The name and value hashes are sorted by the hash value instead of the
|
||||
* inode so we have to use the inode's xattr items to find them. We
|
||||
* only remove the xattr item once the hash items are removed.
|
||||
*
|
||||
* Hash items can be shared amongst xattrs whose names or values hash to
|
||||
* the same hash value. We don't bother trying to remove the hash items
|
||||
* as the last xattr is removed. We remove it the first chance we get,
|
||||
* try to avoid obviously removing the same hash item next, and allow
|
||||
* failure when we try to remove a hash item that wasn't found.
|
||||
*/
|
||||
int scoutfs_xattr_drop(struct super_block *sb, u64 ino)
|
||||
{
|
||||
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
|
||||
struct scoutfs_xattr *xat;
|
||||
struct scoutfs_key first;
|
||||
struct scoutfs_key last;
|
||||
struct scoutfs_key key;
|
||||
struct scoutfs_key name_key;
|
||||
struct scoutfs_key val_key;
|
||||
__le64 last_name;
|
||||
__le64 last_val;
|
||||
u64 val_hash;
|
||||
bool have_last;
|
||||
int ret;
|
||||
|
||||
scoutfs_set_key(&first, ino, SCOUTFS_XATTR_KEY, 0);
|
||||
scoutfs_set_key(&last, ino, SCOUTFS_XATTR_KEY, ~0ULL);
|
||||
|
||||
have_last = false;
|
||||
while ((ret = scoutfs_btree_next(sb, &first, &last, &curs)) > 0) {
|
||||
xat = curs.val;
|
||||
key = *curs.key;
|
||||
val_hash = scoutfs_name_hash(xat_value(xat), xat->value_len);
|
||||
set_name_val_keys(&name_key, &val_key, &key, val_hash);
|
||||
|
||||
first = *curs.key;
|
||||
scoutfs_inc_key(&first);
|
||||
scoutfs_btree_release(&curs);
|
||||
|
||||
if (!have_last || last_name != name_key.inode) {
|
||||
ret = scoutfs_btree_delete(sb, &name_key);
|
||||
if (ret && ret != -ENOENT)
|
||||
break;
|
||||
last_name = name_key.inode;
|
||||
}
|
||||
|
||||
if (!have_last || last_val != val_key.inode) {
|
||||
ret = scoutfs_btree_delete(sb, &val_key);
|
||||
if (ret && ret != -ENOENT)
|
||||
break;
|
||||
last_val = val_key.inode;
|
||||
}
|
||||
|
||||
have_last = true;
|
||||
|
||||
ret = scoutfs_btree_delete(sb, &key);
|
||||
if (ret && ret != -ENOENT)
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -8,4 +8,6 @@ int scoutfs_setxattr(struct dentry *dentry, const char *name,
|
||||
int scoutfs_removexattr(struct dentry *dentry, const char *name);
|
||||
ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size);
|
||||
|
||||
int scoutfs_xattr_drop(struct super_block *sb, u64 ino);
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user