mirror of
https://github.com/sgan81/apfs-fuse.git
synced 2026-09-30 12:15:41 +02:00
484 lines
9.5 KiB
C++
484 lines
9.5 KiB
C++
/*
|
|
This file is part of apfs-fuse, a read-only implementation of APFS
|
|
(Apple File System) for FUSE.
|
|
Copyright (C) 2017 Simon Gander
|
|
|
|
Apfs-fuse is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Apfs-fuse is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with apfs-fuse. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <memory>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <cstring>
|
|
|
|
#include <ApfsLib/Device.h>
|
|
#include <ApfsLib/Util.h>
|
|
#include <ApfsLib/DiskStruct.h>
|
|
#include <ApfsLib/BlockDumper.h>
|
|
#include <ApfsLib/GptPartitionMap.h>
|
|
|
|
#include "Dumper.h"
|
|
|
|
#ifdef __linux__
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
#undef RAW_VERBOSE
|
|
|
|
|
|
constexpr size_t BLOCKSIZE = 0x1000;
|
|
|
|
volatile bool g_abort = 0;
|
|
|
|
void DumpBlockTrunc(std::ostream &os, const uint8_t *data)
|
|
{
|
|
unsigned int sz = BLOCKSIZE - 1;
|
|
|
|
while (sz > 0 && data[sz] == 0)
|
|
sz--;
|
|
|
|
sz = (sz + 0x10) & 0xFFFFFFF0;
|
|
|
|
DumpHex(os, data, sz);
|
|
}
|
|
|
|
#if 0
|
|
|
|
void MapBlocks(std::ostream &os, Device &dev, uint64_t bid_start, uint64_t bcnt)
|
|
{
|
|
using namespace std;
|
|
|
|
uint64_t bid;
|
|
uint8_t block[BLOCKSIZE];
|
|
const APFS_ObjHeader * const blk = reinterpret_cast<const APFS_ObjHeader *>(block);
|
|
const APFS_TableHeader * const tbl = reinterpret_cast<const APFS_TableHeader *>(block + sizeof(APFS_ObjHeader));
|
|
bool last_was_used = false;
|
|
|
|
os << hex << uppercase << setfill('0');
|
|
|
|
os << "[Block] | oid | xid | type | subtype | Page | Levl | Entries | Description" << endl;
|
|
os << "---------+----------+----------+----------+----------+------+------+----------+---------------------------------" << endl;
|
|
|
|
for (bid = 0; bid < bcnt && !g_abort; bid++)
|
|
{
|
|
if ((bid & 0xFFF) == 0)
|
|
{
|
|
std::cout << '.';
|
|
std::cout.flush();
|
|
}
|
|
|
|
dev.Read(block, (bid_start + bid) * BLOCKSIZE, BLOCKSIZE);
|
|
|
|
if (IsEmptyBlock(block, BLOCKSIZE))
|
|
{
|
|
if (last_was_used)
|
|
os << "---------+----------+----------+----------+----------+------+------+----------+ Empty" << endl;
|
|
last_was_used = false;
|
|
continue;
|
|
}
|
|
|
|
if (VerifyBlock(block, BLOCKSIZE))
|
|
{
|
|
os << setw(8) << bid << " | ";
|
|
os << setw(8) << blk->oid << " | ";
|
|
os << setw(8) << blk->xid << " | ";
|
|
os << setw(8) << blk->type << " | ";
|
|
os << setw(8) << blk->subtype << " | ";
|
|
os << setw(4) << tbl->page << " | ";
|
|
os << setw(4) << tbl->level << " | ";
|
|
os << setw(8) << tbl->entries_cnt << " | ";
|
|
os << BlockDumper::GetNodeType(blk->type, blk->subtype);
|
|
if (APFS_OBJ_TYPE(blk->type) == 2)
|
|
os << " [Root]";
|
|
os << endl;
|
|
last_was_used = true;
|
|
}
|
|
else
|
|
{
|
|
os << setw(8) << bid;
|
|
os << " | | | | | | | | Data" << endl;
|
|
last_was_used = true;
|
|
}
|
|
}
|
|
|
|
os << endl;
|
|
}
|
|
|
|
void ScanBlocks(std::ostream &os, Device &dev, uint64_t bid_start, uint64_t bcnt)
|
|
{
|
|
BlockDumper bd(os, BLOCKSIZE);
|
|
uint64_t bid;
|
|
uint8_t block[BLOCKSIZE];
|
|
|
|
bd.SetTextFlags(0x00);
|
|
|
|
for (bid = 0; bid < bcnt && !g_abort; bid++)
|
|
{
|
|
if ((bid & 0xFFF) == 0)
|
|
{
|
|
std::cout << '.';
|
|
std::cout.flush();
|
|
}
|
|
|
|
dev.Read(block, (bid_start + bid) * BLOCKSIZE, BLOCKSIZE);
|
|
|
|
if (IsEmptyBlock(block, BLOCKSIZE))
|
|
continue;
|
|
|
|
if (VerifyBlock(block, BLOCKSIZE))
|
|
bd.DumpNode(block, bid);
|
|
else
|
|
{
|
|
#if 0
|
|
os << std::hex << std::setw(16) << blk_nr << std::endl;
|
|
DumpBlockTrunc(os, block);
|
|
os << std::endl;
|
|
os << "===========================================================================================================================" << std::endl;
|
|
os << std::endl;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void DumpSpaceman(std::ostream &os, Device &dev, uint64_t bid_start, uint64_t bcnt)
|
|
{
|
|
uint8_t sb[BLOCKSIZE];
|
|
uint8_t data[BLOCKSIZE];
|
|
BlockDumper bd(os, BLOCKSIZE);
|
|
const APFS_ObjHeader *bhdr;
|
|
const APFS_Spaceman *sm;
|
|
const APFS_NX_Superblock *nxsb;
|
|
uint64_t bid;
|
|
uint64_t cnt;
|
|
uint64_t smbmp_bid = 0;
|
|
uint64_t smbmp_cnt = 0;
|
|
uint64_t volbmp_bid = 0;
|
|
uint64_t volbmp_cnt = 0;
|
|
|
|
(void)bcnt;
|
|
|
|
if (!dev.Read(sb, bid_start * BLOCKSIZE, BLOCKSIZE))
|
|
return;
|
|
|
|
if (!VerifyBlock(sb, BLOCKSIZE))
|
|
return;
|
|
|
|
nxsb = reinterpret_cast<const APFS_NX_Superblock *>(sb);
|
|
if (nxsb->hdr.type != 0x80000001)
|
|
return;
|
|
|
|
if (nxsb->nx_magic != 0x4253584E)
|
|
return;
|
|
|
|
bd.DumpNode(sb, 0);
|
|
|
|
os << std::endl;
|
|
os << "Dumping Superblock Area" << std::endl;
|
|
os << std::endl;
|
|
|
|
bid = nxsb->nx_xp_desc_base;
|
|
cnt = nxsb->nx_xp_desc_blocks;
|
|
|
|
while (cnt > 0)
|
|
{
|
|
dev.Read(data, (bid_start + bid) * BLOCKSIZE, BLOCKSIZE);
|
|
bd.DumpNode(data, bid);
|
|
cnt--;
|
|
bid++;
|
|
}
|
|
|
|
os << std::endl;
|
|
os << "Dumping Spaceman Area" << std::endl;
|
|
os << std::endl;
|
|
|
|
bid = nxsb->nx_xp_data_base;
|
|
cnt = nxsb->nx_xp_data_blocks;
|
|
|
|
while (cnt > 0)
|
|
{
|
|
dev.Read(data, (bid_start + bid) * BLOCKSIZE, BLOCKSIZE);
|
|
bd.DumpNode(data, bid);
|
|
cnt--;
|
|
bid++;
|
|
|
|
bhdr = reinterpret_cast<const APFS_ObjHeader *>(data);
|
|
if (bhdr->type == 0x80000005)
|
|
{
|
|
sm = reinterpret_cast<const APFS_Spaceman *>(data);
|
|
smbmp_bid = sm->ip_bm_base_address;
|
|
smbmp_cnt = sm->ip_bitmap_block_count;
|
|
volbmp_bid = sm->ip_base_address;
|
|
volbmp_cnt = sm->ip_block_count;
|
|
}
|
|
}
|
|
|
|
bid = smbmp_bid;
|
|
cnt = smbmp_cnt;
|
|
|
|
while (cnt > 0)
|
|
{
|
|
dev.Read(data, (bid_start + bid) * BLOCKSIZE, BLOCKSIZE);
|
|
bd.DumpNode(data, bid);
|
|
cnt--;
|
|
bid++;
|
|
}
|
|
|
|
bid = volbmp_bid;
|
|
cnt = volbmp_cnt;
|
|
|
|
while (cnt > 0)
|
|
{
|
|
dev.Read(data, (bid_start + bid) * BLOCKSIZE, BLOCKSIZE);
|
|
bd.DumpNode(data, bid);
|
|
cnt--;
|
|
bid++;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static void ctrl_c_handler(int sig)
|
|
{
|
|
(void)sig;
|
|
g_abort = true;
|
|
}
|
|
|
|
#if 0
|
|
|
|
int main(int argc, const char *argv[])
|
|
{
|
|
if (argc < 3)
|
|
{
|
|
std::cerr << "Syntax: apfs-dump file.img output.txt [map.txt]" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
Device *dev;
|
|
std::ofstream os;
|
|
uint64_t bid_start = 0;
|
|
uint64_t bcnt = 0;
|
|
|
|
g_debug = 255;
|
|
|
|
#if defined(__linux__) || defined(__APPLE__)
|
|
signal(SIGINT, ctrl_c_handler);
|
|
#endif
|
|
|
|
dev = Device::OpenDevice(argv[1]);
|
|
|
|
if (!dev)
|
|
{
|
|
std::cerr << "Device " << argv[1] << " not found." << std::endl;
|
|
return 2;
|
|
}
|
|
|
|
{
|
|
GptPartitionMap pmap;
|
|
if (pmap.LoadAndVerify(*dev))
|
|
{
|
|
int partid = pmap.FindFirstAPFSPartition();
|
|
if (partid >= 0)
|
|
{
|
|
std::cout << "Dumping EFI partition" << std::endl;
|
|
pmap.GetPartitionOffsetAndSize(partid, bid_start, bcnt);
|
|
bid_start /= BLOCKSIZE;
|
|
bcnt /= BLOCKSIZE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bcnt == 0)
|
|
bcnt = dev->GetSize() / BLOCKSIZE;
|
|
|
|
if (argc > 3)
|
|
{
|
|
os.open(argv[3]);
|
|
if (!os.is_open())
|
|
{
|
|
std::cerr << "Could not open output file " << argv[3] << std::endl;
|
|
dev->Close();
|
|
delete dev;
|
|
return 3;
|
|
}
|
|
|
|
MapBlocks(os, *dev, bid_start, bcnt);
|
|
os.close();
|
|
}
|
|
|
|
os.open(argv[2]);
|
|
if (!os.is_open())
|
|
{
|
|
std::cerr << "Could not open output file " << argv[2] << std::endl;
|
|
dev->Close();
|
|
delete dev;
|
|
return 3;
|
|
}
|
|
|
|
DumpSpaceman(os, *dev, bid_start, bcnt);
|
|
ScanBlocks(os, *dev, bid_start, bcnt);
|
|
|
|
dev->Close();
|
|
os.close();
|
|
|
|
delete dev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
void usage()
|
|
{
|
|
std::cerr << "Syntax:" << std::endl;
|
|
std::cerr << "apfs-dump [-map mapfile.txt] file.img output.txt" << std::endl;
|
|
std::cerr << "apfs-dump [-map mapfile.txt] -fusion main.img tier2.img output.txt" << std::endl;
|
|
}
|
|
|
|
int main(int argc, const char *argv[])
|
|
{
|
|
const char *name_dev_main = 0;
|
|
const char *name_dev_tier2 = 0;
|
|
const char *name_map = 0;
|
|
const char *name_output = 0;
|
|
bool use_fusion = false;
|
|
|
|
int n;
|
|
int idx = 0;
|
|
|
|
for (n = 1; n < argc; n++)
|
|
{
|
|
if (!strcmp(argv[n], "-map"))
|
|
{
|
|
n++;
|
|
if (n >= argc)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
name_map = argv[n];
|
|
}
|
|
|
|
else if (!strcmp(argv[n], "-fusion"))
|
|
use_fusion = true;
|
|
|
|
else
|
|
{
|
|
switch (idx)
|
|
{
|
|
case 0:
|
|
name_dev_main = argv[n];
|
|
printf("main: %s\n", name_dev_main);
|
|
if (use_fusion)
|
|
idx++;
|
|
else
|
|
idx += 2;
|
|
break;
|
|
case 1:
|
|
name_dev_tier2 = argv[n];
|
|
printf("tier2: %s\n", name_dev_tier2);
|
|
idx++;
|
|
break;
|
|
case 2:
|
|
name_output = argv[n];
|
|
printf("out: %s\n", name_output);
|
|
idx++;
|
|
break;
|
|
default:
|
|
usage();
|
|
return 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((name_output == 0) || (use_fusion && name_dev_tier2 == 0))
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
std::unique_ptr<Device> dev_main;
|
|
std::unique_ptr<Device> dev_tier2;
|
|
std::ofstream os;
|
|
|
|
g_debug = 255;
|
|
|
|
#if defined(__linux__) || defined(__APPLE__)
|
|
signal(SIGINT, ctrl_c_handler);
|
|
#endif
|
|
|
|
dev_main.reset(Device::OpenDevice(name_dev_main));
|
|
if (use_fusion)
|
|
dev_tier2.reset(Device::OpenDevice(name_dev_tier2));
|
|
|
|
if (!dev_main)
|
|
{
|
|
std::cerr << "Device " << name_dev_main << " not found." << std::endl;
|
|
return 2;
|
|
}
|
|
|
|
if (use_fusion && !dev_tier2)
|
|
{
|
|
std::cerr << "Device " << name_dev_tier2 << " not found." << std::endl;
|
|
return 2;
|
|
}
|
|
|
|
{
|
|
Dumper dmp(dev_main.get(), dev_tier2.get());
|
|
|
|
if (!dmp.Initialize())
|
|
return -1;
|
|
|
|
#if 1
|
|
if (name_map)
|
|
{
|
|
os.open(name_map);
|
|
if (!os.is_open())
|
|
{
|
|
std::cerr << "Could not open map file " << name_map << std::endl;
|
|
dev_main->Close();
|
|
if (dev_tier2)
|
|
dev_tier2->Close();
|
|
return 3;
|
|
}
|
|
|
|
dmp.DumpBlockList(os);
|
|
os.close();
|
|
}
|
|
#endif
|
|
|
|
os.open(name_output);
|
|
if (!os.is_open())
|
|
{
|
|
std::cerr << "Could not open output file " << name_output << std::endl;
|
|
dev_main->Close();
|
|
if (dev_tier2)
|
|
dev_tier2->Close();
|
|
return 3;
|
|
}
|
|
|
|
dmp.DumpContainer(os);
|
|
}
|
|
|
|
dev_main->Close();
|
|
if (dev_tier2)
|
|
dev_tier2->Close();
|
|
os.close();
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|