/* * Copyright (c) 2014, Peter Rutenbar * 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 #include #include #include #include #include "../core/shoebill.h" /* --- Disk/partition management stuff --- */ #pragma mark Disk/partition management stuff typedef struct __attribute__ ((__packed__)) { 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 __attribute__ ((__packed__)) { 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 __attribute__ ((__packed__)) { 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 == fseeko(disk->f, block_size * blockno, SEEK_SET)); assert(fread(buf, block_size, 1, disk->f) == 1); } static void part_get_block(partition_t *part, uint8_t buf[512], uint32_t blockno) { assert(blockno < part->num_blocks); disk_get_block(part->disk, buf, part->start_block + blockno); } 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; inum_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; inum_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; inum_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 __attribute__ ((__packed__)) { 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 __attribute__ ((__packed__)) { 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; ipart, &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; (isize); 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; 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 __attribute__ ((__packed__)) { 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_r(path, "/", &last); elem; elem = strtok_r(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; ierror_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 __attribute__ ((__packed__)) { 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 __attribute__ ((__packed__)) { 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 __attribute__ ((__packed__)) { 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; ipart, &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; ifrag_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; ipool, 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 __attribute__ ((__packed__)) { 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_r(path, "/", &last); elem; elem = strtok_r(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; inum_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); 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); if (!buf) return 0; FILE *f = fopen("result", "wb"); fwrite(buf, size, 1, f); fclose(f); return 0; }*/