mirror of
https://github.com/linux-apfs/linux-apfs-rw.git
synced 2026-08-18 02:19:59 -07:00
The build is broken for the 7.2 release candidate. The reason is that submit_bh() is gone now, replaced with a new bh_submit() that also takes the I/O end handler as an argument. Adjust apfs_submit_bh() to these changes. Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2373 lines
71 KiB
C
2373 lines
71 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
|
*/
|
|
|
|
#include <linux/buffer_head.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/blk_types.h>
|
|
#include "apfs.h"
|
|
|
|
/**
|
|
* apfs_ext_is_hole - Does this extent represent a hole in a sparse file?
|
|
* @extent: the extent to check
|
|
*/
|
|
static inline bool apfs_ext_is_hole(struct apfs_file_extent *extent)
|
|
{
|
|
return extent->phys_block_num == 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_size_to_blocks - Return the block count for a given size, rounded up
|
|
* @sb: filesystem superblock
|
|
* @size: size in bytes
|
|
*
|
|
* TODO: reuse for inode.c
|
|
*/
|
|
static inline u64 apfs_size_to_blocks(struct super_block *sb, u64 size)
|
|
{
|
|
return (size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
|
|
}
|
|
|
|
/**
|
|
* apfs_extent_from_query - Read the extent found by a successful query
|
|
* @query: the query that found the record
|
|
* @extent: Return parameter. The extent found.
|
|
*
|
|
* Reads the extent record into @extent and performs some basic sanity checks
|
|
* as a protection against crafted filesystems. Returns 0 on success or
|
|
* -EFSCORRUPTED otherwise.
|
|
*/
|
|
int apfs_extent_from_query(struct apfs_query *query,
|
|
struct apfs_file_extent *extent)
|
|
{
|
|
struct super_block *sb = query->node->object.sb;
|
|
char *raw = query->node->object.data;
|
|
u64 ext_len;
|
|
|
|
if (!apfs_is_sealed(sb)) {
|
|
struct apfs_file_extent_val *ext = NULL;
|
|
struct apfs_file_extent_key *ext_key = NULL;
|
|
|
|
if (query->len != sizeof(*ext) || query->key_len != sizeof(*ext_key)) {
|
|
apfs_err(sb, "bad length of key (%d) or value (%d)", query->key_len, query->len);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
ext = (struct apfs_file_extent_val *)(raw + query->off);
|
|
ext_key = (struct apfs_file_extent_key *)(raw + query->key_off);
|
|
ext_len = le64_to_cpu(ext->len_and_flags) & APFS_FILE_EXTENT_LEN_MASK;
|
|
|
|
extent->logical_addr = le64_to_cpu(ext_key->logical_addr);
|
|
extent->phys_block_num = le64_to_cpu(ext->phys_block_num);
|
|
extent->crypto_id = le64_to_cpu(ext->crypto_id);
|
|
} else {
|
|
struct apfs_fext_tree_val *fext_val = NULL;
|
|
struct apfs_fext_tree_key *fext_key = NULL;
|
|
|
|
if (query->len != sizeof(*fext_val) || query->key_len != sizeof(*fext_key)) {
|
|
apfs_err(sb, "bad length of sealed key (%d) or value (%d)", query->key_len, query->len);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
fext_val = (struct apfs_fext_tree_val *)(raw + query->off);
|
|
fext_key = (struct apfs_fext_tree_key *)(raw + query->key_off);
|
|
ext_len = le64_to_cpu(fext_val->len_and_flags) & APFS_FILE_EXTENT_LEN_MASK;
|
|
|
|
extent->logical_addr = le64_to_cpu(fext_key->logical_addr);
|
|
extent->phys_block_num = le64_to_cpu(fext_val->phys_block_num);
|
|
extent->crypto_id = 0;
|
|
}
|
|
|
|
/* Extent length must be a multiple of the block size */
|
|
if (ext_len & (sb->s_blocksize - 1)) {
|
|
apfs_err(sb, "invalid length (0x%llx)", ext_len);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
extent->len = ext_len;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_extent_read - Read the extent record that covers a block
|
|
* @dstream: data stream info
|
|
* @dsblock: logical number of the wanted block (must be in range)
|
|
* @extent: Return parameter. The extent found.
|
|
*
|
|
* Finds and caches the extent record. On success, returns a pointer to the
|
|
* cache record; on failure, returns an error code.
|
|
*/
|
|
static int apfs_extent_read(struct apfs_dstream_info *dstream, sector_t dsblock,
|
|
struct apfs_file_extent *extent)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
|
struct apfs_superblock *vsb_raw = sbi->s_vsb_raw;
|
|
struct apfs_key key;
|
|
struct apfs_query *query;
|
|
struct apfs_file_extent *cache = &dstream->ds_cached_ext;
|
|
u64 iaddr = dsblock << sb->s_blocksize_bits;
|
|
struct apfs_node *root = NULL;
|
|
int ret = 0;
|
|
|
|
spin_lock(&dstream->ds_ext_lock);
|
|
if (iaddr >= cache->logical_addr &&
|
|
iaddr < cache->logical_addr + cache->len) {
|
|
*extent = *cache;
|
|
spin_unlock(&dstream->ds_ext_lock);
|
|
return 0;
|
|
}
|
|
spin_unlock(&dstream->ds_ext_lock);
|
|
|
|
/* We will search for the extent that covers iblock */
|
|
if (!apfs_is_sealed(sb)) {
|
|
apfs_init_file_extent_key(dstream->ds_id, iaddr, &key);
|
|
root = sbi->s_cat_root;
|
|
} else {
|
|
apfs_init_fext_key(dstream->ds_id, iaddr, &key);
|
|
root = apfs_read_node(sb, le64_to_cpu(vsb_raw->apfs_fext_tree_oid), APFS_OBJ_PHYSICAL, false /* write */);
|
|
if (IS_ERR(root)) {
|
|
apfs_err(sb, "failed to read fext root 0x%llx", le64_to_cpu(vsb_raw->apfs_fext_tree_oid));
|
|
return PTR_ERR(root);
|
|
}
|
|
}
|
|
|
|
query = apfs_alloc_query(root, NULL /* parent */);
|
|
if (!query) {
|
|
ret = -ENOMEM;
|
|
goto done;
|
|
}
|
|
query->key = key;
|
|
query->flags = apfs_is_sealed(sb) ? APFS_QUERY_FEXT : APFS_QUERY_CAT;
|
|
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret) {
|
|
apfs_err(sb, "query failed for id 0x%llx, addr 0x%llx", dstream->ds_id, iaddr);
|
|
if (ret == -ENODATA)
|
|
ret = -EFSCORRUPTED;
|
|
goto done;
|
|
}
|
|
|
|
ret = apfs_extent_from_query(query, extent);
|
|
if (ret) {
|
|
apfs_err(sb, "bad extent record for dstream 0x%llx", dstream->ds_id);
|
|
goto done;
|
|
}
|
|
if (iaddr < extent->logical_addr || iaddr >= extent->logical_addr + extent->len) {
|
|
apfs_err(sb, "no extent for addr 0x%llx in dstream 0x%llx", iaddr, dstream->ds_id);
|
|
ret = -EFSCORRUPTED;
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* For now prioritize the deferral of writes.
|
|
* i_extent_dirty is protected by the read semaphore.
|
|
*/
|
|
if (!dstream->ds_ext_dirty) {
|
|
spin_lock(&dstream->ds_ext_lock);
|
|
*cache = *extent;
|
|
spin_unlock(&dstream->ds_ext_lock);
|
|
}
|
|
|
|
done:
|
|
apfs_free_query(query);
|
|
if (apfs_is_sealed(sb))
|
|
apfs_node_free(root);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_logic_to_phys_bno - Find the physical block number for a dstream block
|
|
* @dstream: data stream info
|
|
* @dsblock: logical number of the wanted block
|
|
* @bno: on return, the physical block number (or zero for holes)
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
int apfs_logic_to_phys_bno(struct apfs_dstream_info *dstream, sector_t dsblock, u64 *bno)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_file_extent ext;
|
|
u64 blk_off;
|
|
int ret;
|
|
|
|
ret = apfs_extent_read(dstream, dsblock, &ext);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (apfs_ext_is_hole(&ext)) {
|
|
*bno = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Find the block offset of iblock within the extent */
|
|
blk_off = dsblock - (ext.logical_addr >> sb->s_blocksize_bits);
|
|
*bno = ext.phys_block_num + blk_off;
|
|
return 0;
|
|
}
|
|
|
|
/* This does the same as apfs_get_block(), but without taking any locks */
|
|
int __apfs_get_block(struct apfs_dstream_info *dstream, sector_t dsblock,
|
|
struct buffer_head *bh_result, int create)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_file_extent ext;
|
|
u64 blk_off, bno, map_len;
|
|
int ret;
|
|
|
|
ASSERT(!create);
|
|
|
|
if (dsblock >= apfs_size_to_blocks(sb, dstream->ds_size))
|
|
return 0;
|
|
|
|
ret = apfs_extent_read(dstream, dsblock, &ext);
|
|
if (ret) {
|
|
apfs_err(sb, "extent read failed");
|
|
return ret;
|
|
}
|
|
|
|
/* Find the block offset of iblock within the extent */
|
|
blk_off = dsblock - (ext.logical_addr >> sb->s_blocksize_bits);
|
|
|
|
/* Make sure we don't read past the extent boundaries */
|
|
map_len = ext.len - (blk_off << sb->s_blocksize_bits);
|
|
if (bh_result->b_size > map_len)
|
|
bh_result->b_size = map_len;
|
|
|
|
/*
|
|
* Save the requested mapping length as apfs_map_bh() replaces it with
|
|
* the filesystem block size
|
|
*/
|
|
map_len = bh_result->b_size;
|
|
/* Extents representing holes have block number 0 */
|
|
if (!apfs_ext_is_hole(&ext)) {
|
|
/* Find the block number of iblock within the disk */
|
|
bno = ext.phys_block_num + blk_off;
|
|
apfs_map_bh(bh_result, sb, bno);
|
|
}
|
|
bh_result->b_size = map_len;
|
|
return 0;
|
|
}
|
|
|
|
int apfs_get_block(struct inode *inode, sector_t iblock,
|
|
struct buffer_head *bh_result, int create)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(inode->i_sb);
|
|
struct apfs_inode_info *ai = APFS_I(inode);
|
|
int ret;
|
|
|
|
down_read(&nxi->nx_big_sem);
|
|
ret = __apfs_get_block(&ai->i_dstream, iblock, bh_result, create);
|
|
up_read(&nxi->nx_big_sem);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_set_extent_length - Set a new length in an extent record's value
|
|
* @ext: the extent record's value
|
|
* @len: the new length
|
|
*
|
|
* Preserves the flags, though none are defined yet and I don't know if that
|
|
* will ever be important.
|
|
*/
|
|
static inline void apfs_set_extent_length(struct apfs_file_extent_val *ext, u64 len)
|
|
{
|
|
u64 len_and_flags = le64_to_cpu(ext->len_and_flags);
|
|
u64 flags = len_and_flags & APFS_FILE_EXTENT_FLAG_MASK;
|
|
|
|
ext->len_and_flags = cpu_to_le64(flags | len);
|
|
}
|
|
|
|
static int apfs_range_put_reference(struct super_block *sb, u64 paddr, u64 length);
|
|
|
|
/**
|
|
* apfs_shrink_extent_head - Shrink an extent record in its head
|
|
* @query: the query that found the record
|
|
* @dstream: data stream info
|
|
* @start: new logical start for the extent
|
|
*
|
|
* Also deletes the physical extent records for the head. Returns 0 on success
|
|
* or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_shrink_extent_head(struct apfs_query *query, struct apfs_dstream_info *dstream, u64 start)
|
|
{
|
|
struct super_block *sb = query->node->object.sb;
|
|
struct apfs_file_extent_key key;
|
|
struct apfs_file_extent_val val;
|
|
struct apfs_file_extent extent;
|
|
u64 new_len, head_len;
|
|
void *raw = NULL;
|
|
int err = 0;
|
|
|
|
err = apfs_extent_from_query(query, &extent);
|
|
if (err) {
|
|
apfs_err(sb, "bad extent record for dstream 0x%llx", dstream->ds_id);
|
|
return err;
|
|
}
|
|
raw = query->node->object.data;
|
|
key = *(struct apfs_file_extent_key *)(raw + query->key_off);
|
|
val = *(struct apfs_file_extent_val *)(raw + query->off);
|
|
|
|
new_len = extent.logical_addr + extent.len - start;
|
|
head_len = extent.len - new_len;
|
|
|
|
/* Delete the physical records for the blocks lost in the shrinkage */
|
|
if (!apfs_ext_is_hole(&extent)) {
|
|
err = apfs_range_put_reference(sb, extent.phys_block_num, head_len);
|
|
if (err) {
|
|
apfs_err(sb, "failed to put range 0x%llx-0x%llx", extent.phys_block_num, head_len);
|
|
return err;
|
|
}
|
|
} else {
|
|
dstream->ds_sparse_bytes -= head_len;
|
|
}
|
|
|
|
/* This is the actual shrinkage of the logical extent */
|
|
key.logical_addr = cpu_to_le64(start);
|
|
apfs_set_extent_length(&val, new_len);
|
|
if (!apfs_ext_is_hole(&extent))
|
|
le64_add_cpu(&val.phys_block_num, head_len >> sb->s_blocksize_bits);
|
|
return apfs_btree_replace(query, &key, sizeof(key), &val, sizeof(val));
|
|
}
|
|
|
|
/**
|
|
* apfs_shrink_extent_tail - Shrink an extent record in its tail
|
|
* @query: the query that found the record
|
|
* @dstream: data stream info
|
|
* @end: new logical end for the extent
|
|
*
|
|
* Also puts the physical extent records for the tail. Returns 0 on success or
|
|
* a negative error code in case of failure.
|
|
*/
|
|
static int apfs_shrink_extent_tail(struct apfs_query *query, struct apfs_dstream_info *dstream, u64 end)
|
|
{
|
|
struct super_block *sb = query->node->object.sb;
|
|
struct apfs_file_extent_val *val;
|
|
struct apfs_file_extent extent;
|
|
u64 new_len, new_blkcount, tail_len;
|
|
void *raw;
|
|
int err = 0;
|
|
|
|
ASSERT((end & (sb->s_blocksize - 1)) == 0);
|
|
|
|
err = apfs_query_join_transaction(query);
|
|
if (err) {
|
|
apfs_err(sb, "query join failed");
|
|
return err;
|
|
}
|
|
raw = query->node->object.data;
|
|
|
|
err = apfs_extent_from_query(query, &extent);
|
|
if (err) {
|
|
apfs_err(sb, "bad extent record for dstream 0x%llx", dstream->ds_id);
|
|
return err;
|
|
}
|
|
val = raw + query->off;
|
|
|
|
new_len = end - extent.logical_addr;
|
|
new_blkcount = new_len >> sb->s_blocksize_bits;
|
|
tail_len = extent.len - new_len;
|
|
|
|
/* Delete the physical records for the blocks lost in the shrinkage */
|
|
if (!apfs_ext_is_hole(&extent)) {
|
|
err = apfs_range_put_reference(sb, extent.phys_block_num + new_blkcount, tail_len);
|
|
if (err) {
|
|
apfs_err(sb, "failed to put range 0x%llx-0x%llx", extent.phys_block_num + new_blkcount, tail_len);
|
|
return err;
|
|
}
|
|
} else {
|
|
dstream->ds_sparse_bytes -= tail_len;
|
|
}
|
|
|
|
/* This is the actual shrinkage of the logical extent */
|
|
apfs_set_extent_length(val, new_len);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_query_found_extent - Did this query find an extent with the right id?
|
|
* @query: the (successful) query that found the record
|
|
*/
|
|
static inline bool apfs_query_found_extent(struct apfs_query *query)
|
|
{
|
|
void *raw = query->node->object.data;
|
|
struct apfs_key_header *hdr;
|
|
|
|
if (query->key_len < sizeof(*hdr))
|
|
return false;
|
|
hdr = raw + query->key_off;
|
|
|
|
if (apfs_cat_type(hdr) != APFS_TYPE_FILE_EXTENT)
|
|
return false;
|
|
if (apfs_cat_cnid(hdr) != query->key.id)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* apfs_update_tail_extent - Grow the tail extent for a data stream
|
|
* @dstream: data stream info
|
|
* @extent: new in-memory extent
|
|
*
|
|
* Also takes care of any needed changes to the physical extent records. Returns
|
|
* 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_update_tail_extent(struct apfs_dstream_info *dstream, const struct apfs_file_extent *extent)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
|
struct apfs_query *query;
|
|
struct apfs_file_extent_key raw_key;
|
|
struct apfs_file_extent_val raw_val;
|
|
u64 extent_id = dstream->ds_id;
|
|
int ret;
|
|
u64 new_crypto;
|
|
|
|
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);
|
|
if (apfs_vol_is_encrypted(sb))
|
|
new_crypto = extent_id;
|
|
else
|
|
new_crypto = 0;
|
|
raw_val.crypto_id = cpu_to_le64(new_crypto);
|
|
|
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
|
if (!query)
|
|
return -ENOMEM;
|
|
/* We want the last extent record */
|
|
apfs_init_file_extent_key(extent_id, -1, &query->key);
|
|
query->flags = APFS_QUERY_CAT;
|
|
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret && ret != -ENODATA) {
|
|
apfs_err(sb, "query failed for last extent of id 0x%llx", extent_id);
|
|
goto out;
|
|
}
|
|
|
|
if (ret == -ENODATA || !apfs_query_found_extent(query)) {
|
|
/* We are creating the first extent for the file */
|
|
ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
if (ret) {
|
|
apfs_err(sb, "insertion failed for id 0x%llx, addr 0x%llx", extent_id, extent->logical_addr);
|
|
goto out;
|
|
}
|
|
} else {
|
|
struct apfs_file_extent tail;
|
|
|
|
ret = apfs_extent_from_query(query, &tail);
|
|
if (ret) {
|
|
apfs_err(sb, "bad extent record for dstream 0x%llx", dstream->ds_id);
|
|
goto out;
|
|
}
|
|
|
|
if (tail.logical_addr > extent->logical_addr) {
|
|
apfs_alert(sb, "extent is not tail - bug!");
|
|
ret = -EOPNOTSUPP;
|
|
goto out;
|
|
} else if (tail.logical_addr == extent->logical_addr) {
|
|
ret = apfs_btree_replace(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
if (ret) {
|
|
apfs_err(sb, "update failed for id 0x%llx, addr 0x%llx", extent_id, extent->logical_addr);
|
|
goto out;
|
|
}
|
|
if (apfs_ext_is_hole(&tail)) {
|
|
dstream->ds_sparse_bytes -= tail.len;
|
|
} else if (tail.phys_block_num != extent->phys_block_num) {
|
|
ret = apfs_range_put_reference(sb, tail.phys_block_num, tail.len);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to put range 0x%llx-0x%llx", tail.phys_block_num, tail.len);
|
|
goto out;
|
|
}
|
|
}
|
|
if (new_crypto == tail.crypto_id)
|
|
goto out;
|
|
ret = apfs_crypto_adj_refcnt(sb, tail.crypto_id, -1);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to put crypto id 0x%llx", tail.crypto_id);
|
|
goto out;
|
|
}
|
|
} else {
|
|
/*
|
|
* TODO: we could actually also continue the tail extent
|
|
* if it's right next to the new one (both logically and
|
|
* physically), even if they don't overlap. Or maybe we
|
|
* should always make sure that the tail extent is in
|
|
* the cache before a write...
|
|
*/
|
|
if (extent->logical_addr < tail.logical_addr + tail.len) {
|
|
ret = apfs_shrink_extent_tail(query, dstream, extent->logical_addr);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to shrink tail of dstream 0x%llx", extent_id);
|
|
goto out;
|
|
}
|
|
}
|
|
ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
if (ret) {
|
|
apfs_err(sb, "insertion failed for id 0x%llx, addr 0x%llx", extent_id, extent->logical_addr);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = apfs_crypto_adj_refcnt(sb, new_crypto, 1);
|
|
if (ret)
|
|
apfs_err(sb, "failed to take crypto id 0x%llx", new_crypto);
|
|
|
|
out:
|
|
apfs_free_query(query);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_split_extent - Break an extent in two
|
|
* @query: query pointing to the extent
|
|
* @div: logical address for the division
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_split_extent(struct apfs_query *query, u64 div)
|
|
{
|
|
struct super_block *sb = query->node->object.sb;
|
|
struct apfs_file_extent_val *val1;
|
|
struct apfs_file_extent_key key2;
|
|
struct apfs_file_extent_val val2;
|
|
struct apfs_file_extent extent;
|
|
u64 len1, len2, blkcount1;
|
|
void *raw;
|
|
int err = 0;
|
|
|
|
err = apfs_query_join_transaction(query);
|
|
if (err) {
|
|
apfs_err(sb, "query join failed");
|
|
return err;
|
|
}
|
|
raw = query->node->object.data;
|
|
|
|
err = apfs_extent_from_query(query, &extent);
|
|
if (err) {
|
|
apfs_err(sb, "bad extent record");
|
|
return err;
|
|
}
|
|
val1 = raw + query->off;
|
|
val2 = *(struct apfs_file_extent_val *)(raw + query->off);
|
|
key2 = *(struct apfs_file_extent_key *)(raw + query->key_off);
|
|
|
|
len1 = div - extent.logical_addr;
|
|
blkcount1 = len1 >> sb->s_blocksize_bits;
|
|
len2 = extent.len - len1;
|
|
|
|
/* Modify the current extent in place to become the first half */
|
|
apfs_set_extent_length(val1, len1);
|
|
|
|
/* Insert the second half right after the first */
|
|
key2.logical_addr = cpu_to_le64(div);
|
|
if (!apfs_ext_is_hole(&extent))
|
|
val2.phys_block_num = cpu_to_le64(extent.phys_block_num + blkcount1);
|
|
apfs_set_extent_length(&val2, len2);
|
|
err = apfs_btree_insert(query, &key2, sizeof(key2), &val2, sizeof(val2));
|
|
if (err) {
|
|
apfs_err(sb, "insertion failed in division 0x%llx", div);
|
|
return err;
|
|
}
|
|
|
|
return apfs_crypto_adj_refcnt(sb, extent.crypto_id, 1);
|
|
}
|
|
|
|
/**
|
|
* apfs_update_mid_extent - Create or update a non-tail extent for a dstream
|
|
* @dstream: data stream info
|
|
* @extent: new in-memory extent
|
|
*
|
|
* Also takes care of any needed changes to the physical extent records. Returns
|
|
* 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_update_mid_extent(struct apfs_dstream_info *dstream, const struct apfs_file_extent *extent)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
|
struct apfs_key key;
|
|
struct apfs_query *query;
|
|
struct apfs_file_extent_key raw_key;
|
|
struct apfs_file_extent_val raw_val;
|
|
struct apfs_file_extent prev_ext;
|
|
u64 extent_id = dstream->ds_id;
|
|
u64 prev_crypto, new_crypto;
|
|
u64 prev_start, prev_end;
|
|
bool second_run = false;
|
|
int ret;
|
|
|
|
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);
|
|
if (apfs_vol_is_encrypted(sb))
|
|
new_crypto = extent_id;
|
|
else
|
|
new_crypto = 0;
|
|
raw_val.crypto_id = cpu_to_le64(new_crypto);
|
|
|
|
apfs_init_file_extent_key(extent_id, extent->logical_addr, &key);
|
|
|
|
search_and_insert:
|
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
|
if (!query)
|
|
return -ENOMEM;
|
|
query->key = key;
|
|
query->flags = APFS_QUERY_CAT;
|
|
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret && ret != -ENODATA) {
|
|
apfs_err(sb, "query failed for id 0x%llx, addr 0x%llx", extent_id, extent->logical_addr);
|
|
goto out;
|
|
}
|
|
|
|
if (ret == -ENODATA || !apfs_query_found_extent(query)) {
|
|
/*
|
|
* The new extent goes in a hole we just made, right at the
|
|
* beginning of the file.
|
|
*/
|
|
if (!second_run) {
|
|
apfs_err(sb, "missing extent on dstream 0x%llx", extent_id);
|
|
ret = -EFSCORRUPTED;
|
|
} else {
|
|
ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
if (ret)
|
|
apfs_err(sb, "insertion failed for id 0x%llx, addr 0x%llx", extent_id, extent->logical_addr);
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
if (apfs_extent_from_query(query, &prev_ext)) {
|
|
apfs_err(sb, "bad mid extent record on dstream 0x%llx", extent_id);
|
|
ret = -EFSCORRUPTED;
|
|
goto out;
|
|
}
|
|
prev_crypto = prev_ext.crypto_id;
|
|
prev_start = prev_ext.logical_addr;
|
|
prev_end = prev_ext.logical_addr + prev_ext.len;
|
|
|
|
if (prev_end == extent->logical_addr && second_run) {
|
|
/* The new extent goes in the hole we just made */
|
|
ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
if (ret) {
|
|
apfs_err(sb, "insertion failed for id 0x%llx, addr 0x%llx", extent_id, extent->logical_addr);
|
|
goto out;
|
|
}
|
|
} else if (prev_start == extent->logical_addr && prev_ext.len == extent->len) {
|
|
/* The old and new extents are the same logical block */
|
|
ret = apfs_btree_replace(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
if (ret) {
|
|
apfs_err(sb, "update failed for id 0x%llx, addr 0x%llx", extent_id, extent->logical_addr);
|
|
goto out;
|
|
}
|
|
if (apfs_ext_is_hole(&prev_ext)) {
|
|
dstream->ds_sparse_bytes -= prev_ext.len;
|
|
} else if (prev_ext.phys_block_num != extent->phys_block_num) {
|
|
ret = apfs_range_put_reference(sb, prev_ext.phys_block_num, prev_ext.len);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to put range 0x%llx-0x%llx", prev_ext.phys_block_num, prev_ext.len);
|
|
goto out;
|
|
}
|
|
}
|
|
ret = apfs_crypto_adj_refcnt(sb, prev_crypto, -1);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to put crypto id 0x%llx", prev_crypto);
|
|
goto out;
|
|
}
|
|
} else if (prev_start == extent->logical_addr) {
|
|
/* The new extent is the first logical block of the old one */
|
|
if (second_run) {
|
|
/* I don't know if this is possible, but be safe */
|
|
apfs_alert(sb, "recursion shrinking extent head for dstream 0x%llx", extent_id);
|
|
ret = -EFSCORRUPTED;
|
|
goto out;
|
|
}
|
|
ret = apfs_shrink_extent_head(query, dstream, extent->logical_addr + extent->len);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to shrink extent in dstream 0x%llx", extent_id);
|
|
goto out;
|
|
}
|
|
/* The query should point to the previous record, start again */
|
|
apfs_free_query(query);
|
|
second_run = true;
|
|
goto search_and_insert;
|
|
} else if (prev_end == extent->logical_addr + extent->len) {
|
|
/* The new extent is the last logical block of the old one */
|
|
ret = apfs_shrink_extent_tail(query, dstream, extent->logical_addr);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to shrink extent in dstream 0x%llx", extent_id);
|
|
goto out;
|
|
}
|
|
ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
if (ret) {
|
|
apfs_err(sb, "insertion failed for id 0x%llx, addr 0x%llx", extent_id, extent->logical_addr);
|
|
goto out;
|
|
}
|
|
} else if (prev_start < extent->logical_addr && prev_end > extent->logical_addr + extent->len) {
|
|
/* The new extent is logically in the middle of the old one */
|
|
if (second_run) {
|
|
/* I don't know if this is possible, but be safe */
|
|
apfs_alert(sb, "recursion when splitting extents for dstream 0x%llx", extent_id);
|
|
ret = -EFSCORRUPTED;
|
|
goto out;
|
|
}
|
|
ret = apfs_split_extent(query, extent->logical_addr + extent->len);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to split extent in dstream 0x%llx", extent_id);
|
|
goto out;
|
|
}
|
|
/* The split may make the query invalid */
|
|
apfs_free_query(query);
|
|
second_run = true;
|
|
goto search_and_insert;
|
|
} else {
|
|
/* I don't know what this is, be safe */
|
|
apfs_alert(sb, "strange extents for dstream 0x%llx", extent_id);
|
|
ret = -EFSCORRUPTED;
|
|
goto out;
|
|
}
|
|
|
|
ret = apfs_crypto_adj_refcnt(sb, new_crypto, 1);
|
|
if (ret)
|
|
apfs_err(sb, "failed to take crypto id 0x%llx", new_crypto);
|
|
|
|
out:
|
|
apfs_free_query(query);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_update_extent - Create or update the extent record for an extent
|
|
* @dstream: data stream info
|
|
* @extent: new in-memory file extent
|
|
*
|
|
* The @extent must either be a new tail for the dstream, or a single block.
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_update_extent(struct apfs_dstream_info *dstream, const struct apfs_file_extent *extent)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
|
|
if (extent->logical_addr + extent->len >= dstream->ds_size)
|
|
return apfs_update_tail_extent(dstream, extent);
|
|
if (extent->len > sb->s_blocksize) {
|
|
apfs_err(sb, "can't create mid extents of length 0x%llx", extent->len);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
return apfs_update_mid_extent(dstream, extent);
|
|
}
|
|
|
|
static int apfs_extend_phys_extent(struct apfs_query *query, u64 bno, u64 blkcnt, u64 dstream_id)
|
|
{
|
|
struct apfs_phys_ext_key raw_key;
|
|
struct apfs_phys_ext_val raw_val;
|
|
u64 kind = (u64)APFS_KIND_NEW << APFS_PEXT_KIND_SHIFT;
|
|
|
|
apfs_key_set_hdr(APFS_TYPE_EXTENT, bno, &raw_key);
|
|
raw_val.len_and_kind = cpu_to_le64(kind | blkcnt);
|
|
raw_val.owning_obj_id = cpu_to_le64(dstream_id);
|
|
raw_val.refcnt = cpu_to_le32(1);
|
|
return apfs_btree_replace(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
}
|
|
|
|
static int apfs_insert_new_phys_extent(struct apfs_query *query, u64 bno, u64 blkcnt, u64 dstream_id)
|
|
{
|
|
struct apfs_phys_ext_key raw_key;
|
|
struct apfs_phys_ext_val raw_val;
|
|
u64 kind = (u64)APFS_KIND_NEW << APFS_PEXT_KIND_SHIFT;
|
|
|
|
apfs_key_set_hdr(APFS_TYPE_EXTENT, bno, &raw_key);
|
|
raw_val.len_and_kind = cpu_to_le64(kind | blkcnt);
|
|
raw_val.owning_obj_id = cpu_to_le64(dstream_id);
|
|
raw_val.refcnt = cpu_to_le32(1);
|
|
return apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
}
|
|
|
|
static int apfs_phys_ext_from_query(struct apfs_query *query, struct apfs_phys_extent *pext);
|
|
|
|
/**
|
|
* apfs_insert_phys_extent - Create or grow the physical record for an extent
|
|
* @dstream: data stream info for the extent
|
|
* @extent: new in-memory file extent
|
|
*
|
|
* Only works for appending to extents, for now. TODO: reference counting.
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_insert_phys_extent(struct apfs_dstream_info *dstream, const struct apfs_file_extent *extent)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
|
struct apfs_node *extref_root;
|
|
struct apfs_query *query = NULL;
|
|
struct apfs_phys_extent pext;
|
|
u64 blkcnt = extent->len >> sb->s_blocksize_bits;
|
|
u64 last_bno, new_base, new_blkcnt;
|
|
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)) {
|
|
apfs_err(sb, "failed to read extref root 0x%llx", le64_to_cpu(vsb_raw->apfs_extentref_tree_oid));
|
|
return PTR_ERR(extref_root);
|
|
}
|
|
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
|
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 out;
|
|
}
|
|
|
|
/*
|
|
* The cached logical extent may have been split into multiple physical
|
|
* extents because of clones. If that happens, we want to grow the last
|
|
* one.
|
|
*/
|
|
last_bno = extent->phys_block_num + blkcnt - 1;
|
|
apfs_init_extent_key(last_bno, &query->key);
|
|
query->flags = APFS_QUERY_EXTENTREF;
|
|
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret && ret != -ENODATA) {
|
|
apfs_err(sb, "query failed for paddr 0x%llx", last_bno);
|
|
goto out;
|
|
}
|
|
|
|
if (ret == -ENODATA) {
|
|
/* This is a fresh new physical extent */
|
|
ret = apfs_insert_new_phys_extent(query, extent->phys_block_num, blkcnt, dstream->ds_id);
|
|
if (ret)
|
|
apfs_err(sb, "insertion failed for paddr 0x%llx", extent->phys_block_num);
|
|
goto out;
|
|
}
|
|
|
|
ret = apfs_phys_ext_from_query(query, &pext);
|
|
if (ret) {
|
|
apfs_err(sb, "bad pext record for bno 0x%llx", last_bno);
|
|
goto out;
|
|
}
|
|
if (pext.bno + pext.blkcount <= extent->phys_block_num) {
|
|
/* Also a fresh new physical extent */
|
|
ret = apfs_insert_new_phys_extent(query, extent->phys_block_num, blkcnt, dstream->ds_id);
|
|
if (ret)
|
|
apfs_err(sb, "insertion failed for paddr 0x%llx", extent->phys_block_num);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* There is an existing physical extent that overlaps the new one. The
|
|
* cache was dirty, so the existing extent can't cover the whole tail.
|
|
*/
|
|
if (pext.bno + pext.blkcount >= extent->phys_block_num + blkcnt) {
|
|
apfs_err(sb, "dirty cache tail covered by existing physical extent 0x%llx-0x%llx", pext.bno, pext.blkcount);
|
|
ret = -EFSCORRUPTED;
|
|
goto out;
|
|
}
|
|
if (pext.refcnt == 1) {
|
|
new_base = pext.bno;
|
|
new_blkcnt = extent->phys_block_num + blkcnt - new_base;
|
|
ret = apfs_extend_phys_extent(query, new_base, new_blkcnt, dstream->ds_id);
|
|
if (ret)
|
|
apfs_err(sb, "update failed for paddr 0x%llx", new_base);
|
|
} else {
|
|
/*
|
|
* We can't extend this one, because it would extend the other
|
|
* references as well.
|
|
*/
|
|
new_base = pext.bno + pext.blkcount;
|
|
new_blkcnt = extent->phys_block_num + blkcnt - new_base;
|
|
ret = apfs_insert_new_phys_extent(query, new_base, new_blkcnt, dstream->ds_id);
|
|
if (ret)
|
|
apfs_err(sb, "insertion failed for paddr 0x%llx", new_base);
|
|
}
|
|
|
|
out:
|
|
apfs_free_query(query);
|
|
apfs_node_free(extref_root);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_phys_ext_from_query - Read the physical extent record found by a query
|
|
* @query: the (successful) query that found the record
|
|
* @pext: on return, the physical extent read
|
|
*
|
|
* Reads the physical extent record into @pext and performs some basic sanity
|
|
* checks as a protection against crafted filesystems. Returns 0 on success or
|
|
* -EFSCORRUPTED otherwise.
|
|
*/
|
|
static int apfs_phys_ext_from_query(struct apfs_query *query, struct apfs_phys_extent *pext)
|
|
{
|
|
struct super_block *sb = query->node->object.sb;
|
|
struct apfs_phys_ext_key *key;
|
|
struct apfs_phys_ext_val *val;
|
|
char *raw = query->node->object.data;
|
|
|
|
if (query->len != sizeof(*val) || query->key_len != sizeof(*key)) {
|
|
apfs_err(sb, "bad length of key (%d) or value (%d)", query->key_len, query->len);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
key = (struct apfs_phys_ext_key *)(raw + query->key_off);
|
|
val = (struct apfs_phys_ext_val *)(raw + query->off);
|
|
|
|
pext->bno = apfs_cat_cnid(&key->hdr);
|
|
pext->blkcount = le64_to_cpu(val->len_and_kind) & APFS_PEXT_LEN_MASK;
|
|
pext->len = pext->blkcount << sb->s_blocksize_bits;
|
|
pext->refcnt = le32_to_cpu(val->refcnt);
|
|
pext->kind = le64_to_cpu(val->len_and_kind) >> APFS_PEXT_KIND_SHIFT;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_free_phys_ext - Add all blocks in a physical extent to the free queue
|
|
* @sb: superblock structure
|
|
* @pext: physical range to free
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_free_phys_ext(struct super_block *sb, struct apfs_phys_extent *pext)
|
|
{
|
|
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
|
|
|
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
|
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, -pext->blkcount);
|
|
le64_add_cpu(&vsb_raw->apfs_total_blocks_freed, pext->blkcount);
|
|
|
|
return apfs_free_queue_insert(sb, pext->bno, pext->blkcount);
|
|
}
|
|
|
|
/**
|
|
* apfs_put_phys_extent - Reduce the reference count for a physical extent
|
|
* @pext: physical extent data, already read
|
|
* @query: query that found the extent
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_put_phys_extent(struct apfs_phys_extent *pext, struct apfs_query *query)
|
|
{
|
|
struct super_block *sb = query->node->object.sb;
|
|
struct apfs_phys_ext_val *val;
|
|
void *raw;
|
|
int err;
|
|
|
|
if (--pext->refcnt == 0) {
|
|
err = apfs_btree_remove(query);
|
|
if (err) {
|
|
apfs_err(sb, "removal failed for paddr 0x%llx", pext->bno);
|
|
return err;
|
|
}
|
|
return pext->kind == APFS_KIND_NEW ? apfs_free_phys_ext(sb, pext) : 0;
|
|
}
|
|
|
|
err = apfs_query_join_transaction(query);
|
|
if (err) {
|
|
apfs_err(sb, "query join failed");
|
|
return err;
|
|
}
|
|
raw = query->node->object.data;
|
|
val = raw + query->off;
|
|
val->refcnt = cpu_to_le32(pext->refcnt);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_take_phys_extent - Increase the reference count for a physical extent
|
|
* @pext: physical extent data, already read
|
|
* @query: query that found the extent
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_take_phys_extent(struct apfs_phys_extent *pext, struct apfs_query *query)
|
|
{
|
|
struct super_block *sb = query->node->object.sb;
|
|
struct apfs_phys_ext_val *val;
|
|
void *raw;
|
|
int err;
|
|
|
|
/* An update extent may be dropped when a reference is taken */
|
|
if (++pext->refcnt == 0)
|
|
return apfs_btree_remove(query);
|
|
|
|
err = apfs_query_join_transaction(query);
|
|
if (err) {
|
|
apfs_err(sb, "query join failed");
|
|
return err;
|
|
}
|
|
raw = query->node->object.data;
|
|
val = raw + query->off;
|
|
val->refcnt = cpu_to_le32(pext->refcnt);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_set_phys_ext_length - Set new length in a physical extent record's value
|
|
* @pext: the physical extent record's value
|
|
* @len: the new length (in blocks)
|
|
*
|
|
* Preserves the kind, though I doubt that's the right thing to do in general.
|
|
*/
|
|
static inline void apfs_set_phys_ext_length(struct apfs_phys_ext_val *pext, u64 len)
|
|
{
|
|
u64 len_and_kind = le64_to_cpu(pext->len_and_kind);
|
|
u64 kind = len_and_kind & APFS_PEXT_KIND_MASK;
|
|
|
|
pext->len_and_kind = cpu_to_le64(kind | len);
|
|
}
|
|
|
|
/**
|
|
* apfs_split_phys_ext - Break a physical extent in two
|
|
* @query: query pointing to the extent
|
|
* @div: first physical block number to come after the division
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_split_phys_ext(struct apfs_query *query, u64 div)
|
|
{
|
|
struct super_block *sb = query->node->object.sb;
|
|
struct apfs_phys_ext_val *val1;
|
|
struct apfs_phys_ext_key key2;
|
|
struct apfs_phys_ext_val val2;
|
|
struct apfs_phys_extent pextent;
|
|
u64 blkcount1, blkcount2;
|
|
void *raw;
|
|
int err = 0;
|
|
|
|
err = apfs_query_join_transaction(query);
|
|
if (err) {
|
|
apfs_err(sb, "query join failed");
|
|
return err;
|
|
}
|
|
raw = query->node->object.data;
|
|
|
|
err = apfs_phys_ext_from_query(query, &pextent);
|
|
if (err) {
|
|
apfs_err(sb, "bad pext record over div 0x%llx", div);
|
|
return err;
|
|
}
|
|
val1 = raw + query->off;
|
|
val2 = *(struct apfs_phys_ext_val *)(raw + query->off);
|
|
key2 = *(struct apfs_phys_ext_key *)(raw + query->key_off);
|
|
|
|
blkcount1 = div - pextent.bno;
|
|
blkcount2 = pextent.blkcount - blkcount1;
|
|
|
|
/* Modify the current extent in place to become the first half */
|
|
apfs_set_phys_ext_length(val1, blkcount1);
|
|
|
|
/* Insert the second half right after the first */
|
|
apfs_key_set_hdr(APFS_TYPE_EXTENT, div, &key2);
|
|
apfs_set_phys_ext_length(&val2, blkcount2);
|
|
return apfs_btree_insert(query, &key2, sizeof(key2), &val2, sizeof(val2));
|
|
}
|
|
|
|
/**
|
|
* apfs_create_update_pext - Create a reference update physical extent record
|
|
* @query: query that searched for the physical extent
|
|
* @extent: range of physical blocks to update
|
|
* @diff: reference count change
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_create_update_pext(struct apfs_query *query, const struct apfs_file_extent *extent, u32 diff)
|
|
{
|
|
struct super_block *sb = query->node->object.sb;
|
|
struct apfs_phys_ext_key key = {0};
|
|
struct apfs_phys_ext_val val = {0};
|
|
|
|
apfs_key_set_hdr(APFS_TYPE_EXTENT, extent->phys_block_num, &key);
|
|
val.len_and_kind = cpu_to_le64((u64)APFS_KIND_UPDATE << APFS_PEXT_KIND_SHIFT | extent->len >> sb->s_blocksize_bits);
|
|
val.owning_obj_id = cpu_to_le64(APFS_OWNING_OBJ_ID_INVALID);
|
|
val.refcnt = cpu_to_le32(diff);
|
|
return apfs_btree_insert(query, &key, sizeof(key), &val, sizeof(val));
|
|
}
|
|
|
|
/**
|
|
* apfs_dstream_cache_is_tail - Is the tail of this dstream in its extent cache?
|
|
* @dstream: dstream to check
|
|
*/
|
|
static inline bool apfs_dstream_cache_is_tail(struct apfs_dstream_info *dstream)
|
|
{
|
|
struct apfs_file_extent *cache = &dstream->ds_cached_ext;
|
|
|
|
/* nx_big_sem provides the locking for the cache here */
|
|
return cache->len && (dstream->ds_size <= cache->logical_addr + cache->len);
|
|
}
|
|
|
|
/**
|
|
* apfs_flush_extent_cache - Write the cached extent to the catalog, if dirty
|
|
* @dstream: data stream to flush
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
int apfs_flush_extent_cache(struct apfs_dstream_info *dstream)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_file_extent *ext = &dstream->ds_cached_ext;
|
|
int err;
|
|
|
|
if (!dstream->ds_ext_dirty)
|
|
return 0;
|
|
ASSERT(ext->len > 0);
|
|
|
|
err = apfs_update_extent(dstream, ext);
|
|
if (err) {
|
|
apfs_err(sb, "extent update failed");
|
|
return err;
|
|
}
|
|
err = apfs_insert_phys_extent(dstream, ext);
|
|
if (err) {
|
|
apfs_err(sb, "pext insertion failed");
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* TODO: keep track of the byte and block count through the use of
|
|
* inode_add_bytes() and inode_set_bytes(). This hasn't been done with
|
|
* care in the rest of the module and it doesn't seem to matter beyond
|
|
* stat(), so I'm ignoring it for now.
|
|
*/
|
|
|
|
dstream->ds_ext_dirty = false;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_create_hole - Create and insert a hole extent for the dstream
|
|
* @dstream: data stream info
|
|
* @start: first logical block number for the hole
|
|
* @end: first logical block number right after the hole
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
* TODO: what happens to the crypto refcount?
|
|
*/
|
|
static int apfs_create_hole(struct apfs_dstream_info *dstream, u64 start, u64 end)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
|
struct apfs_query *query;
|
|
struct apfs_file_extent_key raw_key;
|
|
struct apfs_file_extent_val raw_val;
|
|
u64 extent_id = dstream->ds_id;
|
|
int ret;
|
|
|
|
if (start == end)
|
|
return 0;
|
|
|
|
/* File extent records use addresses, not block numbers */
|
|
start <<= sb->s_blocksize_bits;
|
|
end <<= sb->s_blocksize_bits;
|
|
|
|
apfs_key_set_hdr(APFS_TYPE_FILE_EXTENT, extent_id, &raw_key);
|
|
raw_key.logical_addr = cpu_to_le64(start);
|
|
raw_val.len_and_flags = cpu_to_le64(end - start);
|
|
raw_val.phys_block_num = cpu_to_le64(0); /* It's a hole... */
|
|
raw_val.crypto_id = cpu_to_le64(apfs_vol_is_encrypted(sb) ? extent_id : 0);
|
|
|
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
|
if (!query)
|
|
return -ENOMEM;
|
|
apfs_init_file_extent_key(extent_id, start, &query->key);
|
|
query->flags = APFS_QUERY_CAT;
|
|
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret && ret != -ENODATA) {
|
|
apfs_err(sb, "query failed for id 0x%llx, addr 0x%llx", extent_id, start);
|
|
goto out;
|
|
}
|
|
|
|
ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
if (ret)
|
|
apfs_err(sb, "insertion failed for id 0x%llx, addr 0x%llx", extent_id, start);
|
|
dstream->ds_sparse_bytes += end - start;
|
|
|
|
out:
|
|
apfs_free_query(query);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_zero_dstream_tail - Zero out stale bytes in a data stream's last block
|
|
* @dstream: data stream info
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_zero_dstream_tail(struct apfs_dstream_info *dstream)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct inode *inode = NULL;
|
|
struct page *page = NULL;
|
|
void *fsdata = NULL;
|
|
int valid_length;
|
|
int err;
|
|
|
|
/* No stale bytes if no actual content */
|
|
if (dstream->ds_size <= dstream->ds_sparse_bytes)
|
|
return 0;
|
|
|
|
/* No stale tail if the last block is fully used */
|
|
valid_length = dstream->ds_size & (sb->s_blocksize - 1);
|
|
if (valid_length == 0)
|
|
return 0;
|
|
|
|
inode = dstream->ds_inode;
|
|
if (!inode) {
|
|
/* This should never happen, but be safe */
|
|
apfs_alert(sb, "attempt to zero the tail of xattr dstream 0x%llx", dstream->ds_id);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
/* This will take care of the CoW and zeroing */
|
|
err = __apfs_write_begin(NULL, inode->i_mapping, inode->i_size, 0, 0, &page, &fsdata);
|
|
if (err)
|
|
return err;
|
|
return __apfs_write_end(NULL, inode->i_mapping, inode->i_size, 0, 0, page, fsdata);
|
|
}
|
|
|
|
/**
|
|
* apfs_zero_bh_tail - Zero out stale bytes in a buffer head
|
|
* @sb: filesystem superblock
|
|
* @bh: buffer head to zero
|
|
* @length: length of valid bytes to be left alone
|
|
*/
|
|
static void apfs_zero_bh_tail(struct super_block *sb, struct buffer_head *bh, u64 length)
|
|
{
|
|
ASSERT(buffer_trans(bh));
|
|
if (length < sb->s_blocksize)
|
|
memset(bh->b_data + length, 0, sb->s_blocksize - length);
|
|
}
|
|
|
|
/**
|
|
* apfs_range_in_snap - Check if a given block range overlaps a snapshot
|
|
* @sb: filesystem superblock
|
|
* @bno: first block in the range
|
|
* @blkcnt: block count for the range
|
|
* @in_snap: on return, the result
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_range_in_snap(struct super_block *sb, u64 bno, u64 blkcnt, bool *in_snap)
|
|
{
|
|
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
|
struct apfs_node *extref_root = NULL;
|
|
struct apfs_query *query = NULL;
|
|
struct apfs_phys_extent pext = {0};
|
|
int ret;
|
|
|
|
/* Avoid the tree queries when we don't even have snapshots */
|
|
if (vsb_raw->apfs_num_snapshots == 0) {
|
|
*in_snap = false;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Now check if the current physical extent tree has an entry for
|
|
* these blocks
|
|
*/
|
|
extref_root = apfs_read_node(sb, le64_to_cpu(vsb_raw->apfs_extentref_tree_oid), APFS_OBJ_PHYSICAL, false /* write */);
|
|
if (IS_ERR(extref_root)) {
|
|
apfs_err(sb, "failed to read extref root 0x%llx", le64_to_cpu(vsb_raw->apfs_extentref_tree_oid));
|
|
return PTR_ERR(extref_root);
|
|
}
|
|
|
|
query = apfs_alloc_query(extref_root, NULL /* parent */);
|
|
if (!query) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
apfs_init_extent_key(bno, &query->key);
|
|
query->flags = APFS_QUERY_EXTENTREF;
|
|
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret && ret != -ENODATA) {
|
|
apfs_err(sb, "query failed for paddr 0x%llx", bno);
|
|
goto out;
|
|
}
|
|
if (ret == -ENODATA) {
|
|
*in_snap = false;
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
ret = apfs_phys_ext_from_query(query, &pext);
|
|
if (ret) {
|
|
apfs_err(sb, "bad pext record for paddr 0x%llx", bno);
|
|
goto out;
|
|
}
|
|
|
|
if (pext.bno <= bno && pext.bno + pext.blkcount >= bno + blkcnt) {
|
|
if (pext.kind == APFS_KIND_NEW) {
|
|
*in_snap = false;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* I think the file extent could still be covered by two different
|
|
* physical extents from the current tree, but it's easier to just
|
|
* assume the worst here.
|
|
*/
|
|
*in_snap = true;
|
|
|
|
out:
|
|
apfs_free_query(query);
|
|
apfs_node_free(extref_root);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_dstream_cache_in_snap - Check if the cached extent overlaps a snapshot
|
|
* @dstream: the data stream to check
|
|
* @in_snap: on return, the result
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_dstream_cache_in_snap(struct apfs_dstream_info *dstream, bool *in_snap)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_file_extent *cache = NULL;
|
|
|
|
/* All changes to extents get flushed when a snaphot is created */
|
|
if (dstream->ds_ext_dirty) {
|
|
*in_snap = false;
|
|
return 0;
|
|
}
|
|
|
|
cache = &dstream->ds_cached_ext;
|
|
return apfs_range_in_snap(sb, cache->phys_block_num, cache->len >> sb->s_blocksize_bits, in_snap);
|
|
}
|
|
|
|
/**
|
|
* apfs_dstream_get_new_block - Like the get_block_t function, but for dstreams
|
|
* @dstream: data stream info
|
|
* @dsblock: logical dstream block to map
|
|
* @bh_result: buffer head to map (NULL if none)
|
|
* @bno: if not NULL, the new block number is returned here
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_dstream_get_new_block(struct apfs_dstream_info *dstream, u64 dsblock, struct buffer_head *bh_result, u64 *bno)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
|
struct apfs_file_extent *cache = NULL;
|
|
u64 phys_bno, logical_addr, cache_blks, dstream_blks;
|
|
bool in_snap = true;
|
|
int err;
|
|
|
|
/* TODO: preallocate tail blocks */
|
|
logical_addr = dsblock << sb->s_blocksize_bits;
|
|
|
|
err = apfs_spaceman_allocate_block(sb, &phys_bno, false /* backwards */);
|
|
if (err) {
|
|
apfs_err(sb, "block allocation failed");
|
|
return err;
|
|
}
|
|
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
|
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, 1);
|
|
le64_add_cpu(&vsb_raw->apfs_total_blocks_alloced, 1);
|
|
if (bno)
|
|
*bno = phys_bno;
|
|
|
|
if (bh_result) {
|
|
apfs_map_bh(bh_result, sb, phys_bno);
|
|
err = apfs_transaction_join(sb, bh_result);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!buffer_uptodate(bh_result)) {
|
|
/*
|
|
* Truly new buffers need to be marked as such, to get
|
|
* zeroed; this also takes care of holes in sparse files
|
|
*/
|
|
set_buffer_new(bh_result);
|
|
} else if (dstream->ds_size > logical_addr) {
|
|
/*
|
|
* The last block may have stale data left from a
|
|
* truncation
|
|
*/
|
|
apfs_zero_bh_tail(sb, bh_result, dstream->ds_size - logical_addr);
|
|
}
|
|
}
|
|
|
|
dstream_blks = apfs_size_to_blocks(sb, dstream->ds_size);
|
|
if (dstream_blks < dsblock) {
|
|
/*
|
|
* This recurses into apfs_dstream_get_new_block() and dirties
|
|
* the extent cache, so it must happen before flushing it.
|
|
*/
|
|
err = apfs_zero_dstream_tail(dstream);
|
|
if (err) {
|
|
apfs_err(sb, "failed to zero tail for dstream 0x%llx", dstream->ds_id);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = apfs_dstream_cache_in_snap(dstream, &in_snap);
|
|
if (err)
|
|
return err;
|
|
|
|
cache = &dstream->ds_cached_ext;
|
|
cache_blks = apfs_size_to_blocks(sb, cache->len);
|
|
|
|
/* TODO: allow dirty caches of several blocks in the middle of a file */
|
|
if (!in_snap && apfs_dstream_cache_is_tail(dstream) &&
|
|
logical_addr == cache->logical_addr + cache->len &&
|
|
phys_bno == cache->phys_block_num + cache_blks) {
|
|
cache->len += sb->s_blocksize;
|
|
dstream->ds_ext_dirty = true;
|
|
return 0;
|
|
}
|
|
|
|
err = apfs_flush_extent_cache(dstream);
|
|
if (err) {
|
|
apfs_err(sb, "extent cache flush failed for dstream 0x%llx", dstream->ds_id);
|
|
return err;
|
|
}
|
|
|
|
if (dstream_blks < dsblock) {
|
|
/*
|
|
* This puts new extents after the reported end of the file, so
|
|
* it must happen after the flush to avoid conflict with those
|
|
* extent operations.
|
|
*/
|
|
err = apfs_create_hole(dstream, dstream_blks, dsblock);
|
|
if (err) {
|
|
apfs_err(sb, "hole creation failed for dstream 0x%llx", dstream->ds_id);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
cache->logical_addr = logical_addr;
|
|
cache->phys_block_num = phys_bno;
|
|
cache->len = sb->s_blocksize;
|
|
dstream->ds_ext_dirty = true;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_dstream_get_new_bno - Allocate a new block inside a dstream
|
|
* @dstream: data stream info
|
|
* @dsblock: logical dstream block to allocate
|
|
* @bno: on return, the new block number
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
int apfs_dstream_get_new_bno(struct apfs_dstream_info *dstream, u64 dsblock, u64 *bno)
|
|
{
|
|
return apfs_dstream_get_new_block(dstream, dsblock, NULL /* bh_result */, bno);
|
|
}
|
|
|
|
int apfs_get_new_block(struct inode *inode, sector_t iblock,
|
|
struct buffer_head *bh_result, int create)
|
|
{
|
|
struct apfs_inode_info *ai = APFS_I(inode);
|
|
|
|
ASSERT(create);
|
|
return apfs_dstream_get_new_block(&ai->i_dstream, iblock, bh_result, NULL /* bno */);
|
|
}
|
|
|
|
/**
|
|
* apfs_shrink_dstream_last_extent - Shrink last extent of dstream being resized
|
|
* @dstream: data stream info
|
|
* @new_size: new size for the whole data stream
|
|
*
|
|
* Deletes, shrinks or zeroes the last extent, as needed for the truncation of
|
|
* the data stream.
|
|
*
|
|
* Only works with the last extent, so it needs to be called repeatedly to
|
|
* complete the truncation. Returns -EAGAIN in that case, or 0 when the process
|
|
* is complete. Returns other negative error codes in case of failure.
|
|
*/
|
|
static int apfs_shrink_dstream_last_extent(struct apfs_dstream_info *dstream, loff_t new_size)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
|
struct apfs_query *query;
|
|
struct apfs_file_extent tail;
|
|
u64 extent_id = dstream->ds_id;
|
|
int ret = 0;
|
|
|
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
|
if (!query)
|
|
return -ENOMEM;
|
|
apfs_init_file_extent_key(extent_id, -1, &query->key);
|
|
query->flags = APFS_QUERY_CAT;
|
|
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret && ret != -ENODATA) {
|
|
apfs_err(sb, "query failed for last extent of id 0x%llx", extent_id);
|
|
goto out;
|
|
}
|
|
|
|
if (!apfs_query_found_extent(query)) {
|
|
/* No more extents, we deleted the whole file already? */
|
|
if (new_size) {
|
|
apfs_err(sb, "missing extent for dstream 0x%llx", extent_id);
|
|
ret = -EFSCORRUPTED;
|
|
} else {
|
|
ret = 0;
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
ret = apfs_extent_from_query(query, &tail);
|
|
if (ret) {
|
|
apfs_err(sb, "bad tail extent record on dstream 0x%llx", extent_id);
|
|
goto out;
|
|
}
|
|
|
|
if (tail.logical_addr + tail.len < new_size) {
|
|
apfs_err(sb, "missing extent for dstream 0x%llx", extent_id);
|
|
ret = -EFSCORRUPTED; /* Tail extent missing */
|
|
} else if (tail.logical_addr + tail.len == new_size) {
|
|
ret = 0; /* Nothing more to be done */
|
|
} else if (tail.logical_addr >= new_size) {
|
|
/* This whole extent needs to go */
|
|
ret = apfs_btree_remove(query);
|
|
if (ret) {
|
|
apfs_err(sb, "removal failed for id 0x%llx, addr 0x%llx", dstream->ds_id, tail.logical_addr);
|
|
goto out;
|
|
}
|
|
if (apfs_ext_is_hole(&tail)) {
|
|
dstream->ds_sparse_bytes -= tail.len;
|
|
} else {
|
|
ret = apfs_range_put_reference(sb, tail.phys_block_num, tail.len);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to put range 0x%llx-0x%llx", tail.phys_block_num, tail.len);
|
|
goto out;
|
|
}
|
|
}
|
|
ret = apfs_crypto_adj_refcnt(sb, tail.crypto_id, -1);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to take crypto id 0x%llx", tail.crypto_id);
|
|
goto out;
|
|
}
|
|
ret = tail.logical_addr == new_size ? 0 : -EAGAIN;
|
|
} else {
|
|
/*
|
|
* The file is being truncated in the middle of this extent.
|
|
* TODO: preserve the physical tail to be overwritten later.
|
|
*/
|
|
new_size = apfs_size_to_blocks(sb, new_size) << sb->s_blocksize_bits;
|
|
ret = apfs_shrink_extent_tail(query, dstream, new_size);
|
|
if (ret)
|
|
apfs_err(sb, "failed to shrink tail of dstream 0x%llx", extent_id);
|
|
}
|
|
|
|
out:
|
|
apfs_free_query(query);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_shrink_dstream - Shrink a data stream's extents to a new length
|
|
* @dstream: data stream info
|
|
* @new_size: the new size
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_shrink_dstream(struct apfs_dstream_info *dstream, loff_t new_size)
|
|
{
|
|
int ret;
|
|
|
|
do {
|
|
ret = apfs_shrink_dstream_last_extent(dstream, new_size);
|
|
} while (ret == -EAGAIN);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_truncate - Truncate a data stream's content
|
|
* @dstream: data stream info
|
|
* @new_size: the new size
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
u64 old_blks, new_blks;
|
|
struct apfs_file_extent *cache = &dstream->ds_cached_ext;
|
|
int err;
|
|
|
|
/* TODO: don't write the cached extent if it will be deleted */
|
|
err = apfs_flush_extent_cache(dstream);
|
|
if (err) {
|
|
apfs_err(sb, "extent cache flush failed for dstream 0x%llx", dstream->ds_id);
|
|
return err;
|
|
}
|
|
dstream->ds_ext_dirty = false;
|
|
|
|
/* TODO: keep the cache valid on truncation */
|
|
cache->len = 0;
|
|
|
|
/* "<=", because a partial write may have left extents beyond the end */
|
|
if (new_size <= dstream->ds_size)
|
|
return apfs_shrink_dstream(dstream, new_size);
|
|
|
|
err = apfs_zero_dstream_tail(dstream);
|
|
if (err) {
|
|
apfs_err(sb, "failed to zero tail for dstream 0x%llx", dstream->ds_id);
|
|
return err;
|
|
}
|
|
new_blks = apfs_size_to_blocks(sb, new_size);
|
|
old_blks = apfs_size_to_blocks(sb, dstream->ds_size);
|
|
return apfs_create_hole(dstream, old_blks, new_blks);
|
|
}
|
|
|
|
/**
|
|
* apfs_dstream_delete_front - Deletes as many leading extents as possible
|
|
* @sb: filesystem superblock
|
|
* @ds_id: id for the dstream to delete
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure, which may
|
|
* be -ENODATA if there are no more extents, or -EAGAIN if the free queue is
|
|
* getting too full.
|
|
*/
|
|
static int apfs_dstream_delete_front(struct super_block *sb, u64 ds_id)
|
|
{
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
struct apfs_spaceman_free_queue *fq = NULL;
|
|
struct apfs_query *query = NULL;
|
|
struct apfs_file_extent head;
|
|
bool first_match = true;
|
|
int ret;
|
|
|
|
fq = &sm_raw->sm_fq[APFS_SFQ_MAIN];
|
|
if (le64_to_cpu(fq->sfq_count) > APFS_TRANS_MAIN_QUEUE_MAX)
|
|
return -EAGAIN;
|
|
|
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
|
if (!query)
|
|
return -ENOMEM;
|
|
apfs_init_file_extent_key(ds_id, 0, &query->key);
|
|
query->flags = APFS_QUERY_CAT;
|
|
|
|
next_extent:
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret && ret != -ENODATA) {
|
|
apfs_err(sb, "query failed for first extent of id 0x%llx", ds_id);
|
|
goto out;
|
|
}
|
|
apfs_query_direct_forward(query);
|
|
if (!apfs_query_found_extent(query)) {
|
|
/*
|
|
* After the original lookup, the query may not be set to the
|
|
* first extent, but instead to the record that comes right
|
|
* before.
|
|
*/
|
|
if (first_match) {
|
|
first_match = false;
|
|
goto next_extent;
|
|
}
|
|
ret = -ENODATA;
|
|
goto out;
|
|
}
|
|
first_match = false;
|
|
|
|
ret = apfs_extent_from_query(query, &head);
|
|
if (ret) {
|
|
apfs_err(sb, "bad head extent record on dstream 0x%llx", ds_id);
|
|
goto out;
|
|
}
|
|
ret = apfs_btree_remove(query);
|
|
if (ret) {
|
|
apfs_err(sb, "removal failed for id 0x%llx, addr 0x%llx", ds_id, head.logical_addr);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* The official fsck doesn't complain about wrong sparse byte counts
|
|
* for orphans, so I guess we don't need to update them here
|
|
*/
|
|
if (!apfs_ext_is_hole(&head)) {
|
|
ret = apfs_range_put_reference(sb, head.phys_block_num, head.len);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to put range 0x%llx-0x%llx", head.phys_block_num, head.len);
|
|
goto out;
|
|
}
|
|
ret = apfs_crypto_adj_refcnt(sb, head.crypto_id, -1);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to take crypto id 0x%llx", head.crypto_id);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (le64_to_cpu(fq->sfq_count) <= APFS_TRANS_MAIN_QUEUE_MAX)
|
|
goto next_extent;
|
|
ret = -EAGAIN;
|
|
out:
|
|
apfs_free_query(query);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_inode_delete_front - Deletes as many leading extents as possible
|
|
* @inode: inode to delete
|
|
*
|
|
* Tries to delete all extents for @inode, in which case it returns 0. If the
|
|
* free queue is getting too full, deletes as much as is reasonable and returns
|
|
* -EAGAIN. May return other negative error codes as well.
|
|
*/
|
|
int apfs_inode_delete_front(struct inode *inode)
|
|
{
|
|
struct super_block *sb = inode->i_sb;
|
|
struct apfs_dstream_info *dstream = NULL;
|
|
struct apfs_inode_info *ai = APFS_I(inode);
|
|
int ret;
|
|
|
|
if (!ai->i_has_dstream)
|
|
return 0;
|
|
|
|
dstream = &ai->i_dstream;
|
|
ret = apfs_flush_extent_cache(dstream);
|
|
if (ret) {
|
|
apfs_err(sb, "extent cache flush failed for dstream 0x%llx", dstream->ds_id);
|
|
return ret;
|
|
}
|
|
|
|
ret = apfs_dstream_delete_front(sb, dstream->ds_id);
|
|
if (ret == -ENODATA)
|
|
return 0;
|
|
return ret;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
|
|
loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags)
|
|
#else
|
|
int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, u64 len)
|
|
#endif
|
|
{
|
|
struct inode *src_inode = file_inode(src_file);
|
|
struct inode *dst_inode = file_inode(dst_file);
|
|
struct apfs_inode_info *src_ai = APFS_I(src_inode);
|
|
struct apfs_inode_info *dst_ai = APFS_I(dst_inode);
|
|
struct apfs_dstream_info *src_ds = &src_ai->i_dstream;
|
|
struct apfs_dstream_info *dst_ds = &dst_ai->i_dstream;
|
|
struct super_block *sb = src_inode->i_sb;
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
|
const u64 xfield_flags = APFS_INODE_MAINTAIN_DIR_STATS | APFS_INODE_IS_SPARSE | APFS_INODE_HAS_PURGEABLE_FLAGS;
|
|
int err;
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
|
|
if (remap_flags & ~(REMAP_FILE_ADVISORY))
|
|
return -EINVAL;
|
|
#endif
|
|
if (src_inode == dst_inode)
|
|
return -EINVAL;
|
|
|
|
/* We only want to clone whole files, like in the official driver */
|
|
if (off != 0 || destoff != 0 || len != 0)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Clones here work in two steps: first the user creates an empty target
|
|
* file, and then the user calls the ioctl, which replaces the file with
|
|
* a clone. This is not atomic, of course.
|
|
*/
|
|
if (dst_ai->i_has_dstream || dst_ai->i_bsd_flags & APFS_INOBSD_COMPRESSED) {
|
|
apfs_warn(sb, "clones can only replace freshly created files");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
if (dst_ai->i_int_flags & xfield_flags) {
|
|
apfs_warn(sb, "clone can't replace a file that has xfields");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (!src_ai->i_has_dstream) {
|
|
apfs_warn(sb, "can't clone a file with no dstream");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
|
if (err)
|
|
return err;
|
|
apfs_inode_join_transaction(sb, src_inode);
|
|
apfs_inode_join_transaction(sb, dst_inode);
|
|
|
|
err = apfs_flush_extent_cache(src_ds);
|
|
if (err) {
|
|
apfs_err(sb, "extent cache flush failed for dstream 0x%llx", src_ds->ds_id);
|
|
goto fail;
|
|
}
|
|
err = apfs_dstream_adj_refcnt(src_ds, +1);
|
|
if (err) {
|
|
apfs_err(sb, "failed to take dstream id 0x%llx", src_ds->ds_id);
|
|
goto fail;
|
|
}
|
|
src_ds->ds_shared = true;
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
|
dst_inode->i_mtime = dst_inode->i_ctime = current_time(dst_inode);
|
|
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
|
|
dst_inode->i_mtime = inode_set_ctime_current(dst_inode);
|
|
#else
|
|
inode_set_mtime_to_ts(dst_inode, inode_set_ctime_current(dst_inode));
|
|
#endif
|
|
dst_inode->i_size = src_inode->i_size;
|
|
dst_ai->i_key_class = src_ai->i_key_class;
|
|
dst_ai->i_int_flags = src_ai->i_int_flags;
|
|
dst_ai->i_bsd_flags = src_ai->i_bsd_flags;
|
|
dst_ai->i_has_dstream = true;
|
|
|
|
dst_ds->ds_sb = src_ds->ds_sb;
|
|
dst_ds->ds_inode = dst_inode;
|
|
dst_ds->ds_id = src_ds->ds_id;
|
|
dst_ds->ds_size = src_ds->ds_size;
|
|
dst_ds->ds_sparse_bytes = src_ds->ds_sparse_bytes;
|
|
dst_ds->ds_cached_ext = src_ds->ds_cached_ext;
|
|
dst_ds->ds_ext_dirty = false;
|
|
dst_ds->ds_shared = true;
|
|
|
|
dst_ai->i_int_flags |= APFS_INODE_WAS_EVER_CLONED | APFS_INODE_WAS_CLONED;
|
|
src_ai->i_int_flags |= APFS_INODE_WAS_EVER_CLONED;
|
|
|
|
/*
|
|
* The sparse flag is the important one here: if we need it, it will get
|
|
* set later by apfs_update_inode(), after the xfield gets created.
|
|
*/
|
|
dst_ai->i_int_flags &= ~xfield_flags;
|
|
|
|
/*
|
|
* Commit the transaction to make sure all buffers in the source inode
|
|
* go through copy-on-write. This is a bit excessive, but I don't expect
|
|
* clones to be created often enough for it to matter.
|
|
*/
|
|
sbi->s_nxi->nx_transaction.t_state |= APFS_NX_TRANS_FORCE_COMMIT;
|
|
err = apfs_transaction_commit(sb);
|
|
if (err)
|
|
goto fail;
|
|
return dst_ds->ds_size;
|
|
|
|
fail:
|
|
apfs_transaction_abort(sb);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_extent_create_record - Create a logical extent record for a dstream id
|
|
* @sb: filesystem superblock
|
|
* @dstream_id: the dstream id
|
|
* @extent: extent info for the record
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_extent_create_record(struct super_block *sb, u64 dstream_id, struct apfs_file_extent *extent)
|
|
{
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
|
struct apfs_query *query = NULL;
|
|
struct apfs_file_extent_val raw_val;
|
|
struct apfs_file_extent_key raw_key;
|
|
int ret = 0;
|
|
|
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
|
if (!query)
|
|
return -ENOMEM;
|
|
apfs_init_file_extent_key(dstream_id, extent->logical_addr, &query->key);
|
|
query->flags = APFS_QUERY_CAT | APFS_QUERY_EXACT;
|
|
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret && ret != -ENODATA) {
|
|
apfs_err(sb, "query failed for id 0x%llx, addr 0x%llx", dstream_id, extent->logical_addr);
|
|
goto out;
|
|
}
|
|
|
|
apfs_key_set_hdr(APFS_TYPE_FILE_EXTENT, dstream_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 = cpu_to_le64(apfs_vol_is_encrypted(sb) ? dstream_id : 0); /* TODO */
|
|
|
|
ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
if (ret)
|
|
apfs_err(sb, "insertion failed for id 0x%llx, addr 0x%llx", dstream_id, extent->logical_addr);
|
|
out:
|
|
apfs_free_query(query);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_put_single_extent - Put a reference to a single extent
|
|
* @sb: filesystem superblock
|
|
* @paddr_end: first block after the extent to put
|
|
* @paddr_min: don't put references before this block
|
|
*
|
|
* Puts a reference to the physical extent range that ends in @paddr_end. Sets
|
|
* @paddr_end to the beginning of the extent, so that the caller can continue
|
|
* with the previous one. Returns 0 on success, or a negative error code in
|
|
* case of failure.
|
|
*
|
|
* TODO: unify this with apfs_take_single_extent(), they are almost the same.
|
|
*/
|
|
static int apfs_put_single_extent(struct super_block *sb, u64 *paddr_end, u64 paddr_min)
|
|
{
|
|
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
|
struct apfs_node *extref_root = NULL;
|
|
struct apfs_key key;
|
|
struct apfs_query *query = NULL;
|
|
struct apfs_phys_extent prev_ext;
|
|
u64 prev_start, prev_end;
|
|
bool cropped_head = false, cropped_tail = false;
|
|
struct apfs_file_extent tmp = {0}; /* TODO: clean up all the fake extent interfaces? */
|
|
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)) {
|
|
apfs_err(sb, "failed to read extref root 0x%llx", le64_to_cpu(vsb_raw->apfs_extentref_tree_oid));
|
|
return PTR_ERR(extref_root);
|
|
}
|
|
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
|
vsb_raw->apfs_extentref_tree_oid = cpu_to_le64(extref_root->object.oid);
|
|
|
|
apfs_init_extent_key(*paddr_end - 1, &key);
|
|
|
|
restart:
|
|
query = apfs_alloc_query(extref_root, NULL /* parent */);
|
|
if (!query) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
query->key = key;
|
|
query->flags = APFS_QUERY_EXTENTREF;
|
|
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret && ret != -ENODATA) {
|
|
apfs_err(sb, "query failed for paddr 0x%llx", *paddr_end - 1);
|
|
goto out;
|
|
}
|
|
|
|
if (ret == -ENODATA) {
|
|
/* The whole range to put is part of a snapshot */
|
|
tmp.phys_block_num = paddr_min;
|
|
tmp.len = (*paddr_end - paddr_min) << sb->s_blocksize_bits;
|
|
ret = apfs_create_update_pext(query, &tmp, -1);
|
|
*paddr_end = paddr_min;
|
|
goto out;
|
|
}
|
|
|
|
ret = apfs_phys_ext_from_query(query, &prev_ext);
|
|
if (ret) {
|
|
apfs_err(sb, "bad pext record over paddr 0x%llx", *paddr_end - 1);
|
|
goto out;
|
|
}
|
|
prev_start = prev_ext.bno;
|
|
prev_end = prev_ext.bno + prev_ext.blkcount;
|
|
if (prev_end < *paddr_end) {
|
|
/* The extent to put is part of a snapshot */
|
|
tmp.phys_block_num = max(prev_end, paddr_min);
|
|
tmp.len = (*paddr_end - tmp.phys_block_num) << sb->s_blocksize_bits;
|
|
ret = apfs_create_update_pext(query, &tmp, -1);
|
|
*paddr_end = tmp.phys_block_num;
|
|
goto out;
|
|
}
|
|
|
|
if ((cropped_tail && prev_end > *paddr_end) || (cropped_head && prev_start < paddr_min)) {
|
|
/* This should never happen, but be safe */
|
|
apfs_alert(sb, "recursion cropping physical extent 0x%llx-0x%llx", prev_start, prev_end);
|
|
ret = -EFSCORRUPTED;
|
|
goto out;
|
|
}
|
|
|
|
if (prev_end > *paddr_end) {
|
|
ret = apfs_split_phys_ext(query, *paddr_end);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to split pext at 0x%llx", *paddr_end);
|
|
goto out;
|
|
}
|
|
/* The split may make the query invalid */
|
|
apfs_free_query(query);
|
|
cropped_tail = true;
|
|
goto restart;
|
|
}
|
|
|
|
if (prev_start < paddr_min) {
|
|
ret = apfs_split_phys_ext(query, paddr_min);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to split pext at 0x%llx", paddr_min);
|
|
goto out;
|
|
}
|
|
/* The split may make the query invalid */
|
|
apfs_free_query(query);
|
|
cropped_head = true;
|
|
goto restart;
|
|
}
|
|
|
|
/* The extent to put already exists */
|
|
ret = apfs_put_phys_extent(&prev_ext, query);
|
|
if (ret)
|
|
apfs_err(sb, "failed to put pext at 0x%llx", prev_ext.bno);
|
|
*paddr_end = prev_start;
|
|
|
|
out:
|
|
apfs_free_query(query);
|
|
apfs_node_free(extref_root);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_range_put_reference - Put a reference to a physical range
|
|
* @sb: filesystem superblock
|
|
* @paddr: first block of the range
|
|
* @length: length of the range (in bytes)
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_range_put_reference(struct super_block *sb, u64 paddr, u64 length)
|
|
{
|
|
u64 extent_end;
|
|
int err;
|
|
|
|
ASSERT(paddr);
|
|
|
|
extent_end = paddr + (length >> sb->s_blocksize_bits);
|
|
while (extent_end > paddr) {
|
|
err = apfs_put_single_extent(sb, &extent_end, paddr);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_take_single_extent - Take a reference to a single extent
|
|
* @sb: filesystem superblock
|
|
* @paddr_end: first block after the extent to take
|
|
* @paddr_min: don't take references before this block
|
|
*
|
|
* Takes a reference to the physical extent range that ends in @paddr_end. Sets
|
|
* @paddr_end to the beginning of the extent, so that the caller can continue
|
|
* with the previous one. Returns 0 on success, or a negative error code in
|
|
* case of failure.
|
|
*/
|
|
static int apfs_take_single_extent(struct super_block *sb, u64 *paddr_end, u64 paddr_min)
|
|
{
|
|
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
|
struct apfs_node *extref_root = NULL;
|
|
struct apfs_key key;
|
|
struct apfs_query *query = NULL;
|
|
struct apfs_phys_extent prev_ext;
|
|
u64 prev_start, prev_end;
|
|
bool cropped_head = false, cropped_tail = false;
|
|
struct apfs_file_extent tmp = {0}; /* TODO: clean up all the fake extent interfaces? */
|
|
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)) {
|
|
apfs_err(sb, "failed to read extref root 0x%llx", le64_to_cpu(vsb_raw->apfs_extentref_tree_oid));
|
|
return PTR_ERR(extref_root);
|
|
}
|
|
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
|
vsb_raw->apfs_extentref_tree_oid = cpu_to_le64(extref_root->object.oid);
|
|
|
|
apfs_init_extent_key(*paddr_end - 1, &key);
|
|
|
|
restart:
|
|
query = apfs_alloc_query(extref_root, NULL /* parent */);
|
|
if (!query) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
query->key = key;
|
|
query->flags = APFS_QUERY_EXTENTREF;
|
|
|
|
ret = apfs_btree_query(sb, &query);
|
|
if (ret && ret != -ENODATA) {
|
|
apfs_err(sb, "query failed for paddr 0x%llx", *paddr_end - 1);
|
|
goto out;
|
|
}
|
|
|
|
if (ret == -ENODATA) {
|
|
/* The whole range to take is part of a snapshot */
|
|
tmp.phys_block_num = paddr_min;
|
|
tmp.len = (*paddr_end - paddr_min) << sb->s_blocksize_bits;
|
|
ret = apfs_create_update_pext(query, &tmp, +1);
|
|
*paddr_end = paddr_min;
|
|
goto out;
|
|
}
|
|
|
|
ret = apfs_phys_ext_from_query(query, &prev_ext);
|
|
if (ret) {
|
|
apfs_err(sb, "bad pext record over paddr 0x%llx", *paddr_end - 1);
|
|
goto out;
|
|
}
|
|
prev_start = prev_ext.bno;
|
|
prev_end = prev_ext.bno + prev_ext.blkcount;
|
|
if (prev_end < *paddr_end) {
|
|
/* The extent to take is part of a snapshot */
|
|
tmp.phys_block_num = max(prev_end, paddr_min);
|
|
tmp.len = (*paddr_end - tmp.phys_block_num) << sb->s_blocksize_bits;
|
|
ret = apfs_create_update_pext(query, &tmp, +1);
|
|
*paddr_end = tmp.phys_block_num;
|
|
goto out;
|
|
}
|
|
|
|
if ((cropped_tail && prev_end > *paddr_end) || (cropped_head && prev_start < paddr_min)) {
|
|
/* This should never happen, but be safe */
|
|
apfs_alert(sb, "recursion cropping physical extent 0x%llx-0x%llx", prev_start, prev_end);
|
|
ret = -EFSCORRUPTED;
|
|
goto out;
|
|
}
|
|
|
|
if (prev_end > *paddr_end) {
|
|
ret = apfs_split_phys_ext(query, *paddr_end);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to split pext at 0x%llx", *paddr_end);
|
|
goto out;
|
|
}
|
|
/* The split may make the query invalid */
|
|
apfs_free_query(query);
|
|
cropped_tail = true;
|
|
goto restart;
|
|
}
|
|
|
|
if (prev_start < paddr_min) {
|
|
ret = apfs_split_phys_ext(query, paddr_min);
|
|
if (ret) {
|
|
apfs_err(sb, "failed to split pext at 0x%llx", paddr_min);
|
|
goto out;
|
|
}
|
|
/* The split may make the query invalid */
|
|
apfs_free_query(query);
|
|
cropped_head = true;
|
|
goto restart;
|
|
}
|
|
|
|
/* The extent to take already exists */
|
|
ret = apfs_take_phys_extent(&prev_ext, query);
|
|
if (ret)
|
|
apfs_err(sb, "failed to take pext at 0x%llx", prev_ext.bno);
|
|
*paddr_end = prev_start;
|
|
|
|
out:
|
|
apfs_free_query(query);
|
|
apfs_node_free(extref_root);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_range_take_reference - Take a reference to a physical range
|
|
* @sb: filesystem superblock
|
|
* @paddr: first block of the range
|
|
* @length: length of the range (in bytes)
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_range_take_reference(struct super_block *sb, u64 paddr, u64 length)
|
|
{
|
|
u64 extent_end;
|
|
int err;
|
|
|
|
ASSERT(paddr);
|
|
|
|
extent_end = paddr + (length >> sb->s_blocksize_bits);
|
|
while (extent_end > paddr) {
|
|
err = apfs_take_single_extent(sb, &extent_end, paddr);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_clone_single_extent - Make a copy of an extent in a dstream to a new one
|
|
* @dstream: old dstream
|
|
* @new_id: id of the new dstream
|
|
* @log_addr: logical address for the extent
|
|
*
|
|
* Duplicates the logical extent, and updates the references to the physical
|
|
* extents as required. Sets @addr to the end of the extent, so that the caller
|
|
* can continue in the same place. Returns 0 on success, or a negative error
|
|
* code in case of failure.
|
|
*/
|
|
static int apfs_clone_single_extent(struct apfs_dstream_info *dstream, u64 new_id, u64 *log_addr)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
struct apfs_file_extent extent;
|
|
int err;
|
|
|
|
err = apfs_extent_read(dstream, *log_addr >> sb->s_blocksize_bits, &extent);
|
|
if (err) {
|
|
apfs_err(sb, "failed to read an extent to clone for dstream 0x%llx", dstream->ds_id);
|
|
return err;
|
|
}
|
|
err = apfs_extent_create_record(sb, new_id, &extent);
|
|
if (err) {
|
|
apfs_err(sb, "failed to create extent record for clone of dstream 0x%llx", dstream->ds_id);
|
|
return err;
|
|
}
|
|
|
|
if (!apfs_ext_is_hole(&extent)) {
|
|
err = apfs_range_take_reference(sb, extent.phys_block_num, extent.len);
|
|
if (err) {
|
|
apfs_err(sb, "failed to take a reference to physical range 0x%llx-0x%llx", extent.phys_block_num, extent.len);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
*log_addr += extent.len;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_clone_extents - Make a copy of all extents in a dstream to a new one
|
|
* @dstream: old dstream
|
|
* @new_id: id for the new dstream
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
int apfs_clone_extents(struct apfs_dstream_info *dstream, u64 new_id)
|
|
{
|
|
u64 next = 0;
|
|
int err;
|
|
|
|
while (next < dstream->ds_size) {
|
|
err = apfs_clone_single_extent(dstream, new_id, &next);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_nonsparse_dstream_read - Read from a dstream without holes
|
|
* @dstream: dstream to read
|
|
* @buf: destination buffer
|
|
* @count: exact number of bytes to read
|
|
* @offset: dstream offset to read from
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
int apfs_nonsparse_dstream_read(struct apfs_dstream_info *dstream, void *buf, size_t count, u64 offset)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
u64 logical_start_block, logical_end_block, log_bno, blkcnt, idx;
|
|
struct buffer_head **bhs = NULL;
|
|
int ret = 0;
|
|
|
|
/* Save myself from thinking about overflow here */
|
|
if (count >= APFS_MAX_FILE_SIZE || offset >= APFS_MAX_FILE_SIZE) {
|
|
apfs_err(sb, "dstream read overflow (0x%llx-0x%llx)", offset, (unsigned long long)count);
|
|
return -EFBIG;
|
|
}
|
|
|
|
if (offset + count > dstream->ds_size) {
|
|
apfs_err(sb, "reading past the end (0x%llx-0x%llx)", offset, (unsigned long long)count);
|
|
/* No caller is expected to legitimately read out-of-bounds */
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
logical_start_block = offset >> sb->s_blocksize_bits;
|
|
logical_end_block = (offset + count + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
|
|
blkcnt = logical_end_block - logical_start_block;
|
|
bhs = kcalloc(blkcnt, sizeof(*bhs), GFP_KERNEL);
|
|
if (!bhs)
|
|
return -ENOMEM;
|
|
|
|
for (log_bno = logical_start_block; log_bno < logical_end_block; log_bno++) {
|
|
struct buffer_head *bh = NULL;
|
|
u64 bno = 0;
|
|
|
|
idx = log_bno - logical_start_block;
|
|
|
|
ret = apfs_logic_to_phys_bno(dstream, log_bno, &bno);
|
|
if (ret)
|
|
goto out;
|
|
if (bno == 0) {
|
|
apfs_err(sb, "nonsparse dstream has a hole");
|
|
ret = -EFSCORRUPTED;
|
|
goto out;
|
|
}
|
|
|
|
bhs[idx] = __apfs_getblk(sb, bno);
|
|
if (!bhs[idx]) {
|
|
apfs_err(sb, "failed to map block 0x%llx", bno);
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
bh = bhs[idx];
|
|
if (!buffer_uptodate(bh)) {
|
|
get_bh(bh);
|
|
lock_buffer(bh);
|
|
apfs_submit_bh(REQ_OP_READ, 0, bh);
|
|
}
|
|
}
|
|
|
|
for (log_bno = logical_start_block; log_bno < logical_end_block; log_bno++) {
|
|
int off_in_block, left_in_block;
|
|
|
|
idx = log_bno - logical_start_block;
|
|
wait_on_buffer(bhs[idx]);
|
|
if (!buffer_uptodate(bhs[idx])) {
|
|
apfs_err(sb, "failed to read a block");
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if (log_bno == logical_start_block)
|
|
off_in_block = offset & (sb->s_blocksize - 1);
|
|
else
|
|
off_in_block = 0;
|
|
|
|
if (log_bno == logical_end_block - 1)
|
|
left_in_block = count + offset - (log_bno << sb->s_blocksize_bits) - off_in_block;
|
|
else
|
|
left_in_block = sb->s_blocksize - off_in_block;
|
|
|
|
memcpy(buf, bhs[idx]->b_data + off_in_block, left_in_block);
|
|
buf += left_in_block;
|
|
}
|
|
|
|
out:
|
|
if (bhs) {
|
|
for (idx = 0; idx < blkcnt; idx++)
|
|
brelse(bhs[idx]);
|
|
kfree(bhs);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* apfs_nonsparse_dstream_preread - Attempt to preread a dstream without holes
|
|
* @dstream: dstream to preread
|
|
*
|
|
* Requests reads for all blocks of a dstream, but doesn't wait for the result.
|
|
*/
|
|
void apfs_nonsparse_dstream_preread(struct apfs_dstream_info *dstream)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
u64 logical_end_block, log_bno;
|
|
|
|
logical_end_block = (dstream->ds_size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
|
|
|
|
for (log_bno = 0; log_bno < logical_end_block; log_bno++) {
|
|
struct buffer_head *bh = NULL;
|
|
u64 bno = 0;
|
|
int ret;
|
|
|
|
ret = apfs_logic_to_phys_bno(dstream, log_bno, &bno);
|
|
if (ret || bno == 0)
|
|
return;
|
|
|
|
bh = __apfs_getblk(sb, bno);
|
|
if (!bh)
|
|
return;
|
|
if (!buffer_uptodate(bh)) {
|
|
get_bh(bh);
|
|
lock_buffer(bh);
|
|
apfs_submit_bh(REQ_OP_READ, 0, bh);
|
|
}
|
|
brelse(bh);
|
|
bh = NULL;
|
|
}
|
|
}
|