Skip to content

Instantly share code, notes, and snippets.

@neozeed
Created September 14, 2021 14:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neozeed/c9e9f6b07b673025fe40aa4c95955976 to your computer and use it in GitHub Desktop.
Save neozeed/c9e9f6b07b673025fe40aa4c95955976 to your computer and use it in GitHub Desktop.
heavily modified filesystem code from Shoebill to extract a file(s) from a raw SYSV filesystem
// Might work with MinGW/Linux? I am pretty sure I pulled the endian stuff, leaning on winsock. Maybe Linux will figure it out, not sure.
// Anyways Im too lazy to read how to init this stuff and bypass the apple partition map
// Instead I have it open an apple disk, and then when it's time to read a filesystem
// I close the file handle and patch it (lol) to open another filesystem.
// It doesn't work on the s1505_cp3540.dd image, but oddly enough I was able to get files
// from other 68k SYSVr2 and SYSVr3 filesystems without issue.
// Define JASON to turn on my horrible modifications
// As always the real hard work was all Peter's.
#define JASON
#pragma warning(suppress : 4996)
#pragma comment(lib, "ws2_32.lib")
#define _CRT_SECURE_NO_WARNINGS
/*
* Copyright (c) 2014, Peter Rutenbar <pruten@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <ws2tcpip.h>
///////////////////////////////////////////////////////////////////////////////////
// endian flipper
#define slog printf
#define fix_endian(x) do { \
switch (sizeof(x)) { \
case 1: break; \
case 2: (x) = ntohs(x); break; \
case 4: (x) = ntohl(x); break; \
case 8: (x) = ntohll(x); break; \
default: printf("bogus size"); \
}} while (0)
#ifdef __GNUC__
#define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
#endif
#ifdef _MSC_VER
#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop))
#endif
///////////////////////////////////////////////////////////////////////////////////
//Memory Allocator
///////////////////////////////////////////////////////////////////////////////////
#define POOL_START_MAGIC 0x231eb4af
#define POOL_END_MAGIC 0xb09f39f1
#define POOL_ALLOC_TYPE 0
#define POOL_CHILD_LINK 1
#define POOL_HEAD 2
typedef struct _alloc_pool_t {
uint32_t start_magic;
struct _alloc_pool_t* prev, * next;
union {
struct {
uint64_t size;
} alloc;
struct {
struct _alloc_pool_t* child; // pointer to the child's HEAD
} child_link;
struct {
struct _alloc_pool_t* parent_link; // pointer to the parent's CHILD_LINK
} head;
} t;
uint32_t type;
uint32_t end_magic;
} alloc_pool_t;
//////////////////////////////////////////////////////////////////////////////////
// alloc_pool.c
/////////////
static void _check_pool(alloc_pool_t* pool)
{
}
static alloc_pool_t* _ptr_to_header(void* ptr)
{
alloc_pool_t* apt = (alloc_pool_t*)ptr;
return &apt[-1];
}
void* p_alloc(alloc_pool_t* pool, uint64_t size)
{
alloc_pool_t* buf = calloc(sizeof(alloc_pool_t) + size, 1);
buf->type = POOL_ALLOC_TYPE;
buf->t.alloc.size = size;
buf->start_magic = POOL_START_MAGIC;
buf->end_magic = POOL_END_MAGIC;
buf->next = pool->next;
buf->prev = pool;
if (pool->next)
pool->next->prev = buf;
pool->next = buf;
return &buf[1];
}
void* p_realloc(void* ptr, uint64_t size)
{
alloc_pool_t* header = _ptr_to_header(ptr);
//assert(header->start_magic == POOL_START_MAGIC);
//assert(header->end_magic == POOL_END_MAGIC);
//assert(header->type == POOL_ALLOC_TYPE);
alloc_pool_t* new_header = realloc(header, size + sizeof(alloc_pool_t));
if (new_header) {
new_header->t.alloc.size = size;
if (new_header->next)
new_header->next->prev = new_header;
if (new_header->prev)
new_header->prev->next = new_header;
return &new_header[1];
}
return NULL;
}
/*
* Free *any* kind of alloc_pool_t header
*/
static void _p_free_any(alloc_pool_t* header)
{
//assert(header->start_magic == POOL_START_MAGIC);
//assert(header->end_magic == POOL_END_MAGIC);
if (header->next)
header->next->prev = header->prev;
if (header->prev)
header->prev->next = header->next;
free(header);
}
/*
* Free an alloc_pool allocation (but not HEAD or CHILD_LINK)
*/
void p_free(void* ptr)
{
alloc_pool_t* header = _ptr_to_header(ptr);
//assert(header->type == POOL_ALLOC_TYPE);
memset(ptr, 0xaa, header->t.alloc.size);
_p_free_any(header);
}
void p_free_pool(alloc_pool_t* pool)
{
while (pool->prev)
pool = pool->prev;
while (pool) {
alloc_pool_t* cur = pool;
pool = cur->next;
//assert(cur->start_magic == POOL_START_MAGIC);
//assert(cur->end_magic == POOL_END_MAGIC);
switch (cur->type) {
case POOL_ALLOC_TYPE:
_p_free_any(cur);
break;
case POOL_CHILD_LINK: {
// p_free_pool will free and unlink cur
// (its parent's CHILD_LINK)
p_free_pool(cur->t.child_link.child);
break;
}
case POOL_HEAD: {
if (cur->t.head.parent_link) {
//assert(cur->t.head.parent_link->type == POOL_CHILD_LINK);
_p_free_any(cur->t.head.parent_link);
}
_p_free_any(cur);
break;
}
default:
printf("unknown POOL_ type");
}
}
}
alloc_pool_t* p_new_pool(alloc_pool_t* parent_pool)
{
alloc_pool_t* pool = calloc(sizeof(alloc_pool_t), 1);
pool->start_magic = POOL_START_MAGIC;
pool->end_magic = POOL_END_MAGIC;
pool->type = POOL_HEAD;
if (parent_pool) {
alloc_pool_t* link = _ptr_to_header(p_alloc(parent_pool, 0));
link->type = POOL_CHILD_LINK;
link->t.child_link.child = pool; // child_link.child points to the child's HEAD
pool->t.head.parent_link = link; // head.parent_link points to the parent's CHILD_LINK
}
else
pool->t.head.parent_link = NULL;
return pool;
}
//////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
typedef struct PACK ( {
uint32_t magic;
uint8_t cluster;
uint8_t type;
uint16_t inode;
// no no no SLICErrr rrrrrCUR (NOPE, sys-V compiler orders these bits backwards)
// yes RUCrrrrr rrrSLICE
uint8_t dummy : 5;
uint8_t crit : 1;
uint8_t usr : 1;
uint8_t root : 1;
uint8_t slice : 5;
uint8_t dummy2 : 3;
uint16_t dummy3;
uint32_t tmade;
uint32_t tmount;
uint32_t tunmount;
/* "Alternate block map" */
uint32_t abm_size;
uint32_t abm_ents;
uint32_t abm_start;
}) block_zero_t;
typedef struct PACK({
uint8_t pmSig[2];
uint16_t pmSigPad;
uint32_t pmMapBlkCnt;
uint32_t pmPyPartStart;
uint32_t pmPartBlkCnt;
char pmPartName[32];
char pmPartType[32];
uint32_t pmLgDataStart;
uint32_t pmDataCnt;
uint32_t pmPartStatus;
uint32_t pmLgBootStart;
uint32_t pmBootSize;
uint32_t pmBootAddr;
uint32_t pmBootAddr2;
uint32_t pmBootEntry;
uint32_t pmBootEntry2;
uint32_t pmBootCksum;
char pmProcessor[16];
block_zero_t bz;
}) apple_partition_map_t;
typedef struct PACK( {
uint8_t sbSig[2]; // device signature
uint16_t sbBlkSize; // block size of the device
uint32_t sbBlkCount; // number of blocks on the device
uint16_t sbDevType; // reserved
uint16_t sbDevId; // reserved
uint32_t sbData; // reserved
uint16_t sbDrvrCount; // nnumber of driver descriptor entries
uint32_t ddBlock; // first driver's starting block
uint16_t ddSize; // size of the driver, in 512-byte blocks
uint16_t ddType; // operating system type (MacOS = 1)
}) driver_descriptor_record_t;
typedef struct {
struct _disk_t* disk;
char* error_str;
alloc_pool_t* pool;
uint32_t start_block, num_blocks;
char name[33], type[33];
} partition_t;
typedef struct _disk_t {
// -- fill these out --
const char* path;
char* error_str;
// -- "private" --
alloc_pool_t* pool;
FILE* f;
uint32_t block_size;
driver_descriptor_record_t ddr;
uint32_t num_partitions;
apple_partition_map_t* partition_maps;
partition_t* partitions;
} disk_t;
static void disk_get_block(disk_t* disk, uint8_t buf[512], uint32_t blockno)
{
const uint32_t block_size = disk->block_size;
//assert(0 == fseek(disk->f, block_size * blockno, SEEK_SET));
fseek(disk->f, block_size * blockno, SEEK_SET);
//assert(fread(buf, block_size, 1, disk->f) == 1);
fread(buf, block_size, 1, disk->f);
}
static void part_get_block(partition_t* part, uint8_t buf[512], uint32_t blockno)
{
uint32_t newblock;
#ifdef JASON
newblock = blockno - 1;
#else
newblock = blockno;
#endif
//assert(blockno < part->num_blocks);
disk_get_block(part->disk, buf, part->start_block + newblock);
}
static uint8_t disk_load_partition_map(disk_t* disk, apple_partition_map_t* apm, uint32_t idx)
{
uint8_t block[512];
disk_get_block(disk, block, 1 + idx);
memcpy(apm, block, sizeof(apple_partition_map_t));
fix_endian(apm->pmSigPad);
fix_endian(apm->pmMapBlkCnt);
fix_endian(apm->pmPyPartStart);
fix_endian(apm->pmPartBlkCnt);
fix_endian(apm->pmLgDataStart);
fix_endian(apm->pmDataCnt);
fix_endian(apm->pmPartStatus);
fix_endian(apm->pmLgBootStart);
fix_endian(apm->pmBootSize);
fix_endian(apm->pmBootAddr);
fix_endian(apm->pmBootAddr2);
fix_endian(apm->pmBootEntry);
fix_endian(apm->pmBootEntry2);
fix_endian(apm->pmBootCksum);
fix_endian(apm->bz.magic);
fix_endian(apm->bz.inode);
fix_endian(apm->bz.tmade);
fix_endian(apm->bz.tmount);
fix_endian(apm->bz.tunmount);
fix_endian(apm->bz.abm_size);
fix_endian(apm->bz.abm_ents);
fix_endian(apm->bz.abm_start);
if (memcmp(apm->pmSig, "PM", 2) != 0) {
sprintf(disk->error_str, "partition index %u has bad magic %02x%02x",
idx, apm->pmSig[0], apm->pmSig[1]);
return 0;
}
return 1;
}
static void close_disk(disk_t* disk)
{
fclose(disk->f);
p_free_pool(disk->pool);
}
static disk_t* open_disk(const char* disk_path, char* error_str)
{
disk_t* disk;
uint8_t block[512];
apple_partition_map_t apm;
uint32_t i;
alloc_pool_t* pool = p_new_pool(NULL);
FILE* f;
disk = p_alloc(pool, sizeof(disk_t));
disk->pool = pool;
disk->block_size = 512;
disk->error_str = error_str;
disk->path = disk_path;
f = fopen(disk_path, "rb");
if (f == NULL) {
sprintf(error_str, "Can't open that path");
goto fail;
}
disk->f = f;
// Load the driver descriptor record
disk_get_block(disk, block, 0);
memcpy(&disk->ddr, block, sizeof(disk->ddr));
fix_endian(disk->ddr.sbBlkSize);
fix_endian(disk->ddr.sbBlkCount);
fix_endian(disk->ddr.sbDevType);
fix_endian(disk->ddr.sbDevId);
fix_endian(disk->ddr.sbData);
fix_endian(disk->ddr.sbDrvrCount);
fix_endian(disk->ddr.ddBlock);
fix_endian(disk->ddr.ddSize);
fix_endian(disk->ddr.ddType);
// If the DDR block exists, (it doesn't have to necessarially)
if (memcmp(disk->ddr.sbSig, "ER", 2) == 0) {
// Can't handle non-512 byte block sizes
if (disk->ddr.sbBlkSize != 512) {
sprintf(error_str, "This disk uses blkSize=%u and I can't handle that",
disk->ddr.sbBlkSize);
goto fail;
}
}
// slog("sizeof(apple_part_map_t) = %lu\n", sizeof(apple_partition_map_t));
// Load the partition maps
if (!disk_load_partition_map(disk, &apm, 0))
goto fail;
else if ((apm.pmMapBlkCnt > 256) || (apm.pmMapBlkCnt == 0)) {
sprintf(error_str, "Crazy number of partitions on this disk %u", apm.pmMapBlkCnt);
goto fail;
}
disk->num_partitions = apm.pmMapBlkCnt;
disk->partition_maps = p_alloc(disk->pool, disk->num_partitions * sizeof(apple_partition_map_t));
disk->partitions = p_alloc(disk->pool, disk->num_partitions * sizeof(partition_t));
for (i = 0; i < disk->num_partitions; i++) {
if (!disk_load_partition_map(disk, &disk->partition_maps[i], i))
goto fail;
memset(&disk->partitions[i], 0, sizeof(partition_t));
disk->partitions[i].disk = disk;
disk->partitions[i].pool = disk->pool;
disk->partitions[i].error_str = error_str;
disk->partitions[i].start_block = disk->partition_maps[i].pmPyPartStart;
disk->partitions[i].num_blocks = disk->partition_maps[i].pmPartBlkCnt;
memcpy(disk->partitions[i].name, disk->partition_maps[i].pmPartName, 32);
memcpy(disk->partitions[i].type, disk->partition_maps[i].pmPartType, 32);
slog("%u type:%s name:%s\n", i, disk->partitions[i].type, disk->partitions[i].name);
slog("bz_magic=0x%08x slice=%u\n", disk->partition_maps[i].bz.magic, disk->partition_maps[i].bz.slice);
}
return disk;
fail:
if (f) fclose(f);
p_free_pool(pool);
return NULL;
}
/*static uint8_t translate_aux_to_apm_partition_num(disk_t *disk,
uint32_t aux_idx,
uint32_t *apm_idx)
{
uint32_t i, aux_i = 0;
for (i=0; i<disk->num_partitions; i++) {
partition_t *part = &disk->partitions[i];
if (strcmp("Apple_UNIX_SVR2", part->type) != 0)
continue;
else if (strstr(part->name, "Eschatology") != NULL)
continue;
else if (aux_i == aux_idx) {
*apm_idx = i;
return 1;
}
aux_i++;
}
return 0;
}*/
static int32_t find_root_partition_number(disk_t* disk, uint8_t clus_num)
{
/*
* See the man page for bzb for the full scoop.
* Basically, A/UX partition 0 is the first partition with
* ** type="Apple_UNIX_SVR2",
* ** a matching cluster number, and
* ** the root bit set
*
* A/UX partition 1 is the first partition with
* ** type="Apple_UNIX_SVR2",
* ** a matching cluster number, and
* ** the swap filesystem type
*
* A/UX partition 2 is the next partition with
* ** type="Apple_UNIX_SVR2",
* ** a matching cluster number
* ** and the user bit set
*
* The logic is probably even more arbitrary
* and complicated than that. Why doesn't A/UX
* just use the native APM partition numbers?
*/
uint32_t i;
for (i = 0; i < disk->num_partitions; i++) {
partition_t* part = &disk->partitions[i];
apple_partition_map_t* apm = &disk->partition_maps[i];
// slog("%u magic=0x%08x root=%u type=%s\n", i, apm->bz.magic, apm->bz.root, part->type);
if (apm->bz.magic != 0xabadbabe)
continue;
if (!apm->bz.root)
continue;
if (apm->bz.cluster != clus_num)
continue;
// slice==0 -> This partition doesn't prefer any particular slice number
// slice==N -> This partition prefers slice number (N-1)
// (I think)
/*if (apm->bz.slice != 0 && apm->bz.slice != 1)
continue;*/
if (strcmp("Apple_UNIX_SVR2", part->type) != 0)
continue;
return (int32_t)i;
}
return -1;
}
#pragma mark SVFS stuff
/* --- SVFS stuff --- */
typedef struct PACK( {
uint16_t isize;
uint32_t fsize;
uint16_t nfree;
uint32_t free[50];
uint16_t ninode;
uint16_t inode[100];
uint8_t flock;
uint8_t ilock;
uint8_t fmod;
uint8_t ronly;
uint32_t time;
uint16_t dinfo[4];
uint32_t tfree;
uint16_t tinode;
uint8_t fname[6];
uint8_t fpack[6];
uint32_t _unused[13];
uint32_t state;
uint16_t lasti;
uint16_t nbehind;
uint32_t magic;
uint32_t type;
}) svfs_superblock_t;
typedef struct PACK( {
uint16_t mode;
uint16_t nlink;
uint16_t uid;
uint16_t gid;
uint32_t size;
uint8_t __addr[39];
uint8_t gen;
uint32_t atime;
uint32_t mtime;
uint32_t ctime;
uint32_t addr[13];
} ) svfs_inode_t;
typedef struct {
char* error_str;
alloc_pool_t* pool;
partition_t* part;
svfs_superblock_t superblock;
uint32_t blocksize; // SVFS can use 512, 1024, 2048, 4096-byte block sizes
} svfs_t;
static uint8_t svfs_read_block(svfs_t* mount, uint8_t* block, uint32_t blockno)
{
const uint32_t sectors_per_block = mount->blocksize / 512;
const uint32_t start_sector = blockno * sectors_per_block;
uint32_t i;
// slog("sectors_per_block = %u, start_sector=%u\n", sectors_per_block, start_sector);
for (i = 0; i < sectors_per_block; i++) {
part_get_block(mount->part, &block[i * 512], start_sector + i);
}
return 1;
}
static uint8_t svfs_load_inode(svfs_t* mount, svfs_inode_t* inode, uint32_t inum)
{
uint32_t i;
uint8_t block[4096];
const uint32_t max_inode_blocks = mount->superblock.isize - 2;
const uint32_t max_inodes = (max_inode_blocks * mount->blocksize) / 64;
if (inum > max_inodes) {
sprintf(mount->error_str, "svfs_load_inode: inode %u too big (max=%u)", inum, max_inodes);
return 0;
}
const uint32_t inum_byte_idx_in_partition = ((inum - 1) * 64) + (2 * mount->blocksize);
const uint32_t inum_block = inum_byte_idx_in_partition / mount->blocksize;
const uint32_t inum_byte_idx_in_block = inum_byte_idx_in_partition % mount->blocksize;
if (!svfs_read_block(mount, block, inum_block))
return 0;
memcpy(inode, &block[inum_byte_idx_in_block], 64);
fix_endian(inode->mode);
fix_endian(inode->nlink);
fix_endian(inode->uid);
fix_endian(inode->gid);
fix_endian(inode->size);
fix_endian(inode->atime);
fix_endian(inode->mtime);
fix_endian(inode->ctime);
for (i = 0; i < 13; i++) {
uint32_t addr = inode->__addr[i * 3 + 0];
addr = (addr << 8) + inode->__addr[i * 3 + 1];
addr = (addr << 8) + inode->__addr[i * 3 + 2];
inode->addr[i] = addr;
}
return 1;
}
static uint8_t svfs_read_level(svfs_t* mount,
svfs_inode_t* inode,
uint8_t* buf,
uint32_t* len,
uint32_t* indirects,
uint32_t level)
{
uint8_t* tmp = p_alloc(mount->pool, mount->blocksize);
const uint32_t num_indirects = mount->blocksize / 4;
uint32_t i;
for (i = 0; (i < num_indirects) && (*len < inode->size); i++) {
uint32_t chunk_size = inode->size - *len;
if (chunk_size > mount->blocksize)
chunk_size = mount->blocksize;
const uint32_t addr = ntohl(indirects[i]);
if (!svfs_read_block(mount, tmp, addr)) {
sprintf(mount->error_str, "couldn't read svfs block num %u at L%u", addr, level);
goto fail;
}
if (level == 1) {
memcpy(buf + *len, tmp, chunk_size);
*len += chunk_size;
}
else {
if (!svfs_read_level(mount, inode, buf, len, (uint32_t*)tmp, level - 1))
goto fail;
}
}
p_free(tmp);
return 1;
fail:
p_free(tmp);
return 0;
}
static uint8_t* svfs_read_inode_data(svfs_t* mount, svfs_inode_t* inode)
{
uint8_t* tmp = p_alloc(mount->pool, mount->blocksize);
uint8_t* buf = p_alloc(mount->pool, inode->size);
uint32_t i, len = 0;
// The first 10 block pointers in the inode point to data
// The addr[10] is a L1 block pointer, [11] is L2, and [12] is L3
for (i = 0; (len < inode->size) && (i < 10); i++) {
uint32_t chunk_size = inode->size - len;
if (chunk_size > mount->blocksize)
chunk_size = mount->blocksize;
if (!svfs_read_block(mount, tmp, inode->addr[i])) {
sprintf(mount->error_str, "couldn't read svfs block num %u at L0", inode->addr[i]);
goto fail;
}
memcpy(buf + len, tmp, chunk_size);
len += chunk_size;
}
if (!svfs_read_block(mount, tmp, inode->addr[10])) {
sprintf(mount->error_str, "couldn't read svfs L1 block ptr %u", inode->addr[10]);
goto fail;
}
else if (!svfs_read_level(mount, inode, buf, &len, (uint32_t*)tmp, 1))
goto fail;
if (!svfs_read_block(mount, tmp, inode->addr[11])) {
sprintf(mount->error_str, "couldn't read svfs L2 block ptr %u", inode->addr[11]);
goto fail;
}
else if (!svfs_read_level(mount, inode, buf, &len, (uint32_t*)tmp, 2))
goto fail;
if (!svfs_read_block(mount, tmp, inode->addr[12])) {
sprintf(mount->error_str, "couldn't read svfs L3 block ptr %u", inode->addr[12]);
goto fail;
}
else if (!svfs_read_level(mount, inode, buf, &len, (uint32_t*)tmp, 3))
goto fail;
p_free(tmp);
return buf;
fail:
p_free(tmp);
p_free(buf);
return NULL;
}
static svfs_t* svfs_mount(partition_t* part)
{
//assert(sizeof(svfs_superblock_t) == 512);
uint32_t i;
svfs_t* mount = p_alloc(part->pool, sizeof(svfs_t));
mount->pool = part->pool;
mount->error_str = part->error_str;
mount->part = part;
#ifdef JASON
fclose(part->disk->f);
part->disk->f = fopen("c:\\temp\\aux07\\r3v8-s1.img", "rb");
part->start_block = 0;
printf("refile %d\n", part->disk->f);
#endif
part_get_block(part, (uint8_t*)&mount->superblock, 1);
fix_endian(mount->superblock.isize);
fix_endian(mount->superblock.fsize);
fix_endian(mount->superblock.nfree);
for (i = 0; i < 50; i++) {
fix_endian(mount->superblock.free[i]);
}
fix_endian(mount->superblock.ninode);
for (i = 0; i < 100; i++) {
fix_endian(mount->superblock.inode[i]);
}
fix_endian(mount->superblock.time);
for (i = 0; i < 4; i++) {
fix_endian(mount->superblock.dinfo[i]);
}
fix_endian(mount->superblock.tfree);
fix_endian(mount->superblock.tinode);
fix_endian(mount->superblock.state);
fix_endian(mount->superblock.lasti);
fix_endian(mount->superblock.nbehind);
fix_endian(mount->superblock.magic);
fix_endian(mount->superblock.type);
if (mount->superblock.magic != 0xfd187e20) {
sprintf(part->error_str, "Magic doesn't match svfs");
goto fail;
}
// It is SVFS!
const uint32_t type = mount->superblock.type;
if ((type != 1) && (type != 2) && (type != 4) && (type != 8)) {
sprintf(part->error_str, "Unknown SVFS type (%u)", type);
goto fail;
}
mount->blocksize = 512 * type;
return mount;
fail:
if (mount) p_free(mount);
return NULL;
}
typedef struct PACK( {
uint16_t inum;
char name[14];
} ) svfs_dir_entry_t;
svfs_inode_t* svfs_traverse_path(svfs_t* mount, const char* _path)
{
uint32_t i;
uint16_t inum = 2; // 2 == root
svfs_inode_t* inode = p_alloc(mount->pool, sizeof(svfs_inode_t));
char* path = p_alloc(mount->pool, strlen(_path) + 1);
strcpy(path, _path);
if (!svfs_load_inode(mount, inode, inum))
goto fail;
char* last, * elem;
for (elem = strtok(path, "/", &last);
elem;
elem = strtok(NULL, "/", &last)) {
//slog("elem = [%s]\n", elem);
const uint32_t num_entries = inode->size / 16;
//slog("inode size = %u\n", inode->size);
svfs_dir_entry_t* dir = (svfs_dir_entry_t*)svfs_read_inode_data(mount, inode);
if (!dir)
goto fail;
for (i = 0; i < num_entries; i++) {
if (strncmp(dir[i].name, elem, 14) == 0) {
inum = ntohs(dir[i].inum);
if (!svfs_load_inode(mount, inode, inum)) {
p_free(dir);
goto fail;
}
break;
}
}
p_free(dir);
if (i == num_entries) {
sprintf(mount->error_str, "'%s' in '%s' doesn't exist", elem, _path);
goto fail;
}
}
//slog("final inode size = %u\n", inode->size);
p_free(path);
return inode;
fail:
p_free(inode);
p_free(path);
return NULL;
}
/* --- UFS stuff --- */
#pragma mark UFS stuff
typedef struct PACK( {
uint32_t link;
uint32_t rlink;
uint32_t sblkno;
uint32_t cblkno;
uint32_t iblkno;
uint32_t dblkno;
uint32_t cgoffset;
uint32_t cgmask;
uint32_t time;
uint32_t size;
uint32_t dsize;
uint32_t ncg;
uint32_t bsize;
uint32_t fsize;
uint32_t frag;
uint32_t minfree;
uint32_t rotdelay;
uint32_t rps;
uint32_t bmask;
uint32_t fmask;
uint32_t bshift;
uint32_t fshift;
uint32_t maxcontig;
uint32_t maxbpg;
uint32_t fragshift;
uint32_t fsbtodb;
uint32_t sbsize;
uint32_t csmask;
uint32_t csshift;
uint32_t nindir;
uint32_t inopb;
uint32_t nspf;
uint32_t optim;
uint32_t dummy[2];
uint32_t state;
uint32_t id[2];
uint32_t csaddr;
uint32_t cssize;
uint32_t cgsize;
uint32_t ntrak;
uint32_t nsect;
uint32_t spc;
uint32_t ncyl;
uint32_t cpg;
uint32_t ipg;
uint32_t fpg;
uint32_t csum_ndir;
uint32_t csum_nbfree;
uint32_t csum_nifree;
uint32_t csum_nffree;
uint8_t fmod;
uint8_t clean;
uint8_t ronly;
uint8_t flags;
uint8_t fsmnt[500];
uint8_t fname[6];
uint8_t fpack[6];
uint32_t cgrotor;
uint32_t dummy2[32];
uint32_t cpc;
uint16_t postbl[32][8];
uint32_t magic;
} ) ufs_superblock_t;
typedef struct PACK( {
uint32_t link;
uint32_t rlink;
uint32_t time;
uint32_t cgx;
uint16_t ncyl;
uint16_t niblk;
uint32_t ndblk;
uint32_t csum_ndir;
uint32_t csum_nbfree;
uint32_t csum_nifree;
uint32_t csum_nffree;
uint32_t rotor;
uint32_t frotor;
uint32_t irotor;
uint32_t frsum[8];
uint32_t btot[32];
uint16_t b[32][8];
uint8_t iused[256];
uint32_t magic;
} ) ufs_cylinder_group_t;
typedef struct {
char* error_str;
alloc_pool_t* pool;
partition_t* part;
uint32_t frag_size;
uint32_t block_size;
uint32_t frag_per_block;
ufs_superblock_t superblock;
uint32_t num_groups;
ufs_cylinder_group_t* groups;
} ufs_t;
typedef struct PACK( {
uint16_t mode;
uint16_t nlink;
uint16_t uid;
uint16_t gid;
uint32_t size_hi; // UFS stores size as a uint64_t, but A/UX apparently only reads/writes
uint32_t size; // to the low bits, so sometimes the hi bits contain garbage
uint32_t atime;
uint32_t dummy;
uint32_t mtime;
uint32_t dummy2;
uint32_t ctime;
uint32_t dummy3;
uint32_t direct[12];
uint32_t indirect[3];
uint32_t flags;
uint32_t blocks;
uint32_t gen;
uint32_t dummy4[4];
} ) ufs_inode_t;
/*
* I truly don't understand the concept behind cgoffset/cgmask,
* but this is apparently the algorithm for finding the fragment-offset
* into a cylinder group.
*/
#define ufs_group_base(mount, num) ( \
((mount)->superblock.fpg * (num)) + \
((mount)->superblock.cgoffset * \
((num) & ~(mount)->superblock.cgmask)))
static uint8_t ufs_read_frag(ufs_t* mount, uint8_t* frag, uint32_t fragno)
{
const uint32_t sectors_per_frag = mount->frag_size / 512;
const uint32_t start_sector = fragno * sectors_per_frag;
uint32_t i;
for (i = 0; i < sectors_per_frag; i++) {
part_get_block(mount->part, &frag[i * 512], start_sector + i);
}
return 1;
}
static uint8_t ufs_read_block(ufs_t* mount, uint8_t* block, uint32_t blockno)
{
uint32_t i;
/*
* block numbers and fragment numbers are identical - they both refer
* to fragment numbers. But if we're reading a "block", then we're reading
* mount->frag_per_block fragments starting at that block address.
*/
//assert((blockno % mount->frag_per_block) == 0); // This had better align to a block boundary
for (i = 0; i < mount->frag_per_block; i++) {
if (!ufs_read_frag(mount, block + i * mount->frag_size, blockno + i))
return 0;
}
return 1;
}
static uint8_t ufs_load_cylinder_group(ufs_t* mount, uint32_t frag_offset, ufs_cylinder_group_t* group)
{
uint32_t numfrags = sizeof(ufs_cylinder_group_t) / mount->frag_size;
numfrags += ((sizeof(ufs_cylinder_group_t) % mount->frag_size) != 0);
uint8_t* buf = p_alloc(mount->pool, (numfrags + 1) * mount->frag_size);
uint32_t i;
for (i = 0; i <= numfrags; i++)
ufs_read_frag(mount, &buf[i * mount->frag_size], frag_offset + i);
memcpy(group, buf, sizeof(ufs_cylinder_group_t));
fix_endian(group->link);
fix_endian(group->rlink);
fix_endian(group->time);
fix_endian(group->cgx);
fix_endian(group->ncyl);
fix_endian(group->niblk);
fix_endian(group->ndblk);
fix_endian(group->csum_ndir);
fix_endian(group->csum_nbfree);
fix_endian(group->csum_nifree);
fix_endian(group->csum_nffree);
fix_endian(group->rotor);
fix_endian(group->frotor);
fix_endian(group->irotor);
for (i = 0; i < 8; i++)
fix_endian(group->frsum[i]);
for (i = 0; i < (32 * 8); i++)
fix_endian(group->b[i / 8][i % 8]);
fix_endian(group->magic);
p_free(buf);
return 1;
fail:
p_free(buf);
return 0;
}
static uint8_t ufs_load_inode(ufs_t* mount, ufs_inode_t* inode, uint32_t inum)
{
//assert(sizeof(ufs_inode_t) == 128);
/* Which cylinder group is this inode in? */
const uint32_t group_num = inum / mount->superblock.ipg;
/* Index of this inode in its cylinder group's inode table */
const uint32_t group_ino_offset = inum % mount->superblock.ipg;
/* Fragment address that contains inode */
const uint32_t frag_addr = ufs_group_base(mount, group_num) +
mount->superblock.iblkno +
((group_ino_offset * 128) / mount->frag_size);
/* Byte offset into the fragment where the inode begins */
const uint32_t frag_offset = (group_ino_offset * 128) % mount->frag_size;
uint32_t i;
uint8_t* buf = p_alloc(mount->pool, mount->frag_size);
// slog("group_num = %u, ino_offset=%u, addr = 0x%08x, offset = 0x%08x\n", group_num, group_ino_offset, frag_addr, frag_offset);
// slog("mount->superblock.iblkno = 0x%08x\n", mount->superblock.iblkno);
if (!ufs_read_frag(mount, buf, frag_addr))
goto fail;
memcpy(inode, buf + frag_offset, 128);
fix_endian(inode->mode);
fix_endian(inode->nlink);
fix_endian(inode->uid);
fix_endian(inode->gid);
fix_endian(inode->size_hi);
fix_endian(inode->size);
fix_endian(inode->atime);
fix_endian(inode->mtime);
fix_endian(inode->ctime);
for (i = 0; i < 12; i++)
fix_endian(inode->direct[i]);
for (i = 0; i < 3; i++)
fix_endian(inode->indirect[i]);
fix_endian(inode->flags);
fix_endian(inode->blocks);
fix_endian(inode->gen);
p_free(buf);
return 1;
fail:
if (buf)
p_free(buf);
return 0;
}
static uint8_t ufs_read_level(ufs_t* mount,
ufs_inode_t* inode,
uint8_t* buf,
uint64_t* len,
uint32_t indirect_blockno,
uint32_t level)
{
if (inode->size <= *len)
return 1;
uint32_t* table = p_alloc(mount->pool, mount->block_size);
uint8_t* block = NULL;
const uint32_t num_pointers = mount->block_size / 4;
uint32_t i;
if (!ufs_read_block(mount, (uint8_t*)table, indirect_blockno))
goto fail;
// for (i=0; i<num_pointers; i++)
// slog("%u 0x%08x\n", i, ntohl(table[i]));
if (level == 1)
block = p_alloc(mount->pool, mount->block_size);
for (i = 0; (i < num_pointers) && (inode->size > *len); i++) {
const uint32_t blockno = ntohl(table[i]);
if (level == 1) {
// direct block
uint64_t chunk_size = inode->size - *len;
if (chunk_size > mount->block_size) chunk_size = mount->block_size;
// Which block are we reading, and at which byte-offset into it does our data exist
const uint32_t block_addr = (blockno / mount->frag_per_block) * mount->frag_per_block;
const uint32_t block_offset = (blockno - block_addr) * mount->frag_size;
slog("L%u: raw_blkno=0x%08x len=0x%08x blockno:0x%08x chunk_size=0x%08x\n", level - 1, blockno, (uint32_t)*len, block_addr, (uint32_t)chunk_size);
// If the chunk_size is a whole block, then we better be reading in a whole block
if (chunk_size == mount->block_size) {
// slog("block_offset = 0x%x\n", block_offset);
//assert(block_offset == 0);
}
if (!ufs_read_block(mount, block, block_addr))
goto fail;
memcpy(buf + *len, block + block_offset, chunk_size);
(*len) += chunk_size;
}
else {
// indirect block
if (!ufs_read_level(mount, inode, buf, len, blockno, level - 1))
goto fail;
}
}
if (block)
p_free(block);
p_free(table);
return 1;
fail:
if (block)
p_free(block);
p_free(table);
return 0;
}
static uint8_t* ufs_read_inode_data(ufs_t* mount, ufs_inode_t* inode)
{
uint32_t i, j;
uint8_t* block = p_alloc(mount->pool, mount->block_size);
uint8_t* buf = p_alloc(mount->pool, inode->size);
uint64_t len = 0;
/* Read in direct blocks */
for (i = 0; (i < 12) && (len < inode->size); i++) {
// How many bytes are we reading from this block?
uint64_t chunk_size = inode->size - len;
if (chunk_size > mount->block_size) chunk_size = mount->block_size;
// Which block are we reading, and at which byte-offset into it does our data exist
const uint32_t block_addr = (inode->direct[i] / mount->frag_per_block) * mount->frag_per_block;
const uint32_t block_offset = (inode->direct[i] - block_addr) * mount->frag_size;
// slog("block_addr=0x%08x, block_offset")
// If the chunk_size is a whole block, then we better be reading in a whole block
// if (chunk_size == mount->block_size)
// assert(block_offset == 0);
if (!ufs_read_block(mount, block, block_addr))
goto fail;
memcpy(buf + len, block + block_offset, chunk_size);
len += chunk_size;
// slog("direct block %u = 0x%08x\n", i, inode->direct[i]);
}
for (i = 0; i < 3; i++) {
if (!ufs_read_level(mount, inode, buf, &len, inode->indirect[i], i + 1))
goto fail;
}
p_free(block);
return buf;
fail:
p_free(block);
p_free(buf);
return NULL;
}
typedef struct PACK( {
uint32_t inum;
uint16_t len;
uint16_t namelen;
char name[1];
} ) ufs_dir_t;
ufs_inode_t* ufs_traverse_path(ufs_t* mount, const char* _path)
{
uint32_t i;
uint32_t inum = 2; // 2 == root
ufs_inode_t* inode = p_alloc(mount->pool, sizeof(ufs_inode_t));
char* path = p_alloc(mount->pool, strlen(_path) + 1);
strcpy(path, _path);
if (!ufs_load_inode(mount, inode, inum))
goto fail;
char* last, * elem;
for (elem = strtok(path, "/", &last);
elem;
elem = strtok(NULL, "/", &last)) {
uint32_t next_inum = 0;
uint8_t* dir = ufs_read_inode_data(mount, inode);
if (!dir)
goto fail;
for (i = 0; inode->size; ) {
ufs_dir_t* entry = (ufs_dir_t*)&dir[i];
fix_endian(entry->inum);
fix_endian(entry->len);
fix_endian(entry->namelen);
if (entry->inum == 0)
break;
if ((entry->namelen == strlen(elem)) &&
(strncmp(elem, entry->name, entry->namelen) == 0)) {
next_inum = entry->inum;
break;
}
i += entry->len;
}
p_free(dir);
if (next_inum == 0) {
sprintf(mount->error_str, "'%s' in '%s' doesn't exist", elem, _path);
goto fail;
}
inum = next_inum;
if (!ufs_load_inode(mount, inode, inum))
goto fail;
}
p_free(path);
return inode;
fail:
p_free(inode);
p_free(path);
return NULL;
}
static ufs_t* ufs_mount(partition_t* part)
{
ufs_t* mount = p_alloc(part->pool, sizeof(ufs_t));
uint8_t* buf = p_alloc(part->pool, 32 * 512);
uint32_t i;
mount->pool = part->pool;
mount->part = part;
mount->error_str = part->error_str;
for (i = 0; i < 4; i++)
part_get_block(part, &buf[i * 512], 16 + i);
memcpy(&mount->superblock, buf, sizeof(ufs_superblock_t));
fix_endian(mount->superblock.link);
fix_endian(mount->superblock.rlink);
fix_endian(mount->superblock.sblkno);
fix_endian(mount->superblock.cblkno);
fix_endian(mount->superblock.iblkno);
fix_endian(mount->superblock.dblkno);
fix_endian(mount->superblock.cgoffset);
fix_endian(mount->superblock.cgmask);
fix_endian(mount->superblock.time);
fix_endian(mount->superblock.size);
fix_endian(mount->superblock.dsize);
fix_endian(mount->superblock.ncg);
fix_endian(mount->superblock.bsize);
fix_endian(mount->superblock.fsize);
fix_endian(mount->superblock.frag);
fix_endian(mount->superblock.minfree);
fix_endian(mount->superblock.rotdelay);
fix_endian(mount->superblock.rps);
fix_endian(mount->superblock.bmask);
fix_endian(mount->superblock.fmask);
fix_endian(mount->superblock.bshift);
fix_endian(mount->superblock.fshift);
fix_endian(mount->superblock.maxcontig);
fix_endian(mount->superblock.maxbpg);
fix_endian(mount->superblock.fragshift);
fix_endian(mount->superblock.fsbtodb);
fix_endian(mount->superblock.sbsize);
fix_endian(mount->superblock.csmask);
fix_endian(mount->superblock.csshift);
fix_endian(mount->superblock.nindir);
fix_endian(mount->superblock.inopb);
fix_endian(mount->superblock.nspf);
fix_endian(mount->superblock.optim);
fix_endian(mount->superblock.state);
fix_endian(mount->superblock.id[0]);
fix_endian(mount->superblock.id[1]);
fix_endian(mount->superblock.csaddr);
fix_endian(mount->superblock.cssize);
fix_endian(mount->superblock.cgsize);
fix_endian(mount->superblock.ntrak);
fix_endian(mount->superblock.nsect);
fix_endian(mount->superblock.spc);
fix_endian(mount->superblock.ncyl);
fix_endian(mount->superblock.cpg);
fix_endian(mount->superblock.ipg);
fix_endian(mount->superblock.fpg);
fix_endian(mount->superblock.csum_ndir);
fix_endian(mount->superblock.csum_nbfree);
fix_endian(mount->superblock.csum_nifree);
fix_endian(mount->superblock.csum_nffree);
fix_endian(mount->superblock.cgrotor);
fix_endian(mount->superblock.cpc);
for (i = 0; i < (32 * 8); i++)
fix_endian(mount->superblock.postbl[i / 8][i % 8]);
fix_endian(mount->superblock.magic);
if (mount->superblock.magic != 0x00011954) {
sprintf(part->error_str, "Magic doesn't match ufs");
goto fail;
}
// It is UFS!
mount->frag_size = mount->superblock.fsize;
mount->frag_per_block = mount->superblock.frag;
mount->block_size = mount->frag_size * mount->frag_per_block;
//assert(mount->block_size == mount->superblock.bsize);
mount->num_groups = mount->superblock.ncg;
mount->groups = (ufs_cylinder_group_t*)p_alloc(mount->pool,
mount->num_groups * sizeof(ufs_cylinder_group_t));
for (i = 0; i < mount->num_groups; i++) {
uint32_t group_base = ufs_group_base(mount, i) + mount->superblock.cblkno;
ufs_load_cylinder_group(mount, group_base, &mount->groups[i]);
if ((mount->groups[i].cgx != i) || (mount->groups[i].magic != 0x00090255)) {
sprintf(mount->error_str, "bad cylinder group %u frag_offset=0x%x", i, group_base);
goto fail;
}
}
if (buf)
p_free(buf);
return mount;
fail:
if (mount) {
if (mount->groups)
p_free(mount->groups);
p_free(mount);
}
if (buf)
p_free(buf);
return NULL;
}
/* --- Public interfaces --- */
#pragma mark Public interfaces
uint8_t* shoebill_extract_kernel(const char* disk_path, const char* kernel_path, char* error_str, uint32_t* len)
{
uint8_t* pool_data, * kernel_data = NULL;
disk_t* disk;
svfs_t* svfs_mount_obj;
ufs_t* ufs_mount_obj;
int32_t apm_part_num;
strcpy(error_str, "");
disk = open_disk(disk_path, error_str);
if (!disk)
goto done;
apm_part_num = find_root_partition_number(disk, 0);
if (apm_part_num == -1) {
sprintf(error_str, "Couldn't find root partition");
goto done;
}
slog("apm_part_num = %u\n", apm_part_num);
#ifdef JASONzzzz
/// ////////////////////////
{
svfs_mount_obj = svfs_mount(&disk->partitions[apm_part_num]);
for (int k = 2; k < 1000; k++) {
svfs_inode_t* inode = p_alloc(svfs_mount_obj->pool, sizeof(svfs_inode_t));
svfs_load_inode(svfs_mount_obj, inode, k);
uint8_t* pool_data = svfs_read_inode_data(svfs_mount_obj, inode);
if (pool_data && inode->size > 0)
{
char fname[64];
sprintf(fname, "c:\\temp\\result\\result%d", k);
FILE* f = fopen(fname, "wb");
printf("k %d has %d data\n", k, inode->size);
fwrite(pool_data, inode->size, 1, f);
fclose(f);
}
}
}
printf("\n");
return 0;
/// ////////////////////////
#endif
svfs_mount_obj = svfs_mount(&disk->partitions[apm_part_num]);
if (svfs_mount_obj) {
svfs_inode_t* inode = svfs_traverse_path(svfs_mount_obj, kernel_path);
if (!inode)
goto done;
pool_data = svfs_read_inode_data(svfs_mount_obj, inode);
if (!pool_data)
goto done;
kernel_data = malloc(inode->size);
memcpy(kernel_data, pool_data, inode->size);
*len = inode->size;
goto done;
}
ufs_mount_obj = ufs_mount(&disk->partitions[apm_part_num]);
if (ufs_mount_obj) {
ufs_inode_t* inode = ufs_traverse_path(ufs_mount_obj, kernel_path);
if (!inode)
goto done;
pool_data = ufs_read_inode_data(ufs_mount_obj, inode);
if (!pool_data)
goto done;
kernel_data = malloc(inode->size);
memcpy(kernel_data, pool_data, inode->size);
*len = inode->size;
goto done;
}
sprintf(error_str, "I can read the partition map, but the filesystem doesn't seem to be UFS or SVFS");
done:
if (strlen(error_str))
slog("error: [%s]\n", error_str);
if (disk)
close_disk(disk);
return kernel_data;
}
int main (int argc, char **argv)
{
uint8_t *buf;
uint32_t size;
char error_str[1024];
//buf = shoebill_extract_kernel(argv[1], argv[2], error_str, &size);
buf = shoebill_extract_kernel("c:\\temp\\aux07\\aux_beta_compacted.img", "/sysV68", error_str, &size);
if (!buf)
return 0;
FILE *f = fopen("result", "wb");
fwrite(buf, size, 1, f);
fclose(f);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment