mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

The hfs_find_init() method can trigger the crash if tree pointer is NULL: [ 45.746290][ T9787] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000008: 0000 [#1] SMP KAI [ 45.747287][ T9787] KASAN: null-ptr-deref in range [0x0000000000000040-0x0000000000000047] [ 45.748716][ T9787] CPU: 2 UID: 0 PID: 9787 Comm: repro Not tainted 6.16.0-rc3 #10 PREEMPT(full) [ 45.750250][ T9787] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 45.751983][ T9787] RIP: 0010:hfs_find_init+0x86/0x230 [ 45.752834][ T9787] Code: c1 ea 03 80 3c 02 00 0f 85 9a 01 00 00 4c 8d 6b 40 48 c7 45 18 00 00 00 00 48 b8 00 00 00 00 00 fc [ 45.755574][ T9787] RSP: 0018:ffffc90015157668 EFLAGS: 00010202 [ 45.756432][ T9787] RAX: dffffc0000000000 RBX: 0000000000000000 RCX: ffffffff819a4d09 [ 45.757457][ T9787] RDX: 0000000000000008 RSI: ffffffff819acd3a RDI: ffffc900151576e8 [ 45.758282][ T9787] RBP: ffffc900151576d0 R08: 0000000000000005 R09: 0000000000000000 [ 45.758943][ T9787] R10: 0000000080000000 R11: 0000000000000001 R12: 0000000000000004 [ 45.759619][ T9787] R13: 0000000000000040 R14: ffff88802c50814a R15: 0000000000000000 [ 45.760293][ T9787] FS: 00007ffb72734540(0000) GS:ffff8880cec64000(0000) knlGS:0000000000000000 [ 45.761050][ T9787] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 45.761606][ T9787] CR2: 00007f9bd8225000 CR3: 000000010979a000 CR4: 00000000000006f0 [ 45.762286][ T9787] Call Trace: [ 45.762570][ T9787] <TASK> [ 45.762824][ T9787] hfs_ext_read_extent+0x190/0x9d0 [ 45.763269][ T9787] ? submit_bio_noacct_nocheck+0x2dd/0xce0 [ 45.763766][ T9787] ? __pfx_hfs_ext_read_extent+0x10/0x10 [ 45.764250][ T9787] hfs_get_block+0x55f/0x830 [ 45.764646][ T9787] block_read_full_folio+0x36d/0x850 [ 45.765105][ T9787] ? __pfx_hfs_get_block+0x10/0x10 [ 45.765541][ T9787] ? const_folio_flags+0x5b/0x100 [ 45.765972][ T9787] ? __pfx_hfs_read_folio+0x10/0x10 [ 45.766415][ T9787] filemap_read_folio+0xbe/0x290 [ 45.766840][ T9787] ? __pfx_filemap_read_folio+0x10/0x10 [ 45.767325][ T9787] ? __filemap_get_folio+0x32b/0xbf0 [ 45.767780][ T9787] do_read_cache_folio+0x263/0x5c0 [ 45.768223][ T9787] ? __pfx_hfs_read_folio+0x10/0x10 [ 45.768666][ T9787] read_cache_page+0x5b/0x160 [ 45.769070][ T9787] hfs_btree_open+0x491/0x1740 [ 45.769481][ T9787] hfs_mdb_get+0x15e2/0x1fb0 [ 45.769877][ T9787] ? __pfx_hfs_mdb_get+0x10/0x10 [ 45.770316][ T9787] ? find_held_lock+0x2b/0x80 [ 45.770731][ T9787] ? lockdep_init_map_type+0x5c/0x280 [ 45.771200][ T9787] ? lockdep_init_map_type+0x5c/0x280 [ 45.771674][ T9787] hfs_fill_super+0x38e/0x720 [ 45.772092][ T9787] ? __pfx_hfs_fill_super+0x10/0x10 [ 45.772549][ T9787] ? snprintf+0xbe/0x100 [ 45.772931][ T9787] ? __pfx_snprintf+0x10/0x10 [ 45.773350][ T9787] ? do_raw_spin_lock+0x129/0x2b0 [ 45.773796][ T9787] ? find_held_lock+0x2b/0x80 [ 45.774215][ T9787] ? set_blocksize+0x40a/0x510 [ 45.774636][ T9787] ? sb_set_blocksize+0x176/0x1d0 [ 45.775087][ T9787] ? setup_bdev_super+0x369/0x730 [ 45.775533][ T9787] get_tree_bdev_flags+0x384/0x620 [ 45.775985][ T9787] ? __pfx_hfs_fill_super+0x10/0x10 [ 45.776453][ T9787] ? __pfx_get_tree_bdev_flags+0x10/0x10 [ 45.776950][ T9787] ? bpf_lsm_capable+0x9/0x10 [ 45.777365][ T9787] ? security_capable+0x80/0x260 [ 45.777803][ T9787] vfs_get_tree+0x8e/0x340 [ 45.778203][ T9787] path_mount+0x13de/0x2010 [ 45.778604][ T9787] ? kmem_cache_free+0x2b0/0x4c0 [ 45.779052][ T9787] ? __pfx_path_mount+0x10/0x10 [ 45.779480][ T9787] ? getname_flags.part.0+0x1c5/0x550 [ 45.779954][ T9787] ? putname+0x154/0x1a0 [ 45.780335][ T9787] __x64_sys_mount+0x27b/0x300 [ 45.780758][ T9787] ? __pfx___x64_sys_mount+0x10/0x10 [ 45.781232][ T9787] do_syscall_64+0xc9/0x480 [ 45.781631][ T9787] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 45.782149][ T9787] RIP: 0033:0x7ffb7265b6ca [ 45.782539][ T9787] Code: 48 8b 0d c9 17 0d 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 [ 45.784212][ T9787] RSP: 002b:00007ffc0c10cfb8 EFLAGS: 00000206 ORIG_RAX: 00000000000000a5 [ 45.784935][ T9787] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007ffb7265b6ca [ 45.785626][ T9787] RDX: 0000200000000240 RSI: 0000200000000280 RDI: 00007ffc0c10d100 [ 45.786316][ T9787] RBP: 00007ffc0c10d190 R08: 00007ffc0c10d000 R09: 0000000000000000 [ 45.787011][ T9787] R10: 0000000000000048 R11: 0000000000000206 R12: 0000560246733250 [ 45.787697][ T9787] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000 [ 45.788393][ T9787] </TASK> [ 45.788665][ T9787] Modules linked in: [ 45.789058][ T9787] ---[ end trace 0000000000000000 ]--- [ 45.789554][ T9787] RIP: 0010:hfs_find_init+0x86/0x230 [ 45.790028][ T9787] Code: c1 ea 03 80 3c 02 00 0f 85 9a 01 00 00 4c 8d 6b 40 48 c7 45 18 00 00 00 00 48 b8 00 00 00 00 00 fc [ 45.792364][ T9787] RSP: 0018:ffffc90015157668 EFLAGS: 00010202 [ 45.793155][ T9787] RAX: dffffc0000000000 RBX: 0000000000000000 RCX: ffffffff819a4d09 [ 45.794123][ T9787] RDX: 0000000000000008 RSI: ffffffff819acd3a RDI: ffffc900151576e8 [ 45.795105][ T9787] RBP: ffffc900151576d0 R08: 0000000000000005 R09: 0000000000000000 [ 45.796135][ T9787] R10: 0000000080000000 R11: 0000000000000001 R12: 0000000000000004 [ 45.797114][ T9787] R13: 0000000000000040 R14: ffff88802c50814a R15: 0000000000000000 [ 45.798024][ T9787] FS: 00007ffb72734540(0000) GS:ffff8880cec64000(0000) knlGS:0000000000000000 [ 45.799019][ T9787] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 45.799822][ T9787] CR2: 00007f9bd8225000 CR3: 000000010979a000 CR4: 00000000000006f0 [ 45.800747][ T9787] Kernel panic - not syncing: Fatal exception The hfs_fill_super() calls hfs_mdb_get() method that tries to construct Extents Tree and Catalog Tree: HFS_SB(sb)->ext_tree = hfs_btree_open(sb, HFS_EXT_CNID, hfs_ext_keycmp); if (!HFS_SB(sb)->ext_tree) { pr_err("unable to open extent tree\n"); goto out; } HFS_SB(sb)->cat_tree = hfs_btree_open(sb, HFS_CAT_CNID, hfs_cat_keycmp); if (!HFS_SB(sb)->cat_tree) { pr_err("unable to open catalog tree\n"); goto out; } However, hfs_btree_open() calls read_mapping_page() that calls hfs_get_block(). And this method calls hfs_ext_read_extent(): static int hfs_ext_read_extent(struct inode *inode, u16 block) { struct hfs_find_data fd; int res; if (block >= HFS_I(inode)->cached_start && block < HFS_I(inode)->cached_start + HFS_I(inode)->cached_blocks) return 0; res = hfs_find_init(HFS_SB(inode->i_sb)->ext_tree, &fd); if (!res) { res = __hfs_ext_cache_extent(&fd, inode, block); hfs_find_exit(&fd); } return res; } The problem here that hfs_find_init() is trying to use HFS_SB(inode->i_sb)->ext_tree that is not initialized yet. It will be initailized when hfs_btree_open() finishes the execution. The patch adds checking of tree pointer in hfs_find_init() and it reworks the logic of hfs_btree_open() by reading the b-tree's header directly from the volume. The read_mapping_page() is exchanged on filemap_grab_folio() that grab the folio from mapping. Then, sb_bread() extracts the b-tree's header content and copy it into the folio. Reported-by: Wenzhi Wang <wenzhi.wang@uwaterloo.ca> Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com> cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de> cc: Yangtao Li <frank.li@vivo.com> cc: linux-fsdevel@vger.kernel.org Link: https://lore.kernel.org/r/20250710213657.108285-1-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
551 lines
14 KiB
C
551 lines
14 KiB
C
/*
|
|
* linux/fs/hfs/extent.c
|
|
*
|
|
* Copyright (C) 1995-1997 Paul H. Hargrove
|
|
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
|
* This file may be distributed under the terms of the GNU General Public License.
|
|
*
|
|
* This file contains the functions related to the extents B-tree.
|
|
*/
|
|
|
|
#include <linux/pagemap.h>
|
|
|
|
#include "hfs_fs.h"
|
|
#include "btree.h"
|
|
|
|
/*================ File-local functions ================*/
|
|
|
|
/*
|
|
* build_key
|
|
*/
|
|
static void hfs_ext_build_key(hfs_btree_key *key, u32 cnid, u16 block, u8 type)
|
|
{
|
|
key->key_len = 7;
|
|
key->ext.FkType = type;
|
|
key->ext.FNum = cpu_to_be32(cnid);
|
|
key->ext.FABN = cpu_to_be16(block);
|
|
}
|
|
|
|
/*
|
|
* hfs_ext_compare()
|
|
*
|
|
* Description:
|
|
* This is the comparison function used for the extents B-tree. In
|
|
* comparing extent B-tree entries, the file id is the most
|
|
* significant field (compared as unsigned ints); the fork type is
|
|
* the second most significant field (compared as unsigned chars);
|
|
* and the allocation block number field is the least significant
|
|
* (compared as unsigned ints).
|
|
* Input Variable(s):
|
|
* struct hfs_ext_key *key1: pointer to the first key to compare
|
|
* struct hfs_ext_key *key2: pointer to the second key to compare
|
|
* Output Variable(s):
|
|
* NONE
|
|
* Returns:
|
|
* int: negative if key1<key2, positive if key1>key2, and 0 if key1==key2
|
|
* Preconditions:
|
|
* key1 and key2 point to "valid" (struct hfs_ext_key)s.
|
|
* Postconditions:
|
|
* This function has no side-effects */
|
|
int hfs_ext_keycmp(const btree_key *key1, const btree_key *key2)
|
|
{
|
|
__be32 fnum1, fnum2;
|
|
__be16 block1, block2;
|
|
|
|
fnum1 = key1->ext.FNum;
|
|
fnum2 = key2->ext.FNum;
|
|
if (fnum1 != fnum2)
|
|
return be32_to_cpu(fnum1) < be32_to_cpu(fnum2) ? -1 : 1;
|
|
if (key1->ext.FkType != key2->ext.FkType)
|
|
return key1->ext.FkType < key2->ext.FkType ? -1 : 1;
|
|
|
|
block1 = key1->ext.FABN;
|
|
block2 = key2->ext.FABN;
|
|
if (block1 == block2)
|
|
return 0;
|
|
return be16_to_cpu(block1) < be16_to_cpu(block2) ? -1 : 1;
|
|
}
|
|
|
|
/*
|
|
* hfs_ext_find_block
|
|
*
|
|
* Find a block within an extent record
|
|
*/
|
|
u16 hfs_ext_find_block(struct hfs_extent *ext, u16 off)
|
|
{
|
|
int i;
|
|
u16 count;
|
|
|
|
for (i = 0; i < 3; ext++, i++) {
|
|
count = be16_to_cpu(ext->count);
|
|
if (off < count)
|
|
return be16_to_cpu(ext->block) + off;
|
|
off -= count;
|
|
}
|
|
/* panic? */
|
|
return 0;
|
|
}
|
|
|
|
static int hfs_ext_block_count(struct hfs_extent *ext)
|
|
{
|
|
int i;
|
|
u16 count = 0;
|
|
|
|
for (i = 0; i < 3; ext++, i++)
|
|
count += be16_to_cpu(ext->count);
|
|
return count;
|
|
}
|
|
|
|
static u16 hfs_ext_lastblock(struct hfs_extent *ext)
|
|
{
|
|
int i;
|
|
|
|
ext += 2;
|
|
for (i = 0; i < 2; ext--, i++)
|
|
if (ext->count)
|
|
break;
|
|
return be16_to_cpu(ext->block) + be16_to_cpu(ext->count);
|
|
}
|
|
|
|
static int __hfs_ext_write_extent(struct inode *inode, struct hfs_find_data *fd)
|
|
{
|
|
int res;
|
|
|
|
hfs_ext_build_key(fd->search_key, inode->i_ino, HFS_I(inode)->cached_start,
|
|
HFS_IS_RSRC(inode) ? HFS_FK_RSRC : HFS_FK_DATA);
|
|
res = hfs_brec_find(fd);
|
|
if (HFS_I(inode)->flags & HFS_FLG_EXT_NEW) {
|
|
if (res != -ENOENT)
|
|
return res;
|
|
/* Fail early and avoid ENOSPC during the btree operation */
|
|
res = hfs_bmap_reserve(fd->tree, fd->tree->depth + 1);
|
|
if (res)
|
|
return res;
|
|
hfs_brec_insert(fd, HFS_I(inode)->cached_extents, sizeof(hfs_extent_rec));
|
|
HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW);
|
|
} else {
|
|
if (res)
|
|
return res;
|
|
hfs_bnode_write(fd->bnode, HFS_I(inode)->cached_extents, fd->entryoffset, fd->entrylength);
|
|
HFS_I(inode)->flags &= ~HFS_FLG_EXT_DIRTY;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int hfs_ext_write_extent(struct inode *inode)
|
|
{
|
|
struct hfs_find_data fd;
|
|
int res = 0;
|
|
|
|
if (HFS_I(inode)->flags & HFS_FLG_EXT_DIRTY) {
|
|
res = hfs_find_init(HFS_SB(inode->i_sb)->ext_tree, &fd);
|
|
if (res)
|
|
return res;
|
|
res = __hfs_ext_write_extent(inode, &fd);
|
|
hfs_find_exit(&fd);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static inline int __hfs_ext_read_extent(struct hfs_find_data *fd, struct hfs_extent *extent,
|
|
u32 cnid, u32 block, u8 type)
|
|
{
|
|
int res;
|
|
|
|
hfs_ext_build_key(fd->search_key, cnid, block, type);
|
|
fd->key->ext.FNum = 0;
|
|
res = hfs_brec_find(fd);
|
|
if (res && res != -ENOENT)
|
|
return res;
|
|
if (fd->key->ext.FNum != fd->search_key->ext.FNum ||
|
|
fd->key->ext.FkType != fd->search_key->ext.FkType)
|
|
return -ENOENT;
|
|
if (fd->entrylength != sizeof(hfs_extent_rec))
|
|
return -EIO;
|
|
hfs_bnode_read(fd->bnode, extent, fd->entryoffset, sizeof(hfs_extent_rec));
|
|
return 0;
|
|
}
|
|
|
|
static inline int __hfs_ext_cache_extent(struct hfs_find_data *fd, struct inode *inode, u32 block)
|
|
{
|
|
int res;
|
|
|
|
if (HFS_I(inode)->flags & HFS_FLG_EXT_DIRTY) {
|
|
res = __hfs_ext_write_extent(inode, fd);
|
|
if (res)
|
|
return res;
|
|
}
|
|
|
|
res = __hfs_ext_read_extent(fd, HFS_I(inode)->cached_extents, inode->i_ino,
|
|
block, HFS_IS_RSRC(inode) ? HFS_FK_RSRC : HFS_FK_DATA);
|
|
if (!res) {
|
|
HFS_I(inode)->cached_start = be16_to_cpu(fd->key->ext.FABN);
|
|
HFS_I(inode)->cached_blocks = hfs_ext_block_count(HFS_I(inode)->cached_extents);
|
|
} else {
|
|
HFS_I(inode)->cached_start = HFS_I(inode)->cached_blocks = 0;
|
|
HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int hfs_ext_read_extent(struct inode *inode, u16 block)
|
|
{
|
|
struct hfs_find_data fd;
|
|
int res;
|
|
|
|
if (block >= HFS_I(inode)->cached_start &&
|
|
block < HFS_I(inode)->cached_start + HFS_I(inode)->cached_blocks)
|
|
return 0;
|
|
|
|
res = hfs_find_init(HFS_SB(inode->i_sb)->ext_tree, &fd);
|
|
if (!res) {
|
|
res = __hfs_ext_cache_extent(&fd, inode, block);
|
|
hfs_find_exit(&fd);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void hfs_dump_extent(struct hfs_extent *extent)
|
|
{
|
|
int i;
|
|
|
|
hfs_dbg(EXTENT, " ");
|
|
for (i = 0; i < 3; i++)
|
|
hfs_dbg_cont(EXTENT, " %u:%u",
|
|
be16_to_cpu(extent[i].block),
|
|
be16_to_cpu(extent[i].count));
|
|
hfs_dbg_cont(EXTENT, "\n");
|
|
}
|
|
|
|
static int hfs_add_extent(struct hfs_extent *extent, u16 offset,
|
|
u16 alloc_block, u16 block_count)
|
|
{
|
|
u16 count, start;
|
|
int i;
|
|
|
|
hfs_dump_extent(extent);
|
|
for (i = 0; i < 3; extent++, i++) {
|
|
count = be16_to_cpu(extent->count);
|
|
if (offset == count) {
|
|
start = be16_to_cpu(extent->block);
|
|
if (alloc_block != start + count) {
|
|
if (++i >= 3)
|
|
return -ENOSPC;
|
|
extent++;
|
|
extent->block = cpu_to_be16(alloc_block);
|
|
} else
|
|
block_count += count;
|
|
extent->count = cpu_to_be16(block_count);
|
|
return 0;
|
|
} else if (offset < count)
|
|
break;
|
|
offset -= count;
|
|
}
|
|
/* panic? */
|
|
return -EIO;
|
|
}
|
|
|
|
static int hfs_free_extents(struct super_block *sb, struct hfs_extent *extent,
|
|
u16 offset, u16 block_nr)
|
|
{
|
|
u16 count, start;
|
|
int i;
|
|
|
|
hfs_dump_extent(extent);
|
|
for (i = 0; i < 3; extent++, i++) {
|
|
count = be16_to_cpu(extent->count);
|
|
if (offset == count)
|
|
goto found;
|
|
else if (offset < count)
|
|
break;
|
|
offset -= count;
|
|
}
|
|
/* panic? */
|
|
return -EIO;
|
|
found:
|
|
for (;;) {
|
|
start = be16_to_cpu(extent->block);
|
|
if (count <= block_nr) {
|
|
hfs_clear_vbm_bits(sb, start, count);
|
|
extent->block = 0;
|
|
extent->count = 0;
|
|
block_nr -= count;
|
|
} else {
|
|
count -= block_nr;
|
|
hfs_clear_vbm_bits(sb, start + count, block_nr);
|
|
extent->count = cpu_to_be16(count);
|
|
block_nr = 0;
|
|
}
|
|
if (!block_nr || !i)
|
|
return 0;
|
|
i--;
|
|
extent--;
|
|
count = be16_to_cpu(extent->count);
|
|
}
|
|
}
|
|
|
|
int hfs_free_fork(struct super_block *sb, struct hfs_cat_file *file, int type)
|
|
{
|
|
struct hfs_find_data fd;
|
|
u32 total_blocks, blocks, start;
|
|
u32 cnid = be32_to_cpu(file->FlNum);
|
|
struct hfs_extent *extent;
|
|
int res, i;
|
|
|
|
if (type == HFS_FK_DATA) {
|
|
total_blocks = be32_to_cpu(file->PyLen);
|
|
extent = file->ExtRec;
|
|
} else {
|
|
total_blocks = be32_to_cpu(file->RPyLen);
|
|
extent = file->RExtRec;
|
|
}
|
|
total_blocks /= HFS_SB(sb)->alloc_blksz;
|
|
if (!total_blocks)
|
|
return 0;
|
|
|
|
blocks = 0;
|
|
for (i = 0; i < 3; i++)
|
|
blocks += be16_to_cpu(extent[i].count);
|
|
|
|
res = hfs_free_extents(sb, extent, blocks, blocks);
|
|
if (res)
|
|
return res;
|
|
if (total_blocks == blocks)
|
|
return 0;
|
|
|
|
res = hfs_find_init(HFS_SB(sb)->ext_tree, &fd);
|
|
if (res)
|
|
return res;
|
|
do {
|
|
res = __hfs_ext_read_extent(&fd, extent, cnid, total_blocks, type);
|
|
if (res)
|
|
break;
|
|
start = be16_to_cpu(fd.key->ext.FABN);
|
|
hfs_free_extents(sb, extent, total_blocks - start, total_blocks);
|
|
hfs_brec_remove(&fd);
|
|
total_blocks = start;
|
|
} while (total_blocks > blocks);
|
|
hfs_find_exit(&fd);
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* hfs_get_block
|
|
*/
|
|
int hfs_get_block(struct inode *inode, sector_t block,
|
|
struct buffer_head *bh_result, int create)
|
|
{
|
|
struct super_block *sb;
|
|
u16 dblock, ablock;
|
|
int res;
|
|
|
|
sb = inode->i_sb;
|
|
/* Convert inode block to disk allocation block */
|
|
ablock = (u32)block / HFS_SB(sb)->fs_div;
|
|
|
|
if (block >= HFS_I(inode)->fs_blocks) {
|
|
if (!create)
|
|
return 0;
|
|
if (block > HFS_I(inode)->fs_blocks)
|
|
return -EIO;
|
|
if (ablock >= HFS_I(inode)->alloc_blocks) {
|
|
res = hfs_extend_file(inode);
|
|
if (res)
|
|
return res;
|
|
}
|
|
} else
|
|
create = 0;
|
|
|
|
if (ablock < HFS_I(inode)->first_blocks) {
|
|
dblock = hfs_ext_find_block(HFS_I(inode)->first_extents, ablock);
|
|
goto done;
|
|
}
|
|
|
|
mutex_lock(&HFS_I(inode)->extents_lock);
|
|
res = hfs_ext_read_extent(inode, ablock);
|
|
if (!res)
|
|
dblock = hfs_ext_find_block(HFS_I(inode)->cached_extents,
|
|
ablock - HFS_I(inode)->cached_start);
|
|
else {
|
|
mutex_unlock(&HFS_I(inode)->extents_lock);
|
|
return -EIO;
|
|
}
|
|
mutex_unlock(&HFS_I(inode)->extents_lock);
|
|
|
|
done:
|
|
map_bh(bh_result, sb, HFS_SB(sb)->fs_start +
|
|
dblock * HFS_SB(sb)->fs_div +
|
|
(u32)block % HFS_SB(sb)->fs_div);
|
|
|
|
if (create) {
|
|
set_buffer_new(bh_result);
|
|
HFS_I(inode)->phys_size += sb->s_blocksize;
|
|
HFS_I(inode)->fs_blocks++;
|
|
inode_add_bytes(inode, sb->s_blocksize);
|
|
mark_inode_dirty(inode);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int hfs_extend_file(struct inode *inode)
|
|
{
|
|
struct super_block *sb = inode->i_sb;
|
|
u32 start, len, goal;
|
|
int res;
|
|
|
|
mutex_lock(&HFS_I(inode)->extents_lock);
|
|
if (HFS_I(inode)->alloc_blocks == HFS_I(inode)->first_blocks)
|
|
goal = hfs_ext_lastblock(HFS_I(inode)->first_extents);
|
|
else {
|
|
res = hfs_ext_read_extent(inode, HFS_I(inode)->alloc_blocks);
|
|
if (res)
|
|
goto out;
|
|
goal = hfs_ext_lastblock(HFS_I(inode)->cached_extents);
|
|
}
|
|
|
|
len = HFS_I(inode)->clump_blocks;
|
|
start = hfs_vbm_search_free(sb, goal, &len);
|
|
if (!len) {
|
|
res = -ENOSPC;
|
|
goto out;
|
|
}
|
|
|
|
hfs_dbg(EXTENT, "extend %lu: %u,%u\n", inode->i_ino, start, len);
|
|
if (HFS_I(inode)->alloc_blocks == HFS_I(inode)->first_blocks) {
|
|
if (!HFS_I(inode)->first_blocks) {
|
|
hfs_dbg(EXTENT, "first extents\n");
|
|
/* no extents yet */
|
|
HFS_I(inode)->first_extents[0].block = cpu_to_be16(start);
|
|
HFS_I(inode)->first_extents[0].count = cpu_to_be16(len);
|
|
res = 0;
|
|
} else {
|
|
/* try to append to extents in inode */
|
|
res = hfs_add_extent(HFS_I(inode)->first_extents,
|
|
HFS_I(inode)->alloc_blocks,
|
|
start, len);
|
|
if (res == -ENOSPC)
|
|
goto insert_extent;
|
|
}
|
|
if (!res) {
|
|
hfs_dump_extent(HFS_I(inode)->first_extents);
|
|
HFS_I(inode)->first_blocks += len;
|
|
}
|
|
} else {
|
|
res = hfs_add_extent(HFS_I(inode)->cached_extents,
|
|
HFS_I(inode)->alloc_blocks -
|
|
HFS_I(inode)->cached_start,
|
|
start, len);
|
|
if (!res) {
|
|
hfs_dump_extent(HFS_I(inode)->cached_extents);
|
|
HFS_I(inode)->flags |= HFS_FLG_EXT_DIRTY;
|
|
HFS_I(inode)->cached_blocks += len;
|
|
} else if (res == -ENOSPC)
|
|
goto insert_extent;
|
|
}
|
|
out:
|
|
mutex_unlock(&HFS_I(inode)->extents_lock);
|
|
if (!res) {
|
|
HFS_I(inode)->alloc_blocks += len;
|
|
mark_inode_dirty(inode);
|
|
if (inode->i_ino < HFS_FIRSTUSER_CNID)
|
|
set_bit(HFS_FLG_ALT_MDB_DIRTY, &HFS_SB(sb)->flags);
|
|
set_bit(HFS_FLG_MDB_DIRTY, &HFS_SB(sb)->flags);
|
|
hfs_mark_mdb_dirty(sb);
|
|
}
|
|
return res;
|
|
|
|
insert_extent:
|
|
hfs_dbg(EXTENT, "insert new extent\n");
|
|
res = hfs_ext_write_extent(inode);
|
|
if (res)
|
|
goto out;
|
|
|
|
memset(HFS_I(inode)->cached_extents, 0, sizeof(hfs_extent_rec));
|
|
HFS_I(inode)->cached_extents[0].block = cpu_to_be16(start);
|
|
HFS_I(inode)->cached_extents[0].count = cpu_to_be16(len);
|
|
hfs_dump_extent(HFS_I(inode)->cached_extents);
|
|
HFS_I(inode)->flags |= HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW;
|
|
HFS_I(inode)->cached_start = HFS_I(inode)->alloc_blocks;
|
|
HFS_I(inode)->cached_blocks = len;
|
|
|
|
res = 0;
|
|
goto out;
|
|
}
|
|
|
|
void hfs_file_truncate(struct inode *inode)
|
|
{
|
|
struct super_block *sb = inode->i_sb;
|
|
struct hfs_find_data fd;
|
|
u16 blk_cnt, alloc_cnt, start;
|
|
u32 size;
|
|
int res;
|
|
|
|
hfs_dbg(INODE, "truncate: %lu, %Lu -> %Lu\n",
|
|
inode->i_ino, (long long)HFS_I(inode)->phys_size,
|
|
inode->i_size);
|
|
if (inode->i_size > HFS_I(inode)->phys_size) {
|
|
struct address_space *mapping = inode->i_mapping;
|
|
void *fsdata = NULL;
|
|
struct folio *folio;
|
|
|
|
/* XXX: Can use generic_cont_expand? */
|
|
size = inode->i_size - 1;
|
|
res = hfs_write_begin(NULL, mapping, size + 1, 0, &folio,
|
|
&fsdata);
|
|
if (!res) {
|
|
res = generic_write_end(NULL, mapping, size + 1, 0, 0,
|
|
folio, fsdata);
|
|
}
|
|
if (res)
|
|
inode->i_size = HFS_I(inode)->phys_size;
|
|
return;
|
|
} else if (inode->i_size == HFS_I(inode)->phys_size)
|
|
return;
|
|
size = inode->i_size + HFS_SB(sb)->alloc_blksz - 1;
|
|
blk_cnt = size / HFS_SB(sb)->alloc_blksz;
|
|
alloc_cnt = HFS_I(inode)->alloc_blocks;
|
|
if (blk_cnt == alloc_cnt)
|
|
goto out;
|
|
|
|
mutex_lock(&HFS_I(inode)->extents_lock);
|
|
res = hfs_find_init(HFS_SB(sb)->ext_tree, &fd);
|
|
if (res) {
|
|
mutex_unlock(&HFS_I(inode)->extents_lock);
|
|
/* XXX: We lack error handling of hfs_file_truncate() */
|
|
return;
|
|
}
|
|
while (1) {
|
|
if (alloc_cnt == HFS_I(inode)->first_blocks) {
|
|
hfs_free_extents(sb, HFS_I(inode)->first_extents,
|
|
alloc_cnt, alloc_cnt - blk_cnt);
|
|
hfs_dump_extent(HFS_I(inode)->first_extents);
|
|
HFS_I(inode)->first_blocks = blk_cnt;
|
|
break;
|
|
}
|
|
res = __hfs_ext_cache_extent(&fd, inode, alloc_cnt);
|
|
if (res)
|
|
break;
|
|
start = HFS_I(inode)->cached_start;
|
|
hfs_free_extents(sb, HFS_I(inode)->cached_extents,
|
|
alloc_cnt - start, alloc_cnt - blk_cnt);
|
|
hfs_dump_extent(HFS_I(inode)->cached_extents);
|
|
if (blk_cnt > start) {
|
|
HFS_I(inode)->flags |= HFS_FLG_EXT_DIRTY;
|
|
break;
|
|
}
|
|
alloc_cnt = start;
|
|
HFS_I(inode)->cached_start = HFS_I(inode)->cached_blocks = 0;
|
|
HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW);
|
|
hfs_brec_remove(&fd);
|
|
}
|
|
hfs_find_exit(&fd);
|
|
mutex_unlock(&HFS_I(inode)->extents_lock);
|
|
|
|
HFS_I(inode)->alloc_blocks = blk_cnt;
|
|
out:
|
|
HFS_I(inode)->phys_size = inode->i_size;
|
|
HFS_I(inode)->fs_blocks = (inode->i_size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
|
|
inode_set_bytes(inode, HFS_I(inode)->fs_blocks << sb->s_blocksize_bits);
|
|
mark_inode_dirty(inode);
|
|
}
|