From 930ac1900eb5240b3787a4a5a930d19a5e070e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20A=2E=20Fern=C3=A1ndez?= Date: Sat, 7 Sep 2019 22:33:54 -0300 Subject: [PATCH] apfs: implement ->evict_inode() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the ->evict_inode() superblock operation, which must deal with orphaned inode records and also their fake hardlinks under private-dir. Signed-off-by: Ernesto A. Fernández --- fs/apfs/dir.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ fs/apfs/dir.h | 1 + fs/apfs/inode.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++ fs/apfs/inode.h | 1 + fs/apfs/super.c | 1 + 5 files changed, 135 insertions(+) diff --git a/fs/apfs/dir.c b/fs/apfs/dir.c index 7ff7ad1c6fdb..bc5ea35490a8 100644 --- a/fs/apfs/dir.c +++ b/fs/apfs/dir.c @@ -996,6 +996,73 @@ fail: return err; } +/** + * apfs_delete_orphan_link - Delete the link for an orphan inode + * @inode: the vfs inode + * + * Returns 0 on success or a negative error code in case of failure. + */ +int apfs_delete_orphan_link(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + struct apfs_sb_info *sbi = APFS_SB(sb); + struct apfs_key key; + struct apfs_query *query; + struct apfs_inode_val *inode_val; + char *node_raw; + struct qstr qname; + struct apfs_drec drec; + struct apfs_inode_info parent_info; + int err; + + err = apfs_orphan_name(inode, &qname); + if (err) + return err; + + /* + * XXX: fake the basic parent fields needed by apfs_dentry_lookup(); in + * the future we'll use a real private-dir inode. + */ + parent_info.vfs_inode.i_sb = sb; + parent_info.vfs_inode.i_ino = APFS_PRIV_DIR_INO_NUM; +#if BITS_PER_LONG == 32 + parent_info.i_ino = APFS_PRIV_DIR_INO_NUM; +#endif + + query = apfs_dentry_lookup(&parent_info.vfs_inode, &qname, &drec); + if (IS_ERR(query)) { + err = PTR_ERR(query); + query = NULL; + goto fail; + } + err = apfs_btree_remove(query); + if (err) + goto fail; + apfs_free_query(sb, query); + query = NULL; + + /* XXX: update the child count in the parent inode */ + apfs_init_inode_key(APFS_PRIV_DIR_INO_NUM, &key); + query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */); + if (!query) { + err = -ENOMEM; + goto fail; + } + query->key = &key; + query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT; + err = apfs_btree_query(sb, &query); + if (err) + goto fail; + node_raw = query->node->object.bh->b_data; + inode_val = (struct apfs_inode_val *)(node_raw + query->off); + le32_add_cpu(&inode_val->nchildren, -1); + +fail: + apfs_free_query(sb, query); + kfree(qname.name); + return err; +} + int apfs_unlink(struct inode *dir, struct dentry *dentry) { struct super_block *sb = dir->i_sb; diff --git a/fs/apfs/dir.h b/fs/apfs/dir.h index 282bda409e77..094f7c9a88e3 100644 --- a/fs/apfs/dir.h +++ b/fs/apfs/dir.h @@ -64,6 +64,7 @@ extern int apfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, extern int apfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry); extern int apfs_unlink(struct inode *dir, struct dentry *dentry); +extern int apfs_delete_orphan_link(struct inode *inode); extern const struct file_operations apfs_dir_operations; diff --git a/fs/apfs/inode.c b/fs/apfs/inode.c index 7469490da62c..1d70d3f18613 100644 --- a/fs/apfs/inode.c +++ b/fs/apfs/inode.c @@ -508,6 +508,71 @@ fail: return err; } +/** + * apfs_delete_inode - Delete an inode record and update the volume file count + * @inode: the vfs inode to delete + * + * Returns 0 on success or a negative error code in case of failure. + */ +static int apfs_delete_inode(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + struct apfs_sb_info *sbi = APFS_SB(sb); + struct apfs_superblock *vsb_raw = sbi->s_vsb_raw; + struct apfs_query *query; + int ret; + + query = apfs_inode_lookup(inode); + if (IS_ERR(query)) + return PTR_ERR(query); + ret = apfs_btree_remove(query); + apfs_free_query(sb, query); + + ASSERT(sbi->s_xid == le64_to_cpu(vsb_raw->apfs_o.o_xid)); + switch (inode->i_mode & S_IFMT) { + case S_IFREG: + le64_add_cpu(&vsb_raw->apfs_num_files, -1); + break; + case S_IFDIR: + le64_add_cpu(&vsb_raw->apfs_num_directories, -1); + break; + case S_IFLNK: + le64_add_cpu(&vsb_raw->apfs_num_symlinks, -1); + break; + default: + le64_add_cpu(&vsb_raw->apfs_num_other_fsobjects, -1); + break; + } + return ret; +} + +void apfs_evict_inode(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + + if (is_bad_inode(inode) || inode->i_nlink) + goto out_clear; + + if (apfs_transaction_start(sb)) + goto out_report; + if (apfs_delete_inode(inode)) + goto out_abort; + if (apfs_delete_orphan_link(inode)) + goto out_abort; + if (apfs_transaction_commit(sb)) + goto out_abort; + goto out_clear; + +out_abort: + apfs_transaction_abort(sb); +out_report: + apfs_warn(sb, + "failed to delete orphan inode 0x%llx\n", apfs_ino(inode)); +out_clear: + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); +} + /** * apfs_new_inode - Create a new in-memory inode * @dir: parent inode diff --git a/fs/apfs/inode.h b/fs/apfs/inode.h index 2218cda6a5cc..500d816a60a1 100644 --- a/fs/apfs/inode.h +++ b/fs/apfs/inode.h @@ -153,6 +153,7 @@ static inline u64 apfs_ino(const struct inode *inode) extern struct inode *apfs_iget(struct super_block *sb, u64 cnid); extern int apfs_update_inode(struct inode *inode, char *new_name); +extern void apfs_evict_inode(struct inode *inode); extern int apfs_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags); extern struct inode *apfs_new_inode(struct inode *dir, umode_t mode, diff --git a/fs/apfs/super.c b/fs/apfs/super.c index 9896ee7309d8..5fb18964b47a 100644 --- a/fs/apfs/super.c +++ b/fs/apfs/super.c @@ -693,6 +693,7 @@ static const struct super_operations apfs_sops = { .alloc_inode = apfs_alloc_inode, .destroy_inode = apfs_destroy_inode, .write_inode = apfs_write_inode, + .evict_inode = apfs_evict_inode, .put_super = apfs_put_super, .statfs = apfs_statfs, .show_options = apfs_show_options,