Skip to content
API Documentation
BastionGuard™
hfs_fork_io.hpp
Go to the documentation of this file.
1// hfs_fork_io.hpp -- read fork content via inline extents.
2//
3// A "fork" in HFS+ has 8 inline extent descriptors stored in the fork
4// data structure (HFSPlusForkData). For files / special files whose
5// content fits in <= 8 extents, that's all we need to read the entire
6// fork.
7//
8// In session 2 we ONLY support inline extents. Files with > 8 extents
9// also have records in the Extents Overflow file (special file #3),
10// which we will read in session 4.
11//
12// This file is the bridge between hfs_format.hpp (which defines the
13// fork data structure) and the disk reader (which gives us bytes).
14
15#pragma once
16
17#include "hfs_format.hpp"
18#include "disk_reader.hpp"
19
20#include <cstdint>
21#include <string>
22#include <vector>
23
24namespace hfs {
25
26// Read up to `max_bytes` from the start of a fork into `out`. Returns
27// false if a read fails midway. Stops at the smaller of:
28// - the fork's logical_size
29// - the requested max_bytes
30// - the data covered by the 8 inline extents (a soft truncate is
31// treated as a non-fatal warning logged via `logs`)
32//
33// `volume_offset` is the disk byte offset where the HFS+ volume starts
34// (0 for a raw image, partition_start for a partitioned disk).
35//
36// `block_size` is the volume's allocation block size from the volume
37// header.
38bool read_fork_inline(DiskReader& reader,
39 uint64_t volume_offset,
40 uint32_t block_size,
41 const ForkData& fork,
42 uint64_t max_bytes,
43 std::vector<unsigned char>& out,
44 std::vector<std::string>& logs);
45
46// Read a single allocation block from a fork at the given byte offset
47// inside the fork (logical, not physical). Walks the 8 inline extents
48// to find which one contains `fork_logical_offset` and reads from
49// there. Returns false on read failure or if the offset overflows the
50// inline-extent coverage.
51//
52// `block_buf` must have size `block_size`. On success, exactly
53// `block_size` bytes are written into it.
54bool read_fork_block_inline(DiskReader& reader,
55 uint64_t volume_offset,
56 uint32_t block_size,
57 const ForkData& fork,
58 uint64_t fork_logical_offset,
59 unsigned char* block_buf);
60
61// =============================================================================
62// Extents Overflow file (session 4)
63// =============================================================================
64//
65// When a fork has more than 8 extents, the first 8 are stored inline in
66// the catalog record's HFSPlusForkData; the rest live in records of the
67// Extents Overflow B-tree (special file #3, vh.extents_file).
68//
69// Key layout (10 bytes after the 2-byte keyLength):
70// __u8 forkType -- 0x00 = data fork, 0xFF = resource fork
71// __u8 pad
72// __be32 fileID
73// __be32 startBlock -- the FIRST logical block this record covers
74//
75// Records are leaf-only; each leaf record body is exactly 64 bytes:
76// HFSPlusExtentRecord = ForkExtent[8]
77//
78// For a file whose data fork has N > 8 extents:
79// - Records 0..7 inline in catalog
80// - Records 8..15 in overflow with key startBlock = 8
81// - Records 16..23 in overflow with key startBlock = 16
82// - ...
83// (the startBlock in the key counts BLOCKS, not bytes -- it's the
84// cumulative block count NOT the actual physical block number.)
85
86constexpr uint8_t kForkTypeData = 0x00;
87constexpr uint8_t kForkTypeResource = 0xFF;
88
90 uint8_t forkType {0};
91 uint32_t fileID {0};
92 uint32_t startBlock {0};
93};
94
95// Read up to `max_bytes` from a fork using BOTH inline extents and the
96// Extents Overflow file. Equivalent to read_fork_inline() except that
97// when a file has > 8 extents, the additional extents are looked up
98// in the overflow B-tree.
99//
100// `extents_file` is the volume's vh.extents_file (the overflow B-tree
101// content). We load it lazily via read_fork_inline() on first use.
102//
103// `extents_btree_buffer` (in/out): set to empty initially; the function
104// fills it on the first call by reading the overflow file and reuses it
105// on subsequent calls. This avoids re-reading the (possibly large)
106// overflow file once per recovered file.
107//
108// `forkType`: kForkTypeData or kForkTypeResource.
109// `fileID`: the file's CNID (HFSPlusCatalogFile.fileID).
110//
111// Returns true on success. If the overflow file itself can't be loaded,
112// the function falls back to the inline extents and logs a warning.
113bool read_fork_full(DiskReader& reader,
114 uint64_t volume_offset,
115 uint32_t block_size,
116 const ForkData& fork,
117 const ForkData& extents_file,
118 uint8_t forkType,
119 uint32_t fileID,
120 uint64_t max_bytes,
121 std::vector<unsigned char>& extents_btree_buffer,
122 std::vector<unsigned char>& out,
123 std::vector<std::string>& logs);
124
125} // namespace hfs
constexpr uint8_t kForkTypeResource
bool read_fork_full(DiskReader &reader, uint64_t volume_offset, uint32_t block_size, const ForkData &fork, const ForkData &extents_file, uint8_t forkType, uint32_t fileID, uint64_t max_bytes, std::vector< unsigned char > &extents_btree_buffer, std::vector< unsigned char > &out, std::vector< std::string > &logs)
bool read_fork_block_inline(DiskReader &reader, uint64_t volume_offset, uint32_t block_size, const ForkData &fork, uint64_t fork_logical_offset, unsigned char *block_buf)
constexpr uint8_t kForkTypeData
bool read_fork_inline(DiskReader &reader, uint64_t volume_offset, uint32_t block_size, const ForkData &fork, uint64_t max_bytes, std::vector< unsigned char > &out, std::vector< std::string > &logs)