mirror of
https://github.com/linux-apfs/linux-apfs-rw.git
synced 2026-08-18 02:19:59 -07:00
This is the second bug discovered by valgrind in the userland tool. The buffer head for the ip bitmap gets nulled after joining the transaction, but we still hold a reference to it (the transactions takes a new one). Fix it. Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
1481 lines
42 KiB
C
1481 lines
42 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2019 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
|
*/
|
|
|
|
#include <linux/buffer_head.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/slab.h>
|
|
#include "apfs.h"
|
|
|
|
/**
|
|
* apfs_spaceman_read_cib_addr - Get the address of a cib from the spaceman
|
|
* @sb: superblock structure
|
|
* @index: index of the chunk-info block
|
|
*
|
|
* Returns the block number for the chunk-info block.
|
|
*
|
|
* This is not described in the official documentation; credit for figuring it
|
|
* out should go to Joachim Metz: <https://github.com/libyal/libfsapfs>.
|
|
*/
|
|
static u64 apfs_spaceman_read_cib_addr(struct super_block *sb, int index)
|
|
{
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
u32 offset;
|
|
__le64 *addr_p;
|
|
|
|
offset = sm->sm_addr_offset + index * sizeof(*addr_p);
|
|
addr_p = (void *)sm_raw + offset;
|
|
return le64_to_cpup(addr_p);
|
|
}
|
|
|
|
/**
|
|
* apfs_spaceman_write_cib_addr - Store the address of a cib in the spaceman
|
|
* @sb: superblock structure
|
|
* @index: index of the chunk-info block
|
|
* @addr: address of the chunk-info block
|
|
*/
|
|
static void apfs_spaceman_write_cib_addr(struct super_block *sb,
|
|
int index, u64 addr)
|
|
{
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
u32 offset;
|
|
__le64 *addr_p;
|
|
|
|
apfs_assert_in_transaction(sb, &sm_raw->sm_o);
|
|
|
|
offset = sm->sm_addr_offset + index * sizeof(*addr_p);
|
|
addr_p = (void *)sm_raw + offset;
|
|
*addr_p = cpu_to_le64(addr);
|
|
}
|
|
|
|
/**
|
|
* apfs_max_chunks_per_cib - Find the maximum chunk count for a chunk-info block
|
|
* @sb: superblock structure
|
|
*/
|
|
static inline int apfs_max_chunks_per_cib(struct super_block *sb)
|
|
{
|
|
return (sb->s_blocksize - sizeof(struct apfs_chunk_info_block)) /
|
|
sizeof(struct apfs_chunk_info);
|
|
}
|
|
|
|
/**
|
|
* apfs_read_spaceman_dev - Read a space manager device structure
|
|
* @sb: superblock structure
|
|
* @dev: on-disk device structure
|
|
*
|
|
* Initializes the in-memory spaceman fields related to the main device; fusion
|
|
* drives are not yet supported. Returns 0 on success, or a negative error code
|
|
* in case of failure.
|
|
*/
|
|
static int apfs_read_spaceman_dev(struct super_block *sb,
|
|
struct apfs_spaceman_device *dev)
|
|
{
|
|
struct apfs_spaceman *spaceman = APFS_SM(sb);
|
|
|
|
if (dev->sm_cab_count) {
|
|
apfs_err(sb, "large devices are not supported");
|
|
return -EINVAL;
|
|
}
|
|
|
|
spaceman->sm_block_count = le64_to_cpu(dev->sm_block_count);
|
|
spaceman->sm_chunk_count = le64_to_cpu(dev->sm_chunk_count);
|
|
spaceman->sm_cib_count = le32_to_cpu(dev->sm_cib_count);
|
|
spaceman->sm_free_count = le64_to_cpu(dev->sm_free_count);
|
|
spaceman->sm_addr_offset = le32_to_cpu(dev->sm_addr_offset);
|
|
|
|
/* Check that all the cib addresses fit in the spaceman object */
|
|
if ((long long)spaceman->sm_addr_offset +
|
|
(long long)spaceman->sm_cib_count * sizeof(u64) > spaceman->sm_size) {
|
|
apfs_err(sb, "too many cibs (%u)", spaceman->sm_cib_count);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_spaceman_get_16 - Get a 16-bit value from an offset in the spaceman
|
|
* @sb: superblock structure
|
|
* @off: offset for the value
|
|
*
|
|
* Returns a pointer to the value, or NULL if it doesn't fit.
|
|
*/
|
|
static __le16 *apfs_spaceman_get_16(struct super_block *sb, size_t off)
|
|
{
|
|
struct apfs_spaceman *spaceman = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = spaceman->sm_raw;
|
|
|
|
if (off > spaceman->sm_size)
|
|
return NULL;
|
|
if (off + sizeof(__le16) > spaceman->sm_size)
|
|
return NULL;
|
|
return (void *)sm_raw + off;
|
|
}
|
|
|
|
/**
|
|
* apfs_spaceman_get_64 - Get a 64-bit value from an offset in the spaceman
|
|
* @sb: superblock structure
|
|
* @off: offset for the value
|
|
*
|
|
* Returns a pointer to the value, or NULL if it doesn't fit.
|
|
*/
|
|
static __le64 *apfs_spaceman_get_64(struct super_block *sb, size_t off)
|
|
{
|
|
struct apfs_spaceman *spaceman = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = spaceman->sm_raw;
|
|
|
|
if (off > spaceman->sm_size)
|
|
return NULL;
|
|
if (off + sizeof(__le64) > spaceman->sm_size)
|
|
return NULL;
|
|
return (void *)sm_raw + off;
|
|
}
|
|
|
|
/**
|
|
* apfs_allocate_ip_bitmap - Allocate a free ip bitmap block
|
|
* @sb: filesystem superblock
|
|
* @offset_p: on return, the offset from sm_ip_bm_base of the allocated block
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_allocate_ip_bitmap(struct super_block *sb, u16 *offset_p)
|
|
{
|
|
struct apfs_spaceman *spaceman = NULL;
|
|
struct apfs_spaceman_phys *sm_raw = NULL;
|
|
u32 free_next_offset, old_head_off;
|
|
u16 free_head, blkcnt;
|
|
__le16 *old_head_p = NULL;
|
|
|
|
spaceman = APFS_SM(sb);
|
|
sm_raw = spaceman->sm_raw;
|
|
free_next_offset = le32_to_cpu(sm_raw->sm_ip_bm_free_next_offset);
|
|
free_head = le16_to_cpu(sm_raw->sm_ip_bm_free_head);
|
|
blkcnt = (u16)le32_to_cpu(sm_raw->sm_ip_bm_block_count);
|
|
|
|
/*
|
|
* The "free_next" array is a linked list of free blocks that starts
|
|
* with the "free_head". Allocate this head then, and make the next
|
|
* block into the new head.
|
|
*/
|
|
old_head_off = free_next_offset + free_head * sizeof(*old_head_p);
|
|
old_head_p = apfs_spaceman_get_16(sb, old_head_off);
|
|
if (!old_head_p) {
|
|
apfs_err(sb, "free_next head offset out of bounds (%u)", old_head_off);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
*offset_p = free_head;
|
|
free_head = le16_to_cpup(old_head_p);
|
|
sm_raw->sm_ip_bm_free_head = *old_head_p;
|
|
/* No longer free, no longer part of the linked list */
|
|
*old_head_p = cpu_to_le16(APFS_SPACEMAN_IP_BM_INDEX_INVALID);
|
|
|
|
/* Just a little sanity check because I've messed this up before */
|
|
if (free_head >= blkcnt || *offset_p >= blkcnt) {
|
|
apfs_err(sb, "free next list seems empty or corrupt");
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_free_ip_bitmap - Free a used ip bitmap block
|
|
* @sb: filesystem superblock
|
|
* @offset: the offset from sm_ip_bm_base of the block to free
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_free_ip_bitmap(struct super_block *sb, u16 offset)
|
|
{
|
|
struct apfs_spaceman *spaceman = NULL;
|
|
struct apfs_spaceman_phys *sm_raw = NULL;
|
|
u32 free_next_offset, old_tail_off;
|
|
u16 free_tail;
|
|
__le16 *old_tail_p = NULL;
|
|
|
|
spaceman = APFS_SM(sb);
|
|
sm_raw = spaceman->sm_raw;
|
|
free_next_offset = le32_to_cpu(sm_raw->sm_ip_bm_free_next_offset);
|
|
free_tail = le16_to_cpu(sm_raw->sm_ip_bm_free_tail);
|
|
|
|
/*
|
|
* The "free_next" array is a linked list of free blocks that ends
|
|
* with the "free_tail". The block getting freed will become the new
|
|
* tail of the list.
|
|
*/
|
|
old_tail_off = free_next_offset + free_tail * sizeof(*old_tail_p);
|
|
old_tail_p = apfs_spaceman_get_16(sb, old_tail_off);
|
|
if (!old_tail_p) {
|
|
apfs_err(sb, "free_next tail offset out of bounds (%u)", old_tail_off);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
*old_tail_p = cpu_to_le16(offset);
|
|
sm_raw->sm_ip_bm_free_tail = cpu_to_le16(offset);
|
|
free_tail = offset;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_reallocate_ip_bitmap - Find a new block for an ip bitmap
|
|
* @sb: filesystem superblock
|
|
* @offset_p: the offset from sm_ip_bm_base of the block to free
|
|
*
|
|
* On success returns 0 and updates @offset_p to the new offset allocated for
|
|
* the ip bitmap. Since blocks are allocated at the head of the list and freed
|
|
* at the tail, there is no risk of reuse by future reallocations within the
|
|
* same transaction (under there is some serious corruption, of course).
|
|
*
|
|
* Returns a negative error code in case of failure.
|
|
*/
|
|
static int apfs_reallocate_ip_bitmap(struct super_block *sb, __le16 *offset_p)
|
|
{
|
|
int err;
|
|
u16 offset;
|
|
|
|
offset = le16_to_cpup(offset_p);
|
|
|
|
err = apfs_free_ip_bitmap(sb, offset);
|
|
if (err) {
|
|
apfs_err(sb, "failed to free ip bitmap %u", offset);
|
|
return err;
|
|
}
|
|
err = apfs_allocate_ip_bitmap(sb, &offset);
|
|
if (err) {
|
|
apfs_err(sb, "failed to allocate a new ip bitmap block");
|
|
return err;
|
|
}
|
|
|
|
*offset_p = cpu_to_le16(offset);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_write_single_ip_bitmap - Write a single ip bitmap to disk
|
|
* @sb: filesystem superblock
|
|
* @bitmap: bitmap to write
|
|
* @idx: index of the ip bitmap to write
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_write_single_ip_bitmap(struct super_block *sb, char *bitmap, u32 idx)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
|
struct apfs_spaceman *spaceman = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = spaceman->sm_raw;
|
|
struct buffer_head *bh = NULL;
|
|
u64 ip_bm_base, ip_bitmap_bno;
|
|
u32 xid_off, ip_bitmap_off;
|
|
__le64 *xid_p = NULL;
|
|
__le16 *ip_bitmap_p = NULL;
|
|
int err;
|
|
|
|
ip_bm_base = le64_to_cpu(sm_raw->sm_ip_bm_base);
|
|
|
|
/* First update the xid, which is kept in a separate array */
|
|
xid_off = le32_to_cpu(sm_raw->sm_ip_bm_xid_offset) + idx * sizeof(*xid_p);
|
|
xid_p = apfs_spaceman_get_64(sb, xid_off);
|
|
if (!xid_p) {
|
|
apfs_err(sb, "xid out of bounds (%u)", xid_off);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
*xid_p = cpu_to_le64(nxi->nx_xid);
|
|
|
|
/* Now get find new location for the ip bitmap (and free the old one) */
|
|
ip_bitmap_off = le32_to_cpu(sm_raw->sm_ip_bitmap_offset) + idx * sizeof(*ip_bitmap_p);
|
|
ip_bitmap_p = apfs_spaceman_get_16(sb, ip_bitmap_off);
|
|
if (!ip_bitmap_p) {
|
|
apfs_err(sb, "bmap offset out of bounds (%u)", ip_bitmap_off);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
err = apfs_reallocate_ip_bitmap(sb, ip_bitmap_p);
|
|
if (err) {
|
|
apfs_err(sb, "failed to reallocate ip bitmap %u", le16_to_cpup(ip_bitmap_p));
|
|
return err;
|
|
}
|
|
|
|
/* Finally, write the dirty bitmap to the new location */
|
|
ip_bitmap_bno = ip_bm_base + le16_to_cpup(ip_bitmap_p);
|
|
bh = apfs_getblk(sb, ip_bitmap_bno);
|
|
if (!bh) {
|
|
apfs_err(sb, "failed to map block for CoW (0x%llx)", ip_bitmap_bno);
|
|
return -EIO;
|
|
}
|
|
memcpy(bh->b_data, bitmap, sb->s_blocksize);
|
|
err = apfs_transaction_join(sb, bh);
|
|
if (err)
|
|
goto fail;
|
|
put_bh(bh);
|
|
bh = NULL;
|
|
|
|
spaceman->sm_ip_bmaps[idx].dirty = false;
|
|
return 0;
|
|
|
|
fail:
|
|
brelse(bh);
|
|
bh = NULL;
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_write_ip_bitmaps - Write all dirty ip bitmaps to disk
|
|
* @sb: superblock structure
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
int apfs_write_ip_bitmaps(struct super_block *sb)
|
|
{
|
|
struct apfs_spaceman *spaceman = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = spaceman->sm_raw;
|
|
struct apfs_ip_bitmap_block_info *info = NULL;
|
|
u32 bmaps_count = spaceman->sm_ip_bmaps_count;
|
|
int err;
|
|
u32 i;
|
|
|
|
apfs_assert_in_transaction(sb, &sm_raw->sm_o);
|
|
|
|
for (i = 0; i < bmaps_count; ++i) {
|
|
info = &spaceman->sm_ip_bmaps[i];
|
|
if (!info->dirty)
|
|
continue;
|
|
err = apfs_write_single_ip_bitmap(sb, info->block, i);
|
|
if (err) {
|
|
apfs_err(sb, "failed to rotate ip bitmap %u", i);
|
|
return err;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_read_single_ip_bitmap - Read a single ip bitmap to memory
|
|
* @sb: filesystem superblock
|
|
* @idx: index of the ip bitmap to read
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_read_single_ip_bitmap(struct super_block *sb, u32 idx)
|
|
{
|
|
struct apfs_spaceman *spaceman = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = spaceman->sm_raw;
|
|
struct buffer_head *bh = NULL;
|
|
char *bitmap = NULL;
|
|
u64 ip_bm_base, ip_bitmap_bno;
|
|
u32 ip_bitmap_off;
|
|
__le16 *ip_bitmap_p = NULL;
|
|
int err;
|
|
|
|
ip_bm_base = le64_to_cpu(sm_raw->sm_ip_bm_base);
|
|
|
|
ip_bitmap_off = le32_to_cpu(sm_raw->sm_ip_bitmap_offset) + idx * sizeof(*ip_bitmap_p);
|
|
ip_bitmap_p = apfs_spaceman_get_16(sb, ip_bitmap_off);
|
|
if (!ip_bitmap_p) {
|
|
apfs_err(sb, "bmap offset out of bounds (%u)", ip_bitmap_off);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
bitmap = kmalloc(sb->s_blocksize, GFP_KERNEL);
|
|
if (!bitmap)
|
|
return -ENOMEM;
|
|
|
|
ip_bitmap_bno = ip_bm_base + le16_to_cpup(ip_bitmap_p);
|
|
bh = apfs_sb_bread(sb, ip_bitmap_bno);
|
|
if (!bh) {
|
|
apfs_err(sb, "failed to read ip bitmap (0x%llx)", ip_bitmap_bno);
|
|
err = -EIO;
|
|
goto fail;
|
|
}
|
|
memcpy(bitmap, bh->b_data, sb->s_blocksize);
|
|
brelse(bh);
|
|
bh = NULL;
|
|
|
|
spaceman->sm_ip_bmaps[idx].dirty = false;
|
|
spaceman->sm_ip_bmaps[idx].block = bitmap;
|
|
bitmap = NULL;
|
|
return 0;
|
|
|
|
fail:
|
|
kfree(bitmap);
|
|
bitmap = NULL;
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_read_ip_bitmaps - Read all the ip bitmaps to memory
|
|
* @sb: superblock structure
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_read_ip_bitmaps(struct super_block *sb)
|
|
{
|
|
struct apfs_spaceman *spaceman = APFS_SM(sb);
|
|
u32 bmaps_count = spaceman->sm_ip_bmaps_count;
|
|
int err;
|
|
u32 i;
|
|
|
|
for (i = 0; i < bmaps_count; ++i) {
|
|
err = apfs_read_single_ip_bitmap(sb, i);
|
|
if (err) {
|
|
apfs_err(sb, "failed to read ip bitmap %u", i);
|
|
return err;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Free queue record data
|
|
*/
|
|
struct apfs_fq_rec {
|
|
u64 xid;
|
|
u64 bno;
|
|
u64 len;
|
|
};
|
|
|
|
/**
|
|
* apfs_fq_rec_from_query - Read the free queue record found by a query
|
|
* @query: the query that found the record
|
|
* @fqrec: on return, the free queue record
|
|
*
|
|
* Reads the free queue record into @fqrec and performs some basic sanity
|
|
* checks as a protection against crafted filesystems. Returns 0 on success
|
|
* or -EFSCORRUPTED otherwise.
|
|
*/
|
|
static int apfs_fq_rec_from_query(struct apfs_query *query, struct apfs_fq_rec *fqrec)
|
|
{
|
|
char *raw = query->node->object.data;
|
|
struct apfs_spaceman_free_queue_key *key;
|
|
|
|
if (query->key_len != sizeof(*key)) {
|
|
apfs_err(query->node->object.sb, "bad key length (%d)", query->key_len);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
key = (struct apfs_spaceman_free_queue_key *)(raw + query->key_off);
|
|
|
|
fqrec->xid = le64_to_cpu(key->sfqk_xid);
|
|
fqrec->bno = le64_to_cpu(key->sfqk_paddr);
|
|
|
|
if (query->len == 0) {
|
|
fqrec->len = 1; /* Ghost record */
|
|
return 0;
|
|
} else if (query->len == sizeof(__le64)) {
|
|
fqrec->len = le64_to_cpup((__le64 *)(raw + query->off));
|
|
return 0;
|
|
}
|
|
apfs_err(query->node->object.sb, "bad value length (%d)", query->len);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
/**
|
|
* apfs_block_in_ip - Does this block belong to the internal pool?
|
|
* @sm: in-memory spaceman structure
|
|
* @bno: block number to check
|
|
*/
|
|
static inline bool apfs_block_in_ip(struct apfs_spaceman *sm, u64 bno)
|
|
{
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
u64 start = le64_to_cpu(sm_raw->sm_ip_base);
|
|
u64 end = start + le64_to_cpu(sm_raw->sm_ip_block_count);
|
|
|
|
return bno >= start && bno < end;
|
|
}
|
|
|
|
/**
|
|
* apfs_ip_mark_free - Mark a block in the internal pool as free
|
|
* @sb: superblock structure
|
|
* @bno: block number (must belong to the ip)
|
|
*/
|
|
static int apfs_ip_mark_free(struct super_block *sb, u64 bno)
|
|
{
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
struct apfs_ip_bitmap_block_info *info = NULL;
|
|
|
|
bno -= le64_to_cpu(sm_raw->sm_ip_base);
|
|
info = &sm->sm_ip_bmaps[bno >> sm->sm_ip_bmaps_shift];
|
|
__clear_bit_le(bno & sm->sm_ip_bmaps_mask, info->block);
|
|
info->dirty = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* apfs_main_free - Mark a regular block as free
|
|
*/
|
|
static int apfs_main_free(struct super_block *sb, u64 bno);
|
|
|
|
/**
|
|
* apfs_flush_fq_rec - Delete a single fq record and mark its blocks as free
|
|
* @root: free queue root node
|
|
* @xid: transaction to target
|
|
* @len: on return, the number of freed blocks
|
|
*
|
|
* Returns 0 on success, or a negative error code in case of failure. -ENODATA
|
|
* in particular means that there are no matching records left.
|
|
*/
|
|
static int apfs_flush_fq_rec(struct apfs_node *root, u64 xid, u64 *len)
|
|
{
|
|
struct super_block *sb = root->object.sb;
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_query *query = NULL;
|
|
struct apfs_fq_rec fqrec = {0};
|
|
u64 bno;
|
|
int err;
|
|
|
|
query = apfs_alloc_query(root, NULL /* parent */);
|
|
if (!query)
|
|
return -ENOMEM;
|
|
apfs_init_free_queue_key(xid, 0 /* paddr */, &query->key);
|
|
query->flags |= APFS_QUERY_FREE_QUEUE | APFS_QUERY_ANY_NUMBER | APFS_QUERY_EXACT;
|
|
|
|
err = apfs_btree_query(sb, &query);
|
|
if (err) {
|
|
if (err != -ENODATA)
|
|
apfs_err(sb, "query failed for xid 0x%llx, paddr 0x%llx", xid, 0ULL);
|
|
goto fail;
|
|
}
|
|
err = apfs_fq_rec_from_query(query, &fqrec);
|
|
if (err) {
|
|
apfs_err(sb, "bad free queue rec for xid 0x%llx", xid);
|
|
goto fail;
|
|
}
|
|
|
|
for (bno = fqrec.bno; bno < fqrec.bno + fqrec.len; ++bno) {
|
|
if (apfs_block_in_ip(sm, bno))
|
|
err = apfs_ip_mark_free(sb, bno);
|
|
else
|
|
err = apfs_main_free(sb, bno);
|
|
if (err) {
|
|
apfs_err(sb, "freeing block 0x%llx failed (%d)", (unsigned long long)bno, err);
|
|
goto fail;
|
|
}
|
|
}
|
|
err = apfs_btree_remove(query);
|
|
if (err) {
|
|
apfs_err(sb, "removal failed for xid 0x%llx", xid);
|
|
goto fail;
|
|
}
|
|
*len = fqrec.len;
|
|
|
|
fail:
|
|
apfs_free_query(query);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_free_queue_oldest_xid - Find the oldest xid among the free queue records
|
|
* @root: free queue root node
|
|
*/
|
|
static u64 apfs_free_queue_oldest_xid(struct apfs_node *root)
|
|
{
|
|
struct apfs_spaceman_free_queue_key *key;
|
|
char *raw = root->object.data;
|
|
int len, off;
|
|
|
|
if (root->records == 0)
|
|
return 0;
|
|
len = apfs_node_locate_key(root, 0, &off);
|
|
if (len != sizeof(*key)) {
|
|
/* TODO: abort transaction */
|
|
apfs_err(root->object.sb, "bad key length (%d)", len);
|
|
return 0;
|
|
}
|
|
key = (struct apfs_spaceman_free_queue_key *)(raw + off);
|
|
return le64_to_cpu(key->sfqk_xid);
|
|
}
|
|
|
|
/**
|
|
* apfs_flush_free_queue - Free ip blocks queued by old transactions
|
|
* @sb: superblock structure
|
|
* @qid: queue to be freed
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
struct apfs_spaceman_free_queue *fq = &sm_raw->sm_fq[qid];
|
|
struct apfs_node *fq_root;
|
|
struct apfs_btree_info *fq_info = NULL;
|
|
u64 oldest = le64_to_cpu(fq->sfq_oldest_xid);
|
|
int err;
|
|
|
|
fq_root = apfs_read_node(sb, le64_to_cpu(fq->sfq_tree_oid),
|
|
APFS_OBJ_EPHEMERAL, true /* write */);
|
|
if (IS_ERR(fq_root)) {
|
|
apfs_err(sb, "failed to read fq root 0x%llx", le64_to_cpu(fq->sfq_tree_oid));
|
|
return PTR_ERR(fq_root);
|
|
}
|
|
|
|
while (oldest) {
|
|
/*
|
|
* Blocks freed in the current transaction can't be reused
|
|
* safely until after the commit, but I don't think there is
|
|
* any point in preserving old transacions. I'm guessing the
|
|
* official driver keeps multiple transactions going at the
|
|
* same time, that must be why they need a free queue.
|
|
*/
|
|
if (oldest == nxi->nx_xid)
|
|
break;
|
|
|
|
while (true) {
|
|
u64 count = 0;
|
|
|
|
/* Probably not very efficient... */
|
|
err = apfs_flush_fq_rec(fq_root, oldest, &count);
|
|
if (err == -ENODATA) {
|
|
err = 0;
|
|
break;
|
|
} else if (err) {
|
|
apfs_err(sb, "failed to flush fq");
|
|
goto fail;
|
|
} else {
|
|
le64_add_cpu(&fq->sfq_count, -count);
|
|
}
|
|
}
|
|
oldest = apfs_free_queue_oldest_xid(fq_root);
|
|
fq->sfq_oldest_xid = cpu_to_le64(oldest);
|
|
}
|
|
|
|
if (qid == APFS_SFQ_MAIN) {
|
|
fq_info = (void *)fq_root->object.data + sb->s_blocksize - sizeof(*fq_info);
|
|
sm->sm_main_fq_nodes = le64_to_cpu(fq_info->bt_node_count);
|
|
if (sm->sm_main_fq_nodes != 1) {
|
|
apfs_alert(sb, "main queue wasn't flushed in full - bug!");
|
|
err = -EFSCORRUPTED;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
fail:
|
|
apfs_node_free(fq_root);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_allocate_spaceman - Allocate an in-memory spaceman struct, if needed
|
|
* @sb: superblock structure
|
|
* @raw: on-disk spaceman struct
|
|
* @size: size of the on-disk spaceman
|
|
*
|
|
* Returns the spaceman and sets it in the superblock info. Also performs all
|
|
* initializations for the internal pool, including reading all the ip bitmaps.
|
|
* This is a bit out of place here, but it's convenient because it has to
|
|
* happen only once.
|
|
*
|
|
* On failure, returns an error pointer.
|
|
*/
|
|
static struct apfs_spaceman *apfs_allocate_spaceman(struct super_block *sb, struct apfs_spaceman_phys *raw, u32 size)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
|
struct apfs_spaceman *spaceman = NULL;
|
|
int blk_bitcnt = sb->s_blocksize * 8;
|
|
size_t sm_size;
|
|
u32 bmap_cnt;
|
|
int err;
|
|
|
|
if (nxi->nx_spaceman)
|
|
return nxi->nx_spaceman;
|
|
|
|
/* We don't expect filesystems this big, it would be like 260 TiB */
|
|
bmap_cnt = le32_to_cpu(raw->sm_ip_bm_size_in_blocks);
|
|
if (bmap_cnt > 200) {
|
|
apfs_err(sb, "too many ip bitmap blocks (%u)", bmap_cnt);
|
|
return ERR_PTR(-EFSCORRUPTED);
|
|
}
|
|
sm_size = sizeof(*spaceman) + bmap_cnt * sizeof(spaceman->sm_ip_bmaps[0]);
|
|
|
|
spaceman = nxi->nx_spaceman = kzalloc(sm_size, GFP_KERNEL);
|
|
if (!spaceman)
|
|
return ERR_PTR(-ENOMEM);
|
|
spaceman->sm_nxi = nxi;
|
|
/*
|
|
* These two fields must be set before reading the ip bitmaps, since
|
|
* that stuff involves several variable-length arrays inside the
|
|
* spaceman object itself.
|
|
*/
|
|
spaceman->sm_raw = raw;
|
|
spaceman->sm_size = size;
|
|
|
|
spaceman->sm_ip_bmaps_count = bmap_cnt;
|
|
spaceman->sm_ip_bmaps_mask = blk_bitcnt - 1;
|
|
spaceman->sm_ip_bmaps_shift = order_base_2(blk_bitcnt);
|
|
|
|
/* This must happen only once, so it's easier to just leave it here */
|
|
err = apfs_read_ip_bitmaps(sb);
|
|
if (err) {
|
|
apfs_err(sb, "failed to read the ip bitmaps");
|
|
kfree(spaceman);
|
|
nxi->nx_spaceman = spaceman = NULL;
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
return nxi->nx_spaceman;
|
|
}
|
|
|
|
/**
|
|
* apfs_read_spaceman - Find and read the space manager
|
|
* @sb: superblock structure
|
|
*
|
|
* Reads the space manager structure from disk and initializes its in-memory
|
|
* counterpart; returns 0 on success, or a negative error code in case of
|
|
* failure.
|
|
*/
|
|
int apfs_read_spaceman(struct super_block *sb)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
|
struct apfs_nx_superblock *raw_sb = nxi->nx_raw;
|
|
struct apfs_spaceman *spaceman = NULL;
|
|
struct apfs_ephemeral_object_info *sm_eph_info = NULL;
|
|
struct apfs_spaceman_phys *sm_raw;
|
|
u32 sm_flags;
|
|
u64 oid = le64_to_cpu(raw_sb->nx_spaceman_oid);
|
|
int err;
|
|
|
|
if (sb->s_flags & SB_RDONLY) /* The space manager won't be needed */
|
|
return 0;
|
|
|
|
sm_eph_info = apfs_ephemeral_object_lookup(sb, oid);
|
|
if (IS_ERR(sm_eph_info)) {
|
|
apfs_err(sb, "no spaceman object for oid 0x%llx", oid);
|
|
return PTR_ERR(sm_eph_info);
|
|
}
|
|
sm_raw = (struct apfs_spaceman_phys *)sm_eph_info->object;
|
|
sm_raw->sm_o.o_xid = cpu_to_le64(nxi->nx_xid);
|
|
|
|
spaceman = apfs_allocate_spaceman(sb, sm_raw, sm_eph_info->size);
|
|
if (IS_ERR(spaceman)) {
|
|
apfs_err(sb, "failed to allocate spaceman");
|
|
err = PTR_ERR(spaceman);
|
|
goto fail;
|
|
}
|
|
|
|
spaceman->sm_free_cache_base = spaceman->sm_free_cache_blkcnt = 0;
|
|
|
|
sm_flags = le32_to_cpu(sm_raw->sm_flags);
|
|
/* Undocumented feature, but it's too common to refuse to mount */
|
|
if (sm_flags & APFS_SM_FLAG_VERSIONED)
|
|
pr_warn_once("APFS: space manager is versioned\n");
|
|
|
|
/* Only read the main device; fusion drives are not yet supported */
|
|
err = apfs_read_spaceman_dev(sb, &sm_raw->sm_dev[APFS_SD_MAIN]);
|
|
if (err) {
|
|
apfs_err(sb, "failed to read main device");
|
|
goto fail;
|
|
}
|
|
|
|
spaceman->sm_blocks_per_chunk =
|
|
le32_to_cpu(sm_raw->sm_blocks_per_chunk);
|
|
spaceman->sm_chunks_per_cib = le32_to_cpu(sm_raw->sm_chunks_per_cib);
|
|
if (spaceman->sm_chunks_per_cib > apfs_max_chunks_per_cib(sb)) {
|
|
apfs_err(sb, "too many chunks per cib (%u)", spaceman->sm_chunks_per_cib);
|
|
err = -EFSCORRUPTED;
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* We flush free queues whole when each transaction begins, to make it
|
|
* harder for the btrees to become too unbalanced.
|
|
*/
|
|
err = apfs_flush_free_queue(sb, APFS_SFQ_IP);
|
|
if (err) {
|
|
apfs_err(sb, "failed to flush ip fq");
|
|
goto fail;
|
|
}
|
|
err = apfs_flush_free_queue(sb, APFS_SFQ_MAIN);
|
|
if (err) {
|
|
apfs_err(sb, "failed to flush main fq");
|
|
goto fail;
|
|
}
|
|
return 0;
|
|
|
|
fail:
|
|
spaceman->sm_raw = NULL;
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_write_spaceman - Write the in-memory spaceman fields to the disk buffer
|
|
* @sm: in-memory spaceman structure
|
|
*
|
|
* Copies the updated in-memory fields of the space manager into the on-disk
|
|
* structure; the buffer is not dirtied.
|
|
*/
|
|
static void apfs_write_spaceman(struct apfs_spaceman *sm)
|
|
{
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
struct apfs_spaceman_device *dev_raw = &sm_raw->sm_dev[APFS_SD_MAIN];
|
|
struct apfs_nxsb_info *nxi;
|
|
|
|
nxi = sm->sm_nxi;
|
|
ASSERT(le64_to_cpu(sm_raw->sm_o.o_xid) == nxi->nx_xid);
|
|
|
|
dev_raw->sm_free_count = cpu_to_le64(sm->sm_free_count);
|
|
}
|
|
|
|
/**
|
|
* apfs_ip_find_free - Find a free block inside the internal pool
|
|
* @sb: superblock structure
|
|
*
|
|
* Returns the block number for a free block, or 0 in case of corruption.
|
|
*/
|
|
static u64 apfs_ip_find_free(struct super_block *sb)
|
|
{
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
int blk_bitcnt = sb->s_blocksize * 8;
|
|
u64 full_bitcnt = le64_to_cpu(sm_raw->sm_ip_block_count);
|
|
u32 i;
|
|
|
|
for (i = 0; i < sm->sm_ip_bmaps_count; ++i) {
|
|
char *bitmap = sm->sm_ip_bmaps[i].block;
|
|
u64 off_in_bmap_blk, off_in_ip;
|
|
|
|
off_in_bmap_blk = find_next_zero_bit_le(bitmap, blk_bitcnt, 0 /* offset */);
|
|
if (off_in_bmap_blk >= blk_bitcnt) /* No space in this chunk */
|
|
continue;
|
|
|
|
/* We found something, confirm that it's not outside the ip */
|
|
off_in_ip = (i << sm->sm_ip_bmaps_shift) + off_in_bmap_blk;
|
|
if (off_in_ip >= full_bitcnt)
|
|
break;
|
|
return le64_to_cpu(sm_raw->sm_ip_base) + off_in_ip;
|
|
}
|
|
apfs_err(sb, "internal pool seems full");
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_chunk_find_free - Find a free block inside a chunk
|
|
* @sb: superblock structure
|
|
* @bitmap: allocation bitmap for the chunk, which should have free blocks
|
|
* @addr: number of the first block in the chunk
|
|
*
|
|
* Returns the block number for a free block, or 0 in case of corruption.
|
|
*/
|
|
static u64 apfs_chunk_find_free(struct super_block *sb, char *bitmap, u64 addr)
|
|
{
|
|
int bitcount = sb->s_blocksize * 8;
|
|
u64 bno;
|
|
|
|
bno = find_next_zero_bit_le(bitmap, bitcount, 0 /* offset */);
|
|
if (bno >= bitcount)
|
|
return 0;
|
|
return addr + bno;
|
|
}
|
|
|
|
/**
|
|
* apfs_ip_mark_used - Mark a block in the internal pool as used
|
|
* @sb: superblock strucuture
|
|
* @bno: block number (must belong to the ip)
|
|
*/
|
|
static void apfs_ip_mark_used(struct super_block *sb, u64 bno)
|
|
{
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
struct apfs_ip_bitmap_block_info *info = NULL;
|
|
|
|
bno -= le64_to_cpu(sm_raw->sm_ip_base);
|
|
info = &sm->sm_ip_bmaps[bno >> sm->sm_ip_bmaps_shift];
|
|
__set_bit_le(bno & sm->sm_ip_bmaps_mask, info->block);
|
|
info->dirty = true;
|
|
}
|
|
|
|
/**
|
|
* apfs_chunk_mark_used - Mark a block inside a chunk as used
|
|
* @sb: superblock structure
|
|
* @bitmap: allocation bitmap for the chunk
|
|
* @bno: block number (must belong to the chunk)
|
|
*/
|
|
static inline void apfs_chunk_mark_used(struct super_block *sb, char *bitmap,
|
|
u64 bno)
|
|
{
|
|
int bitcount = sb->s_blocksize * 8;
|
|
|
|
__set_bit_le(bno & (bitcount - 1), bitmap);
|
|
}
|
|
|
|
/**
|
|
* apfs_chunk_mark_free - Mark a block inside a chunk as free
|
|
* @sb: superblock structure
|
|
* @bitmap: allocation bitmap for the chunk
|
|
* @bno: block number (must belong to the chunk)
|
|
*/
|
|
static inline int apfs_chunk_mark_free(struct super_block *sb, char *bitmap,
|
|
u64 bno)
|
|
{
|
|
int bitcount = sb->s_blocksize * 8;
|
|
|
|
return __test_and_clear_bit_le(bno & (bitcount - 1), bitmap);
|
|
}
|
|
|
|
/**
|
|
* apfs_free_queue_try_insert - Try to add a block range to its free queue
|
|
* @sb: superblock structure
|
|
* @bno: first block number to free
|
|
* @count: number of consecutive blocks to free
|
|
*
|
|
* Same as apfs_free_queue_insert_nocache(), except that this one can also fail
|
|
* with -ENOSPC if there is no room for the new record.
|
|
*/
|
|
static int apfs_free_queue_try_insert(struct super_block *sb, u64 bno, u64 count)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
struct apfs_spaceman_free_queue *fq;
|
|
struct apfs_node *fq_root = NULL;
|
|
struct apfs_btree_info *fq_info = NULL;
|
|
struct apfs_query *query = NULL;
|
|
struct apfs_spaceman_free_queue_key raw_key;
|
|
bool ghost = count == 1;
|
|
int needed_room;
|
|
__le64 raw_val;
|
|
u64 node_count;
|
|
u16 node_limit;
|
|
unsigned int qid;
|
|
int err;
|
|
|
|
qid = apfs_block_in_ip(sm, bno) ? APFS_SFQ_IP : APFS_SFQ_MAIN;
|
|
fq = &sm_raw->sm_fq[qid];
|
|
|
|
fq_root = apfs_read_node(sb, le64_to_cpu(fq->sfq_tree_oid),
|
|
APFS_OBJ_EPHEMERAL, true /* write */);
|
|
if (IS_ERR(fq_root)) {
|
|
apfs_err(sb, "failed to read fq root 0x%llx", le64_to_cpu(fq->sfq_tree_oid));
|
|
return PTR_ERR(fq_root);
|
|
}
|
|
|
|
query = apfs_alloc_query(fq_root, NULL /* parent */);
|
|
if (!query) {
|
|
err = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
apfs_init_free_queue_key(nxi->nx_xid, bno, &query->key);
|
|
query->flags |= APFS_QUERY_FREE_QUEUE;
|
|
|
|
err = apfs_btree_query(sb, &query);
|
|
if (err && err != -ENODATA) {
|
|
apfs_err(sb, "query failed for xid 0x%llx, paddr 0x%llx", nxi->nx_xid, bno);
|
|
goto fail;
|
|
}
|
|
|
|
fq_info = (void *)fq_root->object.data + sb->s_blocksize - sizeof(*fq_info);
|
|
node_count = le64_to_cpu(fq_info->bt_node_count);
|
|
node_limit = le16_to_cpu(fq->sfq_tree_node_limit);
|
|
if (node_count == node_limit) {
|
|
needed_room = sizeof(raw_key) + (ghost ? 0 : sizeof(raw_val));
|
|
if (!apfs_node_has_room(query->node, needed_room, false /* replace */)) {
|
|
err = -ENOSPC;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
raw_key.sfqk_xid = cpu_to_le64(nxi->nx_xid);
|
|
raw_key.sfqk_paddr = cpu_to_le64(bno);
|
|
if (ghost) {
|
|
/* A lack of value (ghost record) means single-block extent */
|
|
err = apfs_btree_insert(query, &raw_key, sizeof(raw_key), NULL /* val */, 0 /* val_len */);
|
|
} else {
|
|
raw_val = cpu_to_le64(count);
|
|
err = apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
|
|
}
|
|
if (err) {
|
|
apfs_err(sb, "insertion failed for xid 0x%llx, paddr 0x%llx", nxi->nx_xid, bno);
|
|
goto fail;
|
|
}
|
|
|
|
if (!fq->sfq_oldest_xid)
|
|
fq->sfq_oldest_xid = cpu_to_le64(nxi->nx_xid);
|
|
le64_add_cpu(&fq->sfq_count, count);
|
|
|
|
if (qid == APFS_SFQ_MAIN)
|
|
sm->sm_main_fq_nodes = le64_to_cpu(fq_info->bt_node_count);
|
|
|
|
fail:
|
|
apfs_free_query(query);
|
|
apfs_node_free(fq_root);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_free_queue_insert_nocache - Add a block range to its free queue
|
|
* @sb: superblock structure
|
|
* @bno: first block number to free
|
|
* @count: number of consecutive blocks to free
|
|
*
|
|
* Same as apfs_free_queue_insert(), but writes to the free queue directly,
|
|
* bypassing the cache of the latest freed block range.
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
|
|
{
|
|
unsigned int qid;
|
|
int err;
|
|
|
|
err = apfs_free_queue_try_insert(sb, bno, count);
|
|
if (err == -ENOSPC) {
|
|
qid = apfs_block_in_ip(APFS_SM(sb), bno) ? APFS_SFQ_IP : APFS_SFQ_MAIN;
|
|
apfs_alert(sb, "free queue (%u) seems full - bug!", qid);
|
|
err = -EFSCORRUPTED;
|
|
}
|
|
if (err) {
|
|
apfs_err(sb, "fq insert failed (0x%llx-0x%llx)", bno, count);
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_free_queue_insert - Add a block range to its free queue
|
|
* @sb: superblock structure
|
|
* @bno: first block number to free
|
|
* @count: number of consecutive blocks to free
|
|
*
|
|
* Uses a cache to delay the actual tree operations as much as possible.
|
|
*
|
|
* Returns 0 on success or a negative error code in case of failure.
|
|
*/
|
|
int apfs_free_queue_insert(struct super_block *sb, u64 bno, u64 count)
|
|
{
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
int err;
|
|
|
|
if (sm->sm_free_cache_base == 0) {
|
|
/* Nothing yet cached */
|
|
sm->sm_free_cache_base = bno;
|
|
sm->sm_free_cache_blkcnt = count;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* First attempt to extend the cache of freed blocks, but never cache
|
|
* a range that doesn't belong to a single free queue.
|
|
*/
|
|
if (apfs_block_in_ip(sm, bno) == apfs_block_in_ip(sm, sm->sm_free_cache_base)) {
|
|
if (bno == sm->sm_free_cache_base + sm->sm_free_cache_blkcnt) {
|
|
sm->sm_free_cache_blkcnt += count;
|
|
return 0;
|
|
}
|
|
if (bno + count == sm->sm_free_cache_base) {
|
|
sm->sm_free_cache_base -= count;
|
|
sm->sm_free_cache_blkcnt += count;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Failed to extend the cache, so flush it and replace it */
|
|
err = apfs_free_queue_insert_nocache(sb, sm->sm_free_cache_base, sm->sm_free_cache_blkcnt);
|
|
if (err) {
|
|
apfs_err(sb, "fq cache flush failed (0x%llx-0x%llx)", sm->sm_free_cache_base, sm->sm_free_cache_blkcnt);
|
|
return err;
|
|
}
|
|
sm->sm_free_cache_base = bno;
|
|
sm->sm_free_cache_blkcnt = count;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_chunk_alloc_free - Allocate or free block in given CIB and chunk
|
|
* @sb: superblock structure
|
|
* @cib_bh: buffer head for the chunk-info block
|
|
* @index: index of this chunk's info structure inside @cib
|
|
* @bno: block number
|
|
* @is_alloc: true to allocate, false to free
|
|
*/
|
|
static int apfs_chunk_alloc_free(struct super_block *sb,
|
|
struct buffer_head **cib_bh,
|
|
int index, u64 *bno, bool is_alloc)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_chunk_info_block *cib;
|
|
struct apfs_chunk_info *ci;
|
|
struct buffer_head *bmap_bh = NULL;
|
|
char *bmap = NULL;
|
|
bool old_cib = false;
|
|
bool old_bmap = false;
|
|
int err = 0;
|
|
|
|
cib = (struct apfs_chunk_info_block *)(*cib_bh)->b_data;
|
|
ci = &cib->cib_chunk_info[index];
|
|
|
|
/* Cibs and bitmaps from old transactions can't be modified in place */
|
|
if (le64_to_cpu(cib->cib_o.o_xid) < nxi->nx_xid)
|
|
old_cib = true;
|
|
if (le64_to_cpu(ci->ci_xid) < nxi->nx_xid)
|
|
old_bmap = true;
|
|
if (is_alloc && le32_to_cpu(ci->ci_free_count) < 1)
|
|
return -ENOSPC;
|
|
|
|
/* Read the current bitmap, or allocate it if necessary */
|
|
if (!ci->ci_bitmap_addr) {
|
|
u64 bmap_bno;
|
|
|
|
if (!is_alloc) {
|
|
apfs_err(sb, "attempt to free block in all-free chunk");
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
/* All blocks in this chunk are free */
|
|
bmap_bno = apfs_ip_find_free(sb);
|
|
if (!bmap_bno) {
|
|
apfs_err(sb, "no free blocks in ip");
|
|
return -EFSCORRUPTED;
|
|
}
|
|
bmap_bh = apfs_sb_bread(sb, bmap_bno);
|
|
} else {
|
|
bmap_bh = apfs_sb_bread(sb, le64_to_cpu(ci->ci_bitmap_addr));
|
|
}
|
|
if (!bmap_bh) {
|
|
apfs_err(sb, "failed to read bitmap block");
|
|
return -EIO;
|
|
}
|
|
bmap = bmap_bh->b_data;
|
|
if (!ci->ci_bitmap_addr) {
|
|
memset(bmap, 0, sb->s_blocksize);
|
|
old_bmap = false;
|
|
}
|
|
|
|
/* Write the bitmap to its location for the next transaction */
|
|
if (old_bmap) {
|
|
struct buffer_head *new_bmap_bh;
|
|
u64 new_bmap_bno;
|
|
|
|
new_bmap_bno = apfs_ip_find_free(sb);
|
|
if (!new_bmap_bno) {
|
|
apfs_err(sb, "no free blocks in ip");
|
|
err = -EFSCORRUPTED;
|
|
goto fail;
|
|
}
|
|
|
|
new_bmap_bh = apfs_getblk(sb, new_bmap_bno);
|
|
if (!new_bmap_bh) {
|
|
apfs_err(sb, "failed to map new bmap block (0x%llx)", new_bmap_bno);
|
|
err = -EIO;
|
|
goto fail;
|
|
}
|
|
memcpy(new_bmap_bh->b_data, bmap, sb->s_blocksize);
|
|
err = apfs_free_queue_insert(sb, bmap_bh->b_blocknr, 1);
|
|
brelse(bmap_bh);
|
|
bmap_bh = new_bmap_bh;
|
|
if (err) {
|
|
apfs_err(sb, "free queue insertion failed");
|
|
goto fail;
|
|
}
|
|
bmap = bmap_bh->b_data;
|
|
}
|
|
apfs_ip_mark_used(sb, bmap_bh->b_blocknr);
|
|
|
|
/* Write the cib to its location for the next transaction */
|
|
if (old_cib) {
|
|
struct buffer_head *new_cib_bh;
|
|
u64 new_cib_bno;
|
|
|
|
new_cib_bno = apfs_ip_find_free(sb);
|
|
if (!new_cib_bno) {
|
|
apfs_err(sb, "no free blocks in ip");
|
|
err = -EFSCORRUPTED;
|
|
goto fail;
|
|
}
|
|
|
|
new_cib_bh = apfs_getblk(sb, new_cib_bno);
|
|
if (!new_cib_bh) {
|
|
apfs_err(sb, "failed to map new cib block (0x%llx)", new_cib_bno);
|
|
err = -EIO;
|
|
goto fail;
|
|
}
|
|
memcpy(new_cib_bh->b_data, (*cib_bh)->b_data, sb->s_blocksize);
|
|
err = apfs_free_queue_insert(sb, (*cib_bh)->b_blocknr, 1);
|
|
brelse(*cib_bh);
|
|
*cib_bh = new_cib_bh;
|
|
if (err) {
|
|
apfs_err(sb, "free queue insertion failed");
|
|
goto fail;
|
|
}
|
|
|
|
err = apfs_transaction_join(sb, *cib_bh);
|
|
if (err)
|
|
goto fail;
|
|
|
|
cib = (struct apfs_chunk_info_block *)(*cib_bh)->b_data;
|
|
ci = &cib->cib_chunk_info[index];
|
|
cib->cib_o.o_oid = cpu_to_le64(new_cib_bno);
|
|
cib->cib_o.o_xid = cpu_to_le64(nxi->nx_xid);
|
|
|
|
apfs_ip_mark_used(sb, new_cib_bno);
|
|
}
|
|
|
|
/* The chunk info can be updated now */
|
|
apfs_assert_in_transaction(sb, &cib->cib_o);
|
|
ci->ci_xid = cpu_to_le64(nxi->nx_xid);
|
|
le32_add_cpu(&ci->ci_free_count, is_alloc ? -1 : 1);
|
|
ci->ci_bitmap_addr = cpu_to_le64(bmap_bh->b_blocknr);
|
|
ASSERT(buffer_trans(*cib_bh));
|
|
set_buffer_csum(*cib_bh);
|
|
|
|
/* Finally, allocate / free the actual block that was requested */
|
|
if (is_alloc) {
|
|
*bno = apfs_chunk_find_free(sb, bmap, le64_to_cpu(ci->ci_addr));
|
|
if (!*bno) {
|
|
apfs_err(sb, "no free blocks in chunk");
|
|
err = -EFSCORRUPTED;
|
|
goto fail;
|
|
}
|
|
apfs_chunk_mark_used(sb, bmap, *bno);
|
|
sm->sm_free_count -= 1;
|
|
} else {
|
|
if (!apfs_chunk_mark_free(sb, bmap, *bno)) {
|
|
apfs_err(sb, "block already marked as free (0x%llx)", *bno);
|
|
le32_add_cpu(&ci->ci_free_count, -1);
|
|
set_buffer_csum(*cib_bh);
|
|
err = -EFSCORRUPTED;
|
|
} else
|
|
sm->sm_free_count += 1;
|
|
}
|
|
mark_buffer_dirty(bmap_bh);
|
|
|
|
fail:
|
|
brelse(bmap_bh);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_chunk_allocate_block - Allocate a single block from a chunk
|
|
* @sb: superblock structure
|
|
* @cib_bh: buffer head for the chunk-info block
|
|
* @index: index of this chunk's info structure inside @cib
|
|
* @bno: on return, the allocated block number
|
|
*
|
|
* Finds a free block in the chunk and marks it as used; the buffer at @cib_bh
|
|
* may be replaced if needed for copy-on-write. Returns 0 on success, or a
|
|
* negative error code in case of failure.
|
|
*/
|
|
static int apfs_chunk_allocate_block(struct super_block *sb,
|
|
struct buffer_head **cib_bh,
|
|
int index, u64 *bno)
|
|
{
|
|
return apfs_chunk_alloc_free(sb, cib_bh, index, bno, true);
|
|
}
|
|
|
|
/**
|
|
* apfs_cib_allocate_block - Allocate a single block from a cib
|
|
* @sb: superblock structure
|
|
* @cib_bh: buffer head for the chunk-info block
|
|
* @bno: on return, the allocated block number
|
|
* @backwards: start the search on the last chunk
|
|
*
|
|
* Finds a free block among all the chunks in the cib and marks it as used; the
|
|
* buffer at @cib_bh may be replaced if needed for copy-on-write. Returns 0 on
|
|
* success, or a negative error code in case of failure.
|
|
*/
|
|
static int apfs_cib_allocate_block(struct super_block *sb,
|
|
struct buffer_head **cib_bh, u64 *bno, bool backwards)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_chunk_info_block *cib;
|
|
u32 chunk_count;
|
|
int i;
|
|
|
|
cib = (struct apfs_chunk_info_block *)(*cib_bh)->b_data;
|
|
if (nxi->nx_flags & APFS_CHECK_NODES && !apfs_obj_verify_csum(sb, *cib_bh)) {
|
|
apfs_err(sb, "bad checksum for chunk-info block");
|
|
return -EFSBADCRC;
|
|
}
|
|
|
|
/* Avoid out-of-bounds operations on corrupted cibs */
|
|
chunk_count = le32_to_cpu(cib->cib_chunk_info_count);
|
|
if (chunk_count > sm->sm_chunks_per_cib) {
|
|
apfs_err(sb, "too many chunks in cib (%u)", chunk_count);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
for (i = 0; i < chunk_count; ++i) {
|
|
int index;
|
|
int err;
|
|
|
|
index = backwards ? chunk_count - 1 - i : i;
|
|
|
|
err = apfs_chunk_allocate_block(sb, cib_bh, index, bno);
|
|
if (err == -ENOSPC) /* This chunk is full */
|
|
continue;
|
|
if (err)
|
|
apfs_err(sb, "error during allocation");
|
|
return err;
|
|
}
|
|
return -ENOSPC;
|
|
}
|
|
|
|
/**
|
|
* apfs_spaceman_allocate_block - Allocate a single on-disk block
|
|
* @sb: superblock structure
|
|
* @bno: on return, the allocated block number
|
|
* @backwards: start the search on the last chunk
|
|
*
|
|
* Finds a free block among the spaceman bitmaps and marks it as used. Returns
|
|
* 0 on success, or a negative error code in case of failure.
|
|
*/
|
|
int apfs_spaceman_allocate_block(struct super_block *sb, u64 *bno, bool backwards)
|
|
{
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
int i;
|
|
|
|
for (i = 0; i < sm->sm_cib_count; ++i) {
|
|
struct buffer_head *cib_bh;
|
|
u64 cib_bno;
|
|
int index;
|
|
int err;
|
|
|
|
/* Keep extents and metadata separate to limit fragmentation */
|
|
index = backwards ? sm->sm_cib_count - 1 - i : i;
|
|
|
|
cib_bno = apfs_spaceman_read_cib_addr(sb, index);
|
|
cib_bh = apfs_sb_bread(sb, cib_bno);
|
|
if (!cib_bh) {
|
|
apfs_err(sb, "failed to read cib");
|
|
return -EIO;
|
|
}
|
|
|
|
err = apfs_cib_allocate_block(sb, &cib_bh, bno, backwards);
|
|
if (!err) {
|
|
/* The cib may have been moved */
|
|
apfs_spaceman_write_cib_addr(sb, index, cib_bh->b_blocknr);
|
|
/* The free block count has changed */
|
|
apfs_write_spaceman(sm);
|
|
}
|
|
brelse(cib_bh);
|
|
if (err == -ENOSPC) /* This cib is full */
|
|
continue;
|
|
if (err)
|
|
apfs_err(sb, "error during allocation");
|
|
return err;
|
|
}
|
|
/*
|
|
* We checked the free space before starting the transaction, so this
|
|
* isn't expected to happen.
|
|
*/
|
|
apfs_err(sb, "ran out of space during transaction");
|
|
return -ENOSPC;
|
|
}
|
|
|
|
/**
|
|
* apfs_chunk_free - Mark a regular block as free given CIB and chunk
|
|
* @sb: superblock structure
|
|
* @cib_bh: buffer head for the chunk-info block
|
|
* @index: index of this chunk's info structure inside @cib
|
|
* @bno: block number (must not belong to the ip)
|
|
*/
|
|
static int apfs_chunk_free(struct super_block *sb,
|
|
struct buffer_head **cib_bh,
|
|
int index, u64 bno)
|
|
{
|
|
return apfs_chunk_alloc_free(sb, cib_bh, index, &bno, false);
|
|
}
|
|
|
|
/**
|
|
* apfs_main_free - Mark a regular block as free
|
|
* @sb: superblock structure
|
|
* @bno: block number (must not belong to the ip)
|
|
*/
|
|
static int apfs_main_free(struct super_block *sb, u64 bno)
|
|
{
|
|
struct apfs_spaceman *sm = APFS_SM(sb);
|
|
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
|
struct apfs_sb_info *sbi = NULL;
|
|
u64 cib_idx, chunk_idx;
|
|
struct buffer_head *cib_bh;
|
|
u64 cib_bno;
|
|
int err, orphan_err;
|
|
|
|
if (!sm_raw->sm_blocks_per_chunk || !sm_raw->sm_chunks_per_cib) {
|
|
apfs_err(sb, "block or chunk count not set");
|
|
return -EINVAL;
|
|
}
|
|
/* TODO: use bitshifts instead of do_div() */
|
|
chunk_idx = bno;
|
|
do_div(chunk_idx, sm->sm_blocks_per_chunk);
|
|
cib_idx = chunk_idx;
|
|
chunk_idx = do_div(cib_idx, sm->sm_chunks_per_cib);
|
|
|
|
cib_bno = apfs_spaceman_read_cib_addr(sb, cib_idx);
|
|
cib_bh = apfs_sb_bread(sb, cib_bno);
|
|
if (!cib_bh) {
|
|
apfs_err(sb, "failed to read cib");
|
|
return -EIO;
|
|
}
|
|
|
|
err = apfs_chunk_free(sb, &cib_bh, chunk_idx, bno);
|
|
if (!err) {
|
|
/* The cib may have been moved */
|
|
apfs_spaceman_write_cib_addr(sb, cib_idx, cib_bh->b_blocknr);
|
|
/* The free block count has changed */
|
|
apfs_write_spaceman(sm);
|
|
}
|
|
brelse(cib_bh);
|
|
if (err) {
|
|
apfs_err(sb, "error during free");
|
|
return err;
|
|
}
|
|
|
|
/* It may be time to resume orphan cleanups, if we made enough room */
|
|
sbi = APFS_SB(sb);
|
|
orphan_err = atomic_read(&sbi->s_orphan_cleanup_err);
|
|
if (orphan_err == -ENOSPC && sm->sm_free_count >= 2 * APFS_DEL_ROOM) {
|
|
atomic_set(&sbi->s_orphan_cleanup_err, 0);
|
|
apfs_schedule_orphan_cleanup(sb);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_spaceman_get_free_blkcnt - Calculate the total number of free blocks
|
|
* @sb: filesystem superblock
|
|
* @blkcnt: on return, the total number of free blocks for all devices
|
|
*
|
|
* Can be called even if the spaceman has not been read (for example, on a
|
|
* read-only mount). Returns 0 on success, or a negative error code in case of
|
|
* failure.
|
|
*/
|
|
int apfs_spaceman_get_free_blkcnt(struct super_block *sb, u64 *blkcnt)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
|
struct apfs_nx_superblock *raw_sb = NULL;
|
|
struct apfs_spaceman_phys *sm_raw = NULL;
|
|
struct apfs_ephemeral_object_info *sm_eph_info = NULL;
|
|
struct apfs_spaceman_device *dev = NULL;
|
|
u64 oid;
|
|
int err;
|
|
|
|
if (!nxi->nx_eph_list) {
|
|
err = apfs_read_ephemeral_objects(sb);
|
|
if (err) {
|
|
apfs_err(sb, "failed to read the ephemeral objects");
|
|
return err;
|
|
}
|
|
}
|
|
|
|
raw_sb = nxi->nx_raw;
|
|
oid = le64_to_cpu(raw_sb->nx_spaceman_oid);
|
|
sm_eph_info = apfs_ephemeral_object_lookup(sb, oid);
|
|
if (IS_ERR(sm_eph_info)) {
|
|
apfs_err(sb, "no spaceman object for oid 0x%llx", oid);
|
|
return PTR_ERR(sm_eph_info);
|
|
}
|
|
sm_raw = (struct apfs_spaceman_phys *)sm_eph_info->object;
|
|
|
|
*blkcnt = 0;
|
|
dev = &sm_raw->sm_dev[APFS_SD_MAIN];
|
|
*blkcnt += le64_to_cpu(dev->sm_free_count);
|
|
dev = &sm_raw->sm_dev[APFS_SD_TIER2];
|
|
*blkcnt += le64_to_cpu(dev->sm_free_count);
|
|
return 0;
|
|
}
|