7 Commits
Author SHA1 Message Date
Ernesto A. Fernández 5a2db9bd68 0.3.12
This release fixes a serious data corruption bug for containers bigger
than ~1.32 TiB, which is immediately triggered by a writable mount. This
goes to show why mounts are still read-only by default.

Another much smaller data loss bug for much bigger containers (~7 TiB)
is also addressed.

Support is added for the 6.12 kernel release candidate. A bit early this
time, so let's hope the build doesn't break again for the final release.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-11-01 20:06:39 -03:00
Ernesto A. Fernández 96e6d41bb7 Silence gcc warnings due to switch to min()
Building an old kernel (5.3) with an old version of gcc (4.9.2), I get
some warnings about "comparison of distinct pointer types". I'm pretty
sure this doesn't matter, and I don't even know if the blame lies with
the old compiler or the old kernel, but just silence this by making the
integer literals of the warning unsigned like the other argument.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-10-31 17:01:30 -03:00
Woody Suwalski 6c2ce34a67 Fix whitespace damage in apfs_aops definition
This has been around for a while so I guess checkpatch isn't picking up
on it for some reason.

[eafer: took this from a bigger patch, added commit message]
Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-10-31 16:46:20 -03:00
Ernesto A. Fernández a34220c93d Switch ->write_begin() and ->write_end() to folios
The build is broken for the 6.12 release candidate. The problem is that
->write_begin() and ->write_end() now take folios as parameters instead
of pages. The same is true, of course, of the associated
__block_write_begin() and generic_write_end(). Update the driver.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-10-31 16:02:06 -03:00
Ernesto A. Fernández 49507c65bc Implement ip bitmap rotation correctly
I never fully understood how the ip bitmap list was supposed to work,
but now I'm noticing that the difference between what I do and what the
official driver does are bad enough to quickly corrupt the list when a
container moves back and forth. Of course this is only an issue for
filesystems above 1.32 TiB or so, but it's still pretty serious.

So, I thought about it harder and I'm pretty sure I finally figured it
out: the array entries for each free bitmap are actually kept in a
linked list, that starts in sm_ip_bm_free_head and ends in
sm_ip_bm_free_tail. All entries outside the list (that is, the entries
for the used blocks) are marked with the invalid index 0xFFFF. The tail
itself also gets 0xFFFF even though it's free, because it has no next
entry - so that accounts for the constant off-by-one weirdness in the
number of free blocks.

So, implement all this stuff correctly. Stop assuming that all ip
bitmaps in use are consecutive, and stop rotating them all in lockstep
as soon as the transaction begins. Instead, read all ip bitmaps into
memory during the first transaction and keep them there, only tracking
if they have been dirtied. At commit time, allocate new blocks for the
dirty bitmaps and write them back to disk.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-10-29 21:13:46 -03:00
Ernesto A. Fernández dfdafd2a07 Wrap ephemeral objects around if there is no room
Currently, the driver doesn't allow multiblock ephemeral objects to wrap
around. When an object doesn't fit in the checkpoint data area, we just
leave some empty space and adjust the mappings accordingly. This seemed
like a simple and reasonable solution to me, but the official driver
actually demands that all ephemeral objects are kept consecutive (and in
the same order as their mappings, which we already do).

In practice this means that if you are working with a container with a
multiblock spaceman (say 7T in size), and if the unmount transaction
just so happens to be the one where the spaceman doesn't fit, then you
will be left with a container that is seen as corrupt by the official
driver. To make matters worse, checkpoint integrity is checked on mount,
so macos will actually mount the container no problem, but silently
discarding my final transaction.

This is all, of course, extremely unlikely. Such large containers are
rare, and checkpoint data areas for them are huge so a wrap-around takes
a while to happen. And it needs to happen precisely during the unmount
transaction, and that same transaction has to involve important data the
user doesn't want to lose. All in all it's not a serious issue, but get
it fixed.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-10-22 17:34:51 -03:00
Integral 2920180ed6 Remove unused macro MIN()/MAX() & replace them with min()/max() (#78) 2024-10-04 16:04:05 -03:00
9 changed files with 352 additions and 190 deletions

No files matched your search

+7 -1
View File
@@ -117,6 +117,11 @@ static inline bool apfs_node_has_fixed_kv_size(struct apfs_node *node)
return (node->flags & APFS_BTNODE_FIXED_KV_SIZE) != 0;
}
struct apfs_ip_bitmap_block_info {
bool dirty; /* Do we need to commit this block to disk? */
void *block; /* In-memory address of the bitmap block */
};
/*
* Space manager data in memory.
*/
@@ -147,7 +152,7 @@ struct apfs_spaceman {
/* Number of ip bitmaps */
u32 sm_ip_bmaps_count;
/* List of ip bitmaps, in order */
struct buffer_head *sm_ip_bmaps[];
struct apfs_ip_bitmap_block_info sm_ip_bmaps[];
};
#define APFS_TRANS_MAIN_QUEUE_MAX 10000
@@ -1066,6 +1071,7 @@ extern int apfs_read_spaceman(struct super_block *sb);
extern int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count);
extern int apfs_free_queue_insert(struct super_block *sb, u64 bno, u64 count);
extern int apfs_spaceman_allocate_block(struct super_block *sb, u64 *bno, bool backwards);
extern int apfs_write_ip_bitmaps(struct super_block *sb);
/* super.c */
extern int apfs_map_volume_super_bno(struct super_block *sb, u64 bno, bool check);
+1 -1
View File
@@ -1,5 +1,5 @@
PACKAGE_NAME="linux-apfs-rw"
PACKAGE_VERSION="0.3.11"
PACKAGE_VERSION="0.3.12"
BUILT_MODULE_NAME[0]="apfs"
DEST_MODULE_LOCATION[0]="/extra"
+2 -6
View File
@@ -8,10 +8,6 @@
#include <linux/blk_types.h>
#include "apfs.h"
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0)
#define MAX(X, Y) ((X) <= (Y) ? (Y) : (X))
#endif
/**
* apfs_ext_is_hole - Does this extent represent a hole in a sparse file?
* @extent: the extent to check
@@ -1975,7 +1971,7 @@ restart:
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.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;
@@ -2115,7 +2111,7 @@ restart:
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.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;
+31 -3
View File
@@ -565,7 +565,11 @@ int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t
}
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
err = __block_write_begin(page_folio(page), pos, len, apfs_get_new_block);
#else
err = __block_write_begin(page, pos, len, apfs_get_new_block);
#endif
if (err) {
apfs_err(sb, "CoW failed in inode 0x%llx", apfs_ino(inode));
goto out_put_page;
@@ -580,7 +584,11 @@ out_put_page:
return err;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
static int apfs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned int len,
struct folio **foliop, void **fsdata)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
static int apfs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned int len,
struct page **pagep, void **fsdata)
@@ -592,6 +600,10 @@ static int apfs_write_begin(struct file *file, struct address_space *mapping,
{
struct inode *inode = mapping->host;
struct super_block *sb = inode->i_sb;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
struct page *page = NULL;
struct page **pagep = &page;
#endif
int blkcount = (len + sb->s_blocksize - 1) >> inode->i_blkbits;
struct apfs_max_ops maxops;
int err;
@@ -615,6 +627,9 @@ static int apfs_write_begin(struct file *file, struct address_space *mapping,
err = __apfs_write_begin(file, mapping, pos, len, flags, pagep, fsdata);
if (err)
goto fail;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
*foliop = page_folio(page);
#endif
return 0;
fail:
@@ -628,7 +643,11 @@ int __apfs_write_end(struct file *file, struct address_space *mapping, loff_t po
struct apfs_dstream_info *dstream = &APFS_I(inode)->i_dstream;
int ret, err;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
ret = generic_write_end(file, mapping, pos, len, copied, page_folio(page), fsdata);
#else
ret = generic_write_end(file, mapping, pos, len, copied, page, fsdata);
#endif
dstream->ds_size = i_size_read(inode);
if (ret < len && pos + len > inode->i_size) {
truncate_pagecache(inode, inode->i_size);
@@ -641,13 +660,22 @@ int __apfs_write_end(struct file *file, struct address_space *mapping, loff_t po
return ret;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
static int apfs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned int len, unsigned int copied,
struct folio *folio, void *fsdata)
#else
static int apfs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned int len, unsigned int copied,
struct page *page, void *fsdata)
#endif
{
struct inode *inode = mapping->host;
struct super_block *sb = inode->i_sb;
struct apfs_nx_transaction *trans = &APFS_NXI(sb)->nx_transaction;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
struct page *page = &folio->page;
#endif
int ret, err;
ret = __apfs_write_end(file, mapping, pos, len, copied, page, fsdata);
@@ -693,9 +721,9 @@ static const struct address_space_operations apfs_aops = {
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
.readahead = apfs_readahead,
.readahead = apfs_readahead,
#else
.readpages = apfs_readpages,
.readpages = apfs_readpages,
#endif
.write_begin = apfs_write_begin,
-2
View File
@@ -12,8 +12,6 @@
#include <linux/string.h>
#include "libzbitmap.h"
#define MIN(x, y) ((x) > (y) ? (y) : (x))
#define ZBM_MAGIC "ZBM\x09"
#define ZBM_MAGIC_SZ 4
+262 -134
View File
File diff suppressed because it is too large. Load diff
+11 -2
View File
@@ -323,6 +323,8 @@ static inline void apfs_free_main_super(struct apfs_sb_info *sbi)
fmode_t mode = FMODE_READ | FMODE_EXCL;
#endif
struct apfs_ephemeral_object_info *eph_list = NULL;
struct apfs_spaceman *sm = NULL;
u32 bmap_idx;
int i;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
@@ -362,8 +364,15 @@ static inline void apfs_free_main_super(struct apfs_sb_info *sbi)
#endif
list_del(&nxi->nx_list);
kfree(nxi->nx_spaceman);
nxi->nx_spaceman = NULL;
sm = nxi->nx_spaceman;
if (sm) {
for (bmap_idx = 0; bmap_idx < sm->sm_ip_bmaps_count; ++bmap_idx) {
kfree(sm->sm_ip_bmaps[bmap_idx].block);
sm->sm_ip_bmaps[bmap_idx].block = NULL;
}
kfree(sm);
nxi->nx_spaceman = sm = NULL;
}
kfree(nxi);
out:
sbi->s_nxi = NULL;
+36 -35
View File
@@ -101,13 +101,18 @@ static bool apfs_transaction_has_room(struct super_block *sb, struct apfs_max_op
static int apfs_read_single_ephemeral_object(struct super_block *sb, struct apfs_checkpoint_mapping *map)
{
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_nx_superblock *raw_sb = NULL;
struct apfs_ephemeral_object_info *list = NULL;
struct buffer_head *bh = NULL;
char *object = NULL;
int count;
u32 size;
u64 bno, oid;
int err, i;
u32 data_blks, size;
u64 data_base, bno, oid;
int err, i, data_idx;
raw_sb = nxi->nx_raw;
data_base = le64_to_cpu(raw_sb->nx_xp_data_base);
data_blks = le32_to_cpu(raw_sb->nx_xp_data_blocks);
list = nxi->nx_eph_list;
count = nxi->nx_eph_count;
@@ -135,8 +140,9 @@ static int apfs_read_single_ephemeral_object(struct super_block *sb, struct apfs
if (!object)
return -ENOMEM;
data_idx = bno - data_base;
for (i = 0; i < size >> sb->s_blocksize_bits; ++i) {
bh = apfs_sb_bread(sb, bno + i);
bh = apfs_sb_bread(sb, data_base + data_idx);
if (!bh) {
apfs_err(sb, "failed to read ephemeral block");
err = -EIO;
@@ -145,6 +151,9 @@ static int apfs_read_single_ephemeral_object(struct super_block *sb, struct apfs
memcpy(object + (i << sb->s_blocksize_bits), bh->b_data, sb->s_blocksize);
brelse(bh);
bh = NULL;
/* Somewhat surprisingly, objects can wrap around */
if (++data_idx == data_blks)
data_idx = 0;
}
/*
@@ -464,18 +473,24 @@ int apfs_transaction_flush_all_inodes(struct super_block *sb)
static int apfs_write_single_ephemeral_object(struct super_block *sb, struct apfs_obj_phys *obj_raw, const struct apfs_checkpoint_mapping *map)
{
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_nx_superblock *raw_sb = NULL;
struct buffer_head *bh = NULL;
u64 bno;
u32 size;
int err, i;
u64 data_base, bno;
u32 data_blks, size;
int err, i, data_idx;
raw_sb = nxi->nx_raw;
data_base = le64_to_cpu(raw_sb->nx_xp_data_base);
data_blks = le32_to_cpu(raw_sb->nx_xp_data_blocks);
bno = le64_to_cpu(map->cpm_paddr);
size = le32_to_cpu(map->cpm_size);
obj_raw->o_xid = cpu_to_le64(nxi->nx_xid);
apfs_multiblock_set_csum((char *)obj_raw, size);
data_idx = bno - data_base;
for (i = 0; i < size >> sb->s_blocksize_bits; ++i) {
bh = apfs_getblk(sb, bno + i);
bh = apfs_getblk(sb, data_base + data_idx);
if (!bh) {
apfs_err(sb, "failed to map ephemeral block");
return -EIO;
@@ -489,6 +504,9 @@ static int apfs_write_single_ephemeral_object(struct super_block *sb, struct apf
memcpy(bh->b_data, (char *)obj_raw + (i << sb->s_blocksize_bits), sb->s_blocksize);
brelse(bh);
bh = NULL;
/* Somewhat surprisingly, objects can wrap around */
if (++data_idx == data_blks)
data_idx = 0;
}
return 0;
}
@@ -564,15 +582,6 @@ static int apfs_write_ephemeral_objects(struct super_block *sb)
eph_info = &nxi->nx_eph_list[i];
data_next = (data_index + data_len) % data_blks;
obj_blkcnt = eph_info->size >> sb->s_blocksize_bits;
if (obj_blkcnt > data_blks - data_next) {
/*
* This multiblock object does not fit in what's left
* of the ring buffer, so move it to the beginning and
* leave some empty blocks.
*/
data_len += data_blks - data_next;
data_next = 0;
}
err = apfs_create_cpoint_map(sb, cpm, eph_info->object, data_base + data_next, eph_info->size);
if (err) {
@@ -631,7 +640,6 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
struct apfs_nx_transaction *nx_trans = &nxi->nx_transaction;
struct apfs_bh_info *bhi, *tmp;
int err = 0;
u32 bmap_idx;
ASSERT(!(sb->s_flags & SB_RDONLY));
@@ -655,6 +663,16 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
}
sm->sm_free_cache_base = sm->sm_free_cache_blkcnt = 0;
}
/*
* Writing the ip bitmaps modifies the spaceman, so it must happen
* before we commit the ephemeral objects. It must also happen after we
* flush the free queue, in case the last freed range was in the ip.
*/
err = apfs_write_ip_bitmaps(sb);
if (err) {
apfs_err(sb, "failed to commit the ip bitmaps");
return err;
}
err = apfs_write_ephemeral_objects(sb);
if (err)
return err;
@@ -744,12 +762,6 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
return err;
}
for (bmap_idx = 0; bmap_idx < APFS_SM(sb)->sm_ip_bmaps_count; ++bmap_idx) {
brelse(APFS_SM(sb)->sm_ip_bmaps[bmap_idx]);
APFS_SM(sb)->sm_ip_bmaps[bmap_idx] = NULL;
}
APFS_SM(sb)->sm_raw = NULL;
nx_trans->t_starts_count = 0;
nx_trans->t_buffers_count = 0;
return 0;
@@ -934,8 +946,6 @@ void apfs_transaction_abort(struct super_block *sb)
struct apfs_nx_transaction *nx_trans = &nxi->nx_transaction;
struct apfs_bh_info *bhi, *tmp;
struct apfs_inode_info *ai, *ai_tmp;
struct apfs_spaceman *sm = NULL;
u32 bmap_idx;
if (sb->s_flags & SB_RDONLY) {
/* Transaction already aborted, do nothing */
@@ -963,15 +973,6 @@ void apfs_transaction_abort(struct super_block *sb)
kfree(bhi);
}
sm = APFS_SM(sb);
if (sm) {
for (bmap_idx = 0; bmap_idx < sm->sm_ip_bmaps_count; ++bmap_idx) {
brelse(sm->sm_ip_bmaps[bmap_idx]);
sm->sm_ip_bmaps[bmap_idx] = NULL;
}
APFS_SM(sb)->sm_raw = NULL;
}
/*
* It's not possible to undo in-memory changes from old operations in
* the aborted transaction. To avoid corruption, never write again.
+2 -6
View File
@@ -15,10 +15,6 @@
#include <linux/version.h>
#include "unicode.h"
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0)
#define MIN(X, Y) ((X) <= (Y) ? (X) : (Y))
#endif
/* The arrays of unicode data are defined at the bottom of the file */
/* TODO: would a single trie with all the data be more efficient? */
static u16 apfs_nfd_trie[];
@@ -237,7 +233,7 @@ static int apfs_get_normalization_length(const char *utf8str, unsigned int total
while (1) {
if (!total_len || !*utf8str)
return norm_len;
utf8len = utf8_to_utf32(utf8str, MIN(total_len, 4), &utf32char);
utf8len = utf8_to_utf32(utf8str, min(total_len, 4U), &utf32char);
if (utf8len < 0) /* Invalid unicode; don't normalize anything */
return 0;
@@ -309,7 +305,7 @@ new_starter:
unicode_t utf32char;
int utf8len, pos;
utf8len = utf8_to_utf32(utf8str, MIN(total_len, 4), &utf32char);
utf8len = utf8_to_utf32(utf8str, min(total_len, 4U), &utf32char);
for (pos = 0;; pos++, str_pos++) {
unicode_t utf32norm;
u8 ccc;