diff --git a/kmod/src/dir.c b/kmod/src/dir.c index 02b02f4f..a605fcfa 100644 --- a/kmod/src/dir.c +++ b/kmod/src/dir.c @@ -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. diff --git a/kmod/src/dir.h b/kmod/src/dir.h index c550ed00..07edc195 100644 --- a/kmod/src/dir.h +++ b/kmod/src/dir.h @@ -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 diff --git a/kmod/src/filerw.c b/kmod/src/filerw.c index 7ef5ea2a..e42a485f 100644 --- a/kmod/src/filerw.c +++ b/kmod/src/filerw.c @@ -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. diff --git a/kmod/src/filerw.h b/kmod/src/filerw.h index c182349d..ba2bb81f 100644 --- a/kmod/src/filerw.h +++ b/kmod/src/filerw.h @@ -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 diff --git a/kmod/src/inode.c b/kmod/src/inode.c index 75060fc9..93d68202 100644 --- a/kmod/src/inode.c +++ b/kmod/src/inode.c @@ -15,6 +15,7 @@ #include #include #include +#include #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) { diff --git a/kmod/src/inode.h b/kmod/src/inode.h index fab38b3f..e02acf27 100644 --- a/kmod/src/inode.h +++ b/kmod/src/inode.h @@ -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); diff --git a/kmod/src/super.c b/kmod/src/super.c index 252a27ca..605b5ef0 100644 --- a/kmod/src/super.c +++ b/kmod/src/super.c @@ -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, diff --git a/kmod/src/xattr.c b/kmod/src/xattr.c index 95def5ae..82d92961 100644 --- a/kmod/src/xattr.c +++ b/kmod/src/xattr.c @@ -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; +} diff --git a/kmod/src/xattr.h b/kmod/src/xattr.h index 7abb00c3..e0fadf32 100644 --- a/kmod/src/xattr.h +++ b/kmod/src/xattr.h @@ -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