From eeb18a4107600299ada89b8d346e43848906b4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20A=2E=20Fern=C3=A1ndez?= Date: Wed, 17 Feb 2021 01:59:46 -0300 Subject: [PATCH] apfs: provide basic support for writing to files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Begin work to support file writes. To this end, add functions to write file extent records and to deal with the new data stream record type. A whole new b-tree needs to be used as well: the extent reference tree. Adjust the node and query code to deal with it. There is too much I don't understand about the vfs, and writes look tricky, so try to keep things simple for now. Don't allow writes through mmap(), only create extents with a single block (even if this defeats the whole purpose of extents), and neglect to handle short writes correctly. Don't implement any form of truncation yet, and just let apfs_delete_inode() fail when the inode is not empty. Signed-off-by: Ernesto A. Fernández --- fs/apfs/apfs.h | 47 +++++++-- fs/apfs/extents.c | 256 +++++++++++++++++++++++++++++++++++++++++++-- fs/apfs/file.c | 2 + fs/apfs/inode.c | 258 +++++++++++++++++++++++++++++++++++++++++++++- fs/apfs/key.c | 19 ++++ fs/apfs/node.c | 3 + 6 files changed, 571 insertions(+), 14 deletions(-) diff --git a/fs/apfs/apfs.h b/fs/apfs/apfs.h index 2bfc58ac9ff0..3a32b5c3893d 100644 --- a/fs/apfs/apfs.h +++ b/fs/apfs/apfs.h @@ -228,6 +228,19 @@ static inline void apfs_init_omap_key(u64 oid, u64 xid, struct apfs_key *key) key->name = NULL; } +/** + * apfs_init_extent_key - Initialize an in-memory key for an extentref query + * @bno: physical block number for the start of the extent + * @key: apfs_key structure to initialize + */ +static inline void apfs_init_extent_key(u64 bno, struct apfs_key *key) +{ + key->id = bno; + key->type = APFS_TYPE_EXTENT; + key->number = 0; + key->name = NULL; +} + /** * apfs_init_inode_key - Initialize an in-memory key for an inode query * @ino: inode number @@ -256,6 +269,19 @@ static inline void apfs_init_file_extent_key(u64 id, u64 offset, key->name = NULL; } +/** + * apfs_init_dstream_id_key - Initialize an in-memory key for a dstream query + * @id: data stream id + * @key: apfs_key structure to initialize + */ +static inline void apfs_init_dstream_id_key(u64 id, struct apfs_key *key) +{ + key->id = id; + key->type = APFS_TYPE_DSTREAM_ID; + key->number = 0; + key->name = NULL; +} + /** * apfs_init_sibling_link_key - Initialize an in-memory key for a sibling query * @ino: inode number @@ -316,15 +342,16 @@ static inline void apfs_key_set_hdr(u64 type, u64 id, void *key) } /* Flags for the query structure */ -#define APFS_QUERY_TREE_MASK 0007 /* Which b-tree we query */ +#define APFS_QUERY_TREE_MASK 0017 /* Which b-tree we query */ #define APFS_QUERY_OMAP 0001 /* This is a b-tree object map query */ #define APFS_QUERY_CAT 0002 /* This is a catalog tree query */ #define APFS_QUERY_FREE_QUEUE 0004 /* This is a free queue query */ -#define APFS_QUERY_NEXT 0010 /* Find next of multiple matches */ -#define APFS_QUERY_EXACT 0020 /* Search for an exact match */ -#define APFS_QUERY_DONE 0040 /* The search at this level is over */ -#define APFS_QUERY_ANY_NAME 0100 /* Multiple search for any name */ -#define APFS_QUERY_ANY_NUMBER 0200 /* Multiple search for any number */ +#define APFS_QUERY_EXTENTREF 0010 /* This is an extent reference query */ +#define APFS_QUERY_NEXT 0020 /* Find next of multiple matches */ +#define APFS_QUERY_EXACT 0040 /* Search for an exact match */ +#define APFS_QUERY_DONE 0100 /* The search at this level is over */ +#define APFS_QUERY_ANY_NAME 0200 /* Multiple search for any name */ +#define APFS_QUERY_ANY_NUMBER 0400 /* Multiple search for any number */ #define APFS_QUERY_MULTIPLE (APFS_QUERY_ANY_NAME | APFS_QUERY_ANY_NUMBER) /* @@ -360,6 +387,8 @@ static inline u32 apfs_query_storage(struct apfs_query *query) return APFS_OBJ_VIRTUAL; if (query->flags & APFS_QUERY_FREE_QUEUE) return APFS_OBJ_EPHEMERAL; + if (query->flags & APFS_QUERY_EXTENTREF) + return APFS_OBJ_PHYSICAL; BUG(); } @@ -497,8 +526,12 @@ extern int apfs_delete_orphan_link(struct inode *inode); /* extents.c */ extern int apfs_extent_from_query(struct apfs_query *query, struct apfs_file_extent *extent); +extern int __apfs_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create); extern int apfs_get_block(struct inode *inode, sector_t iblock, struct buffer_head *bh_result, int create); +extern int apfs_get_new_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create); /* inode.c */ extern struct inode *apfs_iget(struct super_block *sb, u64 cnid); @@ -510,6 +543,7 @@ extern struct inode *apfs_new_inode(struct inode *dir, umode_t mode, dev_t rdev); extern int apfs_create_inode_rec(struct super_block *sb, struct inode *inode, struct dentry *dentry); +extern int apfs_setattr(struct dentry *dentry, struct iattr *iattr); /* key.c */ extern int apfs_filename_cmp(struct super_block *sb, @@ -519,6 +553,7 @@ extern int apfs_keycmp(struct super_block *sb, extern int apfs_read_cat_key(void *raw, int size, struct apfs_key *key); extern int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key); extern int apfs_read_omap_key(void *raw, int size, struct apfs_key *key); +extern int apfs_read_extentref_key(void *raw, int size, struct apfs_key *key); /* message.c */ extern __printf(3, 4) diff --git a/fs/apfs/extents.c b/fs/apfs/extents.c index 16a6dad2c9d0..dcfe2419e51e 100644 --- a/fs/apfs/extents.c +++ b/fs/apfs/extents.c @@ -101,20 +101,20 @@ done: return ret; } -int apfs_get_block(struct inode *inode, sector_t iblock, - struct buffer_head *bh_result, int create) +/* This does the same as apfs_get_block(), but without taking any locks */ +int __apfs_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create) { struct super_block *sb = inode->i_sb; - struct apfs_sb_info *sbi = APFS_SB(sb); struct apfs_file_extent ext; u64 blk_off, bno, map_len; int ret; - down_read(&sbi->s_big_sem); + ASSERT(!create); ret = apfs_extent_read(inode, iblock, &ext); if (ret) - goto fail; + return ret; /* Find the block offset of iblock within the extent */ blk_off = iblock - (ext.logical_addr >> inode->i_blkbits); @@ -136,8 +136,252 @@ int apfs_get_block(struct inode *inode, sector_t iblock, map_bh(bh_result, sb, bno); } bh_result->b_size = map_len; + return 0; +} -fail: +int apfs_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create) +{ + struct super_block *sb = inode->i_sb; + struct apfs_sb_info *sbi = APFS_SB(sb); + int ret; + + down_read(&sbi->s_big_sem); + ret = __apfs_get_block(inode, iblock, bh_result, create); up_read(&sbi->s_big_sem); return ret; } + +/** + * apfs_update_extents - Update the extent records to accommodate a new extent + * @inode: the vfs inode + * @extent: new in-memory file extent; the old physical location is set + * here on return so that the caller can release the blocks + * + * For now, assumes that all extents have a single block, and only creates or + * updates the records for @extent. TODO: support longer extents. Returns 0 + * on success or a negative error code in case of failure. + */ +static int apfs_update_extents(struct inode *inode, + struct apfs_file_extent *extent) +{ + struct super_block *sb = inode->i_sb; + struct apfs_sb_info *sbi = APFS_SB(sb); + struct apfs_inode_info *ai = APFS_I(inode); + struct apfs_key key; + struct apfs_query *query; + struct apfs_file_extent_key raw_key; + struct apfs_file_extent_val raw_val; + u64 extent_id = ai->i_extent_id; + int ret; + + ASSERT(extent->len == sb->s_blocksize); + + apfs_key_set_hdr(APFS_TYPE_FILE_EXTENT, extent_id, &raw_key); + raw_key.logical_addr = cpu_to_le64(extent->logical_addr); + raw_val.len_and_flags = cpu_to_le64(extent->len); + raw_val.phys_block_num = cpu_to_le64(extent->phys_block_num); + raw_val.crypto_id = 0; + + apfs_init_file_extent_key(extent_id, extent->logical_addr, &key); + + query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */); + if (!query) + return -ENOMEM; + query->key = &key; + /* The query is exact for now because we assume single-block extents */ + query->flags = APFS_QUERY_CAT | APFS_QUERY_EXACT; + + ret = apfs_btree_query(sb, &query); + if (ret && ret != -ENODATA) + goto fail; + + extent->phys_block_num = 0; + extent->len = 0; + if (!ret) { + /* TODO: preserve the flags, although none are defined yet */ + if (apfs_extent_from_query(query, extent)) { + ret = -EFSCORRUPTED; + goto fail; + } + } + + if (ret) + ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), + &raw_val, sizeof(raw_val)); + else + ret = apfs_btree_replace(query, &raw_key, sizeof(raw_key), + &raw_val, sizeof(raw_val)); + +fail: + apfs_free_query(sb, query); + return ret; +} + +/** + * apfs_create_phys_extent - Create the physical record for a new extent + * @inode: the vfs inode + * @extent: new in-memory file extent + * + * Only works with single-block extents, for now. Returns 0 on success or a + * negative error code in case of failure. + */ +static int apfs_create_phys_extent(struct inode *inode, + struct apfs_file_extent *extent) +{ + 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_inode_info *ai = APFS_I(inode); + struct apfs_node *extref_root; + struct apfs_key key; + struct apfs_query *query = NULL; + struct apfs_phys_ext_key raw_key; + struct apfs_phys_ext_val raw_val; + u64 kind = (u64)APFS_KIND_NEW << APFS_PEXT_KIND_SHIFT; + u64 blkcnt = extent->len >> sb->s_blocksize_bits; + int ret; + + extref_root = apfs_read_node(sb, + le64_to_cpu(vsb_raw->apfs_extentref_tree_oid), + APFS_OBJ_PHYSICAL, true /* write */); + if (IS_ERR(extref_root)) + return PTR_ERR(extref_root); + ASSERT(sbi->s_xid == le64_to_cpu(vsb_raw->apfs_o.o_xid)); + vsb_raw->apfs_extentref_tree_oid = cpu_to_le64(extref_root->object.oid); + + query = apfs_alloc_query(extref_root, NULL /* parent */); + if (!query) { + ret = -ENOMEM; + goto fail; + } + + apfs_init_extent_key(extent->phys_block_num, &key); + query->key = &key; + /* The query is exact for now because we assume single-block extents */ + query->flags = APFS_QUERY_EXTENTREF | APFS_QUERY_EXACT; + + ret = apfs_btree_query(sb, &query); + if (!ret) { + apfs_alert(sb, "physical extent record for unused block 0x%llx", + extent->phys_block_num); + ret = -EFSCORRUPTED; + goto fail; + } + if (ret != -ENODATA) + goto fail; + + apfs_key_set_hdr(APFS_TYPE_EXTENT, extent->phys_block_num, &raw_key); + raw_val.len_and_kind = cpu_to_le64(kind | blkcnt); + raw_val.owning_obj_id = cpu_to_le64(ai->i_extent_id); + raw_val.refcnt = cpu_to_le32(1); + ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), + &raw_val, sizeof(raw_val)); + +fail: + apfs_free_query(sb, query); + apfs_node_put(extref_root); + return ret; +} + +/** + * apfs_delete_phys_extent - Delete the physical record for an extent + * @inode: the vfs inode + * @extent: in-memory extent to delete + * + * Only works with single-block extents, for now. Returns 0 on success or a + * negative error code in case of failure. + */ +static int apfs_delete_phys_extent(struct inode *inode, + struct apfs_file_extent *extent) +{ + 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_node *extref_root; + struct apfs_key key; + struct apfs_query *query = NULL; + int ret; + + extref_root = apfs_read_node(sb, + le64_to_cpu(vsb_raw->apfs_extentref_tree_oid), + APFS_OBJ_PHYSICAL, true /* write */); + if (IS_ERR(extref_root)) + return PTR_ERR(extref_root); + ASSERT(sbi->s_xid == le64_to_cpu(vsb_raw->apfs_o.o_xid)); + vsb_raw->apfs_extentref_tree_oid = cpu_to_le64(extref_root->object.oid); + + query = apfs_alloc_query(extref_root, NULL /* parent */); + if (!query) { + ret = -ENOMEM; + goto fail; + } + + apfs_init_extent_key(extent->phys_block_num, &key); + query->key = &key; + /* The query is exact for now because we assume single-block extents */ + query->flags = APFS_QUERY_EXTENTREF | APFS_QUERY_EXACT; + + ret = apfs_btree_query(sb, &query); + if (ret == -ENODATA) + ret = -EFSCORRUPTED; + if (!ret) + ret = apfs_btree_remove(query); + +fail: + apfs_free_query(sb, query); + apfs_node_put(extref_root); + return ret; +} + +int apfs_get_new_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create) +{ + 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_inode_info *ai = APFS_I(inode); + struct apfs_file_extent ext; + int err; + + ASSERT(create); + + /* TODO: preallocate tail blocks, support sparse files */ + ext.len = sb->s_blocksize; + ext.logical_addr = iblock << inode->i_blkbits; + + err = apfs_spaceman_allocate_block(sb, &ext.phys_block_num); + if (err) + return err; + + map_bh(bh_result, sb, ext.phys_block_num); + err = apfs_transaction_join(sb, bh_result); + if (err) + return err; + + err = apfs_create_phys_extent(inode, &ext); + if (err) + return err; + err = apfs_update_extents(inode, &ext); + if (err) + return err; + inode_add_bytes(inode, sb->s_blocksize); + le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, 1); + + if (ext.len) { + /* Release the old blocks. TODO: support cloned files */ + inode_sub_bytes(inode, sb->s_blocksize); + le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, -1); + err = apfs_delete_phys_extent(inode, &ext); + if (err) + return err; + err = apfs_free_queue_insert(sb, ext.phys_block_num); + if (err) + return err; + } + + /* Just invalidate the cache; s_big_sem provides the locking here */ + ai->i_cached_extent.len = 0; + + return 0; +} diff --git a/fs/apfs/file.c b/fs/apfs/file.c index e35ffcf5e122..575f049ae078 100644 --- a/fs/apfs/file.c +++ b/fs/apfs/file.c @@ -8,6 +8,7 @@ const struct file_operations apfs_file_operations = { .llseek = generic_file_llseek, .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, .mmap = generic_file_readonly_mmap, .open = generic_file_open, }; @@ -15,4 +16,5 @@ const struct file_operations apfs_file_operations = { const struct inode_operations apfs_file_inode_operations = { .getattr = apfs_getattr, .listxattr = apfs_listxattr, + .setattr = apfs_setattr, }; diff --git a/fs/apfs/inode.c b/fs/apfs/inode.c index affceb02c1b6..4f4a98dea072 100644 --- a/fs/apfs/inode.c +++ b/fs/apfs/inode.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "apfs.h" static int apfs_readpage(struct file *file, struct page *page) @@ -19,10 +20,156 @@ static int apfs_readpages(struct file *file, struct address_space *mapping, return mpage_readpages(mapping, pages, nr_pages, apfs_get_block); } +/** + * apfs_create_dstream_rec - Create the data stream record for an inode + * @inode: the vfs inode + * + * Does nothing if the record already exists. TODO: support cloned files. + * Returns 0 on success or a negative error code in case of failure. + */ +static int apfs_create_dstream_rec(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + struct apfs_sb_info *sbi = APFS_SB(sb); + struct apfs_inode_info *ai = APFS_I(inode); + struct apfs_key key; + struct apfs_query *query; + struct apfs_dstream_id_key raw_key; + struct apfs_dstream_id_val raw_val; + int ret; + + if (inode->i_size || inode->i_blocks) /* Already has a dstream */ + return 0; + + apfs_init_dstream_id_key(ai->i_extent_id, &key); + query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */); + if (!query) + return -ENOMEM; + query->key = &key; + query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT; + + ret = apfs_btree_query(sb, &query); + if (ret != -ENODATA) /* Either an error, or the record already exists */ + goto out; + + apfs_key_set_hdr(APFS_TYPE_DSTREAM_ID, ai->i_extent_id, &raw_key); + raw_val.refcnt = cpu_to_le32(1); + ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), + &raw_val, sizeof(raw_val)); +out: + apfs_free_query(sb, query); + return ret; +} + +static int apfs_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, unsigned int flags, + struct page **pagep, void **fsdata) +{ + struct inode *inode = mapping->host; + struct super_block *sb = inode->i_sb; + struct page *page; + struct buffer_head *bh, *head; + unsigned int blocksize, block_start, block_end, from, to; + pgoff_t index = pos >> PAGE_SHIFT; + sector_t iblock = (sector_t)index << (PAGE_SHIFT - inode->i_blkbits); + int err; + + err = apfs_transaction_start(sb); + if (err) + return err; + + err = apfs_create_dstream_rec(inode); + if (err) + goto out_abort; + + page = grab_cache_page_write_begin(mapping, index, + flags | AOP_FLAG_NOFS); + if (!page) { + err = -ENOMEM; + goto out_abort; + } + if (!page_has_buffers(page)) + create_empty_buffers(page, sb->s_blocksize, 0); + + /* CoW moves existing blocks, so read them but mark them as unmapped */ + head = page_buffers(page); + blocksize = head->b_size; + from = pos & (PAGE_SIZE - 1); + to = from + min(inode->i_size - pos, (loff_t)len); + for (bh = head, block_start = 0; bh != head || !block_start; + block_start = block_end, bh = bh->b_this_page, ++iblock) { + block_end = block_start + blocksize; + if (to > block_start && from < block_end) { + if (!buffer_mapped(bh)) { + err = __apfs_get_block(inode, iblock, bh, + false /* create */); + if (err) + goto out_put_page; + } + if (!buffer_uptodate(bh)) { + get_bh(bh); + lock_buffer(bh); + bh->b_end_io = end_buffer_read_sync; + submit_bh(REQ_OP_READ, 0, bh); + wait_on_buffer(bh); + if (!buffer_uptodate(bh)) { + err = -EIO; + goto out_put_page; + } + } + clear_buffer_mapped(bh); + } + } + + err = __block_write_begin(page, pos, len, apfs_get_new_block); + if (err) + goto out_put_page; + + *pagep = page; + return 0; + +out_put_page: + unlock_page(page); + put_page(page); +out_abort: + apfs_transaction_abort(sb); + return err; +} + +static int apfs_write_end(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, unsigned int copied, + struct page *page, void *fsdata) +{ + struct inode *inode = mapping->host; + struct super_block *sb = inode->i_sb; + int ret, err; + + ret = generic_write_end(file, mapping, pos, len, copied, page, fsdata); + if (ret < len) { + /* XXX: handle short writes */ + err = -EIO; + goto out_abort; + } + + err = apfs_update_inode(inode, NULL /* new_name */); + if (err) + goto out_abort; + + err = apfs_transaction_commit(sb); + if (!err) + return ret; + +out_abort: + apfs_transaction_abort(sb); + return err; +} + /* bmap is not implemented to avoid issues with CoW on swapfiles */ static const struct address_space_operations apfs_aops = { .readpage = apfs_readpage, .readpages = apfs_readpages, + .write_begin = apfs_write_begin, + .write_end = apfs_write_end, }; /** @@ -381,6 +528,99 @@ fail: return err; } +/** + * apfs_create_dstream_xfield - Create the inode xfield for a new data stream + * @inode: the in-memory inode + * @query: the query that found the inode record + * + * Returns 0 on success, or a negative error code in case of failure. + */ +static int apfs_create_dstream_xfield(struct inode *inode, + struct apfs_query *query) +{ + char *raw = query->node->object.bh->b_data; + struct apfs_inode_val *new_val; + struct apfs_dstream dstream = {0}; + struct apfs_x_field xkey; + int xlen; + int buflen; + int err; + + buflen = query->len; + buflen += sizeof(struct apfs_x_field) + sizeof(dstream); + new_val = kzalloc(buflen, GFP_KERNEL); + if (!new_val) + return -ENOMEM; + memcpy(new_val, raw + query->off, query->len); + + dstream.size = cpu_to_le64(inode->i_size); + dstream.alloced_size = cpu_to_le64(inode->i_blocks << 9); + + /* TODO: can we assume that all inode records have an xfield blob? */ + xkey.x_type = APFS_INO_EXT_TYPE_DSTREAM; + xkey.x_flags = APFS_XF_SYSTEM_FIELD; + xkey.x_size = cpu_to_le16(sizeof(dstream)); + xlen = apfs_insert_xfield(new_val->xfields, buflen - sizeof(*new_val), + &xkey, &dstream); + if (!xlen) { + /* Buffer has enough space, but the metadata claims otherwise */ + apfs_alert(inode->i_sb, "bad xfields on inode 0x%llx", + apfs_ino(inode)); + err = -EFSCORRUPTED; + goto fail; + } + + /* Just remove the old record and create a new one */ + err = apfs_btree_replace(query, NULL /* key */, 0 /* key_len */, + new_val, sizeof(*new_val) + xlen); + +fail: + kfree(new_val); + return err; +} + +/** + * apfs_inode_resize - Update the sizes reported in an inode record + * @inode: the in-memory inode + * @query: the query that found the inode record + * + * Returns 0 on success, or a negative error code in case of failure. + */ +static int apfs_inode_resize(struct inode *inode, struct apfs_query *query) +{ + char *raw = query->node->object.bh->b_data; + struct apfs_inode_val *inode_raw; + char *xval; + int xlen; + int err; + + err = apfs_query_join_transaction(query); + if (err) + return err; + inode_raw = (void *)raw + query->off; + + xlen = apfs_find_xfield(inode_raw->xfields, + query->len - sizeof(*inode_raw), + APFS_INO_EXT_TYPE_DSTREAM, &xval); + if (!xlen && !inode->i_size) /* Empty file: no dstream needed yet */ + return 0; + + if (xlen) { + struct apfs_dstream *dstream; + + if (xlen != sizeof(*dstream)) + return -EFSCORRUPTED; + dstream = (struct apfs_dstream *)xval; + + /* TODO: count bytes read and written */ + dstream->size = cpu_to_le64(inode->i_size); + dstream->alloced_size = cpu_to_le64(inode->i_blocks << 9); + return 0; + } + /* This inode has no dstream xfield, so we need to create it */ + return apfs_create_dstream_xfield(inode, query); +} + /** * apfs_update_inode - Update an existing inode record * @inode: the modified in-memory inode @@ -407,6 +647,10 @@ int apfs_update_inode(struct inode *inode, char *new_name) if (err) goto fail; + err = apfs_inode_resize(inode, query); + if (err) + goto fail; + /* TODO: just use apfs_btree_replace()? */ err = apfs_query_join_transaction(query); if (err) @@ -440,8 +684,6 @@ int apfs_update_inode(struct inode *inode, char *new_name) inode_raw->nlink = cpu_to_le32(inode->i_nlink ? : 1); } - /* TODO: set size and block count */ - fail: apfs_free_query(sb, query); return err; @@ -461,6 +703,9 @@ static int apfs_delete_inode(struct inode *inode) struct apfs_query *query; int ret; + if (inode->i_size || inode->i_blocks) /* TODO: implement truncation */ + return -EOPNOTSUPP; + query = apfs_inode_lookup(inode); if (IS_ERR(query)) return PTR_ERR(query); @@ -635,3 +880,12 @@ fail: apfs_free_query(sb, query); return ret; } + +int apfs_setattr(struct dentry *dentry, struct iattr *iattr) +{ + struct inode *inode = d_inode(dentry); + + if (iattr->ia_valid & ATTR_SIZE && iattr->ia_size != inode->i_size) + return -EOPNOTSUPP; /* TODO: implement truncation */ + return simple_setattr(dentry, iattr); +} diff --git a/fs/apfs/key.c b/fs/apfs/key.c index 201532fc07d8..f3c1cd96c612 100644 --- a/fs/apfs/key.c +++ b/fs/apfs/key.c @@ -195,6 +195,25 @@ int apfs_read_omap_key(void *raw, int size, struct apfs_key *key) return 0; } +/** + * apfs_read_extentref_key - Parse an on-disk extent reference tree key + * @raw: pointer to the raw key + * @size: size of the raw key + * @key: apfs_key structure to store the result + * + * Returns 0 on success, or a negative error code otherwise. + */ +int apfs_read_extentref_key(void *raw, int size, struct apfs_key *key) +{ + if (size != sizeof(struct apfs_phys_ext_key)) + return -EFSCORRUPTED; + key->id = apfs_cat_cnid((struct apfs_key_header *)raw); + key->type = apfs_cat_type((struct apfs_key_header *)raw); + key->number = 0; + key->name = NULL; + return 0; +} + /** * apfs_init_drec_hashed_key - Initialize an in-memory key for a dentry query * @sb: filesystem superblock diff --git a/fs/apfs/node.c b/fs/apfs/node.c index 3e6cf5afc0c4..7c8fbf47a653 100644 --- a/fs/apfs/node.c +++ b/fs/apfs/node.c @@ -510,6 +510,9 @@ static int apfs_key_from_query(struct apfs_query *query, struct apfs_key *key) case APFS_QUERY_FREE_QUEUE: err = apfs_read_free_queue_key(raw_key, query->key_len, key); break; + case APFS_QUERY_EXTENTREF: + err = apfs_read_extentref_key(raw_key, query->key_len, key); + break; default: /* Not implemented yet */ err = -EINVAL;