mirror of
https://github.com/linux-apfs/linux-apfs.git
synced 2026-08-18 02:22:47 -07:00
apfs: provide basic support for writing to files
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 <ernesto@corellium.com>
This commit is contained in:
1 parent
4f9f794eba
commit
eeb18a4107
6 files changed
+571
-14
No files matched your search
+41
-6
@@ -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)
|
||||
|
||||
+250
-6
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
+256
-2
@@ -6,6 +6,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/mpage.h>
|
||||
#include <linux/blk_types.h>
|
||||
#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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user