/* $Id: fsw_hfs.c 33540 2010-10-28 09:27:05Z vboxsync $ */ /** @file * fsw_hfs.c - HFS file system driver code, see * * http://developer.apple.com/technotes/tn/tn1150.html * * Current limitations: * - Doesn't support permissions * - Complete Unicode case-insensitiveness disabled (large tables) * - links -> now supported symlinks and hardlinks. No aliasis. * - Only supports pure HFS+ (i.e. no HFS, or HFS+ embedded to HFS) */ /* * Copyright (C) 2010 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ // improved by nms42: VolumeName, links. 2014 // Cleanup, tested and corrected by Slice #include "fsw_hfs.h" #include #include #define VBOXHFS_BTREE_BINSEARCH 0 #define DEBUG_HFS 0 #if DEBUG_HFS==2 #define DBG(...) AsciiPrint(__VA_ARGS__) #elif DEBUG_HFS==1 #define DBG(...) MemLog(TRUE, 1, __VA_ARGS__) #else #define DBG(...) #endif #if DEBUG_HFS == 2 #define CONCAT(x,y) x##y #define DPRINT(x) Print(CONCAT(L,x)) #define DPRINT2(x,y) Print(CONCAT(L,x), y) #define BP(msg) DPRINT(msg) #else #define CONCAT(x,y) x##y #define DPRINT(x) #define DPRINT2(x,y) #define BP(msg) #endif #if defined(__GNUC__) && __GNUC__ >= 9 #pragma GCC diagnostic ignored "-Waddress-of-packed-member" #endif static int hardlink = 0; static fsw_status_t fsw_hfs_volume_mount(struct fsw_hfs_volume *vol); static void fsw_hfs_volume_free(struct fsw_hfs_volume *vol); static fsw_status_t fsw_hfs_volume_stat(struct fsw_hfs_volume *vol, struct fsw_volume_stat *sb); static fsw_status_t fsw_hfs_dnode_fill(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno); static void fsw_hfs_dnode_free(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno); static fsw_status_t fsw_hfs_dnode_stat(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno, struct fsw_dnode_stat_str *sb); static fsw_status_t fsw_hfs_get_extent(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno, struct fsw_extent *extent); static fsw_status_t fsw_hfs_dir_lookup(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno, struct fsw_string *lookup_name, struct fsw_hfs_dnode **child_dno); static fsw_status_t fsw_hfs_dir_lookup_id(struct fsw_hfs_volume *vol, fsw_u32 lookup_id, file_info_t *file_info); static fsw_status_t fsw_hfs_dir_read(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno, struct fsw_shandle *shand, struct fsw_hfs_dnode **child_dno); #if 0 static fsw_status_t fsw_hfs_read_dirrec(struct fsw_shandle *shand, struct hfs_dirrec_buffer *dirrec_buffer); #endif static fsw_status_t fsw_hfs_readlink(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno, struct fsw_string *link); // // Dispatch Table // struct fsw_fstype_table FSW_FSTYPE_TABLE_NAME(hfs) = { { FSW_STRING_TYPE_ISO88591, 4, 4, "hfs" }, sizeof(struct fsw_hfs_volume), sizeof(struct fsw_hfs_dnode), fsw_hfs_volume_mount, // volume open fsw_hfs_volume_free, // volume close fsw_hfs_volume_stat, // volume info: total_bytes, free_bytes fsw_hfs_dnode_fill, //return FSW_SUCCESS; fsw_hfs_dnode_free, // empty fsw_hfs_dnode_stat, //size and times fsw_hfs_get_extent, // get the physical disk block number for the requested logical block number fsw_hfs_dir_lookup, //retrieve the directory entry with the given name fsw_hfs_dir_read, // next directory entry when reading a directory fsw_hfs_readlink, // ; }; static fsw_s32 fsw_hfs_read_block (struct fsw_hfs_dnode *dno, fsw_u32 log_bno, fsw_u32 off, fsw_s32 len, fsw_u8 *buf) { fsw_status_t status; struct fsw_extent extent; fsw_u32 phys_bno; fsw_u8 *buffer; extent.log_start = log_bno; status = fsw_hfs_get_extent(dno->g.vol, dno, &extent); if (status) return status; phys_bno = extent.phys_start; //Slice - increase cache level from 0 to 3 status = fsw_block_get(dno->g.vol, phys_bno, 3, (void **)&buffer); if (status) return status; fsw_memcpy(buf, buffer + off, len); fsw_block_release(dno->g.vol, phys_bno, buffer); return FSW_SUCCESS; } /* Read data from HFS file. */ static fsw_s32 fsw_hfs_read_file (struct fsw_hfs_dnode * dno, fsw_u64 pos, fsw_s32 len, fsw_u8 * buf) { fsw_status_t status; fsw_u32 log_bno; fsw_u32 block_size_bits = dno->g.vol->block_size_shift; fsw_u32 block_size = (1 << block_size_bits); fsw_u32 block_size_mask = block_size - 1; fsw_s32 read = 0; while (len > 0) { fsw_u32 off = (fsw_u32) (pos & block_size_mask); fsw_s32 next_len = len; log_bno = (fsw_u32)RShiftU64(pos, block_size_bits); if ((next_len >= 0) && ((fsw_u32)next_len > block_size)) { next_len = block_size; } status = fsw_hfs_read_block(dno, log_bno, off, next_len, buf); if (status) return -1; buf += next_len; pos += next_len; len -= next_len; read += next_len; } hardlink = 0; //this is static value that me must initialize sometimes return read; } static fsw_s32 fsw_hfs_compute_shift(fsw_u32 size) { fsw_s32 i; for (i = 0; i < 32; i++) { if ((size >> i) == 0) return i - 1; } return 0; } /** * Mount an HFS+ volume. Reads the superblock and constructs the * root directory dnode. */ static fsw_status_t fsw_hfs_volume_mount(struct fsw_hfs_volume *vol) { fsw_status_t status, rv; void *buffer = NULL; HFSPlusVolumeHeader *voldesc; fsw_u32 blockno; struct fsw_string s; HFSMasterDirectoryBlock* mdb; INTN i; rv = FSW_UNSUPPORTED; vol->primary_voldesc = NULL; fsw_set_blocksize(vol, HFS_BLOCKSIZE, HFS_BLOCKSIZE); blockno = HFS_SUPERBLOCK_BLOCKNO; #define CHECK(s) \ if (status) { \ rv = status; \ break; \ } vol->emb_block_off = 0; vol->hfs_kind = 0; do { fsw_u16 signature; BTHeaderRec tree_header; fsw_s32 r; fsw_u32 block_size; status = fsw_block_get(vol, blockno, 0, &buffer); CHECK(status); voldesc = (HFSPlusVolumeHeader *)buffer; mdb = (HFSMasterDirectoryBlock*)buffer; signature = be16_to_cpu(voldesc->signature); if ((signature == kHFSPlusSigWord) || (signature == kHFSXSigWord)) { //H+ or HX if (vol->hfs_kind == 0) { // DBG("found HFS+\n"); vol->hfs_kind = FSW_HFS_PLUS; } } else if (signature == kHFSSigWord) {// 'BD' // HFSMasterDirectoryBlock* mdb = (HFSMasterDirectoryBlock*)buffer; //VolumeName = mdb->drVN 28bytes if (be16_to_cpu(mdb->drEmbedSigWord) == kHFSPlusSigWord) { DBG("found HFS+ inside HFS, untested\n"); vol->hfs_kind = FSW_HFS_PLUS_EMB; vol->emb_block_off = be32_to_cpu(mdb->drEmbedExtent.startBlock); fsw_block_release (vol, blockno, buffer); blockno += vol->emb_block_off; /* retry */ continue; } else { DBG("found plain HFS, unsupported\n"); vol->hfs_kind = FSW_HFS_PLAIN; } rv = FSW_UNSUPPORTED; break; } else { rv = FSW_UNSUPPORTED; break; } status = fsw_memdup((void **)&vol->primary_voldesc, voldesc, sizeof(HFSPlusVolumeHeader)); CHECK(status); block_size = be32_to_cpu(voldesc->blockSize); vol->block_size_shift = fsw_hfs_compute_shift(block_size); // DBG("vol block_size=%d\n", block_size); fsw_block_release(vol, blockno, buffer); buffer = NULL; voldesc = NULL; fsw_set_blocksize(vol, block_size, block_size); /* get volume name */ for (i = kHFSMaxVolumeNameChars; i > 0; i--) if (mdb->drVN[i-1] != ' ') break; s.type = FSW_STRING_TYPE_ISO88591; s.size = s.len = (int)i; s.data = &mdb->drVN; //"HFS+ volume"; //fsw_status_t fsw_strdup_coerce(struct fsw_string *dest, int type, struct fsw_string *src) status = fsw_strdup_coerce(&vol->g.label, vol->g.host_string_type, &s); CHECK(status); DBG("HW Volume label:"); for (i=0; ig.label.data)[i]); } DBG("\n"); /* Setup catalog dnode */ status = fsw_dnode_create_root(vol, kHFSCatalogFileID, &vol->catalog_tree.file); CHECK(status); //Slice - why copy structure? fsw_memcpy (vol->catalog_tree.file->extents, vol->primary_voldesc->catalogFile.extents, sizeof vol->catalog_tree.file->extents); vol->catalog_tree.file->g.size = be64_to_cpu_ua(&vol->primary_voldesc->catalogFile.logicalSize); /* Setup extents overflow file */ /*status = */fsw_dnode_create_root(vol, kHFSExtentsFileID, &vol->extents_tree.file); fsw_memcpy (vol->extents_tree.file->extents, vol->primary_voldesc->extentsFile.extents, sizeof vol->extents_tree.file->extents); vol->extents_tree.file->g.size = be64_to_cpu_ua(&vol->primary_voldesc->extentsFile.logicalSize); /* Setup the root dnode */ status = fsw_dnode_create_root(vol, kHFSRootFolderID, &vol->g.root); CHECK(status); /* * Read catalog file, we know that first record is in the first node, right after * the node descriptor. */ r = fsw_hfs_read_file(vol->catalog_tree.file, sizeof (BTNodeDescriptor), sizeof (BTHeaderRec), (fsw_u8 *) &tree_header); if (r <= 0) { status = FSW_VOLUME_CORRUPTED; DBG("bad read catalog file\n"); break; } vol->case_sensitive = (signature == kHFSXSigWord) && (tree_header.keyCompareType == kHFSBinaryCompare); vol->catalog_tree.root_node = be32_to_cpu (tree_header.rootNode); vol->catalog_tree.node_size = be16_to_cpu (tree_header.nodeSize); //nms42 /* Take Volume Name before tree_header overwritten */ { fsw_u32 firstLeafNum; fsw_u64 catfOffset; fsw_u8 cbuff[sizeof (BTNodeDescriptor) + sizeof (HFSPlusCatalogKey)]; firstLeafNum = be32_to_cpu(tree_header.firstLeafNode); catfOffset = firstLeafNum * vol->catalog_tree.node_size; r = fsw_hfs_read_file(vol->catalog_tree.file, catfOffset, sizeof (cbuff), cbuff); if (r == sizeof (cbuff)) { BTNodeDescriptor *btnd; HFSPlusCatalogKey *ck; btnd = (BTNodeDescriptor*) cbuff; ck = (HFSPlusCatalogKey*) (cbuff + sizeof(BTNodeDescriptor)); // DBG(" btnd->kind ==%d expected -1\n", btnd->kind); // DBG(" ck->parentID ==%d expected 1\n", be32_to_cpu (ck->parentID)); if (btnd->kind == kBTLeafNode && be32_to_cpu_ua ((fsw_u32 *)(((UINT8 *)ck) + OFFSET_OF(HFSPlusCatalogKey, parentID))) == kHFSRootParentID) { struct fsw_string vn; vn.type = FSW_STRING_TYPE_UTF16_BE; vn.len = be16_to_cpu (ck->nodeName.length); vn.size = vn.len * sizeof (fsw_u16); vn.data = ck->nodeName.unicode; fsw_strfree (&vol->g.label); status = fsw_strdup_coerce(&vol->g.label, FSW_STRING_TYPE_UTF16, &vn); DBG("Volume label:"); for (i=0; ig.label.data)[i]); } DBG("\n"); CHECK(status); } } } /* Read extents overflow file */ r = fsw_hfs_read_file(vol->extents_tree.file, sizeof (BTNodeDescriptor), sizeof (BTHeaderRec), (fsw_u8 *) &tree_header); if (r != sizeof (BTHeaderRec)) { status = FSW_VOLUME_CORRUPTED; DBG("extents overflow file CORRUPTED\n"); break; } vol->extents_tree.root_node = be32_to_cpu (tree_header.rootNode); vol->extents_tree.node_size = be16_to_cpu (tree_header.nodeSize); rv = FSW_SUCCESS; break; } while (1); #undef CHECK if (buffer != NULL) { fsw_block_release(vol, blockno, buffer); } return rv; } /** * Free the volume data structure. Called by the core after an unmount or after * an unsuccessful mount to release the memory used by the file system type specific * part of the volume structure. */ static void fsw_hfs_volume_free(struct fsw_hfs_volume *vol) { if (vol->primary_voldesc) { fsw_free(vol->primary_voldesc); vol->primary_voldesc = NULL; } if (vol->catalog_tree.file) { fsw_dnode_release((struct fsw_dnode*)(vol->catalog_tree.file)); vol->catalog_tree.file = NULL; } if (vol->extents_tree.file) { fsw_dnode_release((struct fsw_dnode*)(vol->extents_tree.file)); vol->extents_tree.file = NULL; } } /** * Get in-depth information on a volume. */ static fsw_status_t fsw_hfs_volume_stat(struct fsw_hfs_volume *vol, struct fsw_volume_stat *sb) { sb->total_bytes = LShiftU64(be32_to_cpu(vol->primary_voldesc->totalBlocks), vol->block_size_shift); sb->free_bytes = LShiftU64(be32_to_cpu(vol->primary_voldesc->freeBlocks), vol->block_size_shift); return FSW_SUCCESS; } /** * Get full information on a dnode from disk. This function is called by the core * whenever it needs to access fields in the dnode structure that may not * be filled immediately upon creation of the dnode. */ static fsw_status_t fsw_hfs_dnode_fill(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno) { // Print(L"dnode_fill\n"); return FSW_SUCCESS; } /** * Free the dnode data structure. Called by the core when deallocating a dnode * structure to release the memory used by the file system type specific part * of the dnode structure. */ static void fsw_hfs_dnode_free(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno) { } static fsw_u32 mac_to_posix(fsw_u32 mac_time) { /* Mac time is 1904 year based */ return mac_time ? mac_time - 2082844800 : 0; } /** * Get in-depth information on a dnode. The core makes sure that fsw_hfs_dnode_fill * has been called on the dnode before this function is called. Note that some * data is not directly stored into the structure, but passed to a host-specific * callback that converts it to the host-specific format. */ static fsw_status_t fsw_hfs_dnode_stat(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno, struct fsw_dnode_stat_str *sb) { sb->used_bytes = dno->used_bytes; // DBG("dnode_stat used_bytes =%ld\n", sb->used_bytes); sb->store_time_posix(sb, FSW_DNODE_STAT_CTIME, mac_to_posix(dno->ctime)); sb->store_time_posix(sb, FSW_DNODE_STAT_MTIME, mac_to_posix(dno->mtime)); sb->store_time_posix(sb, FSW_DNODE_STAT_ATIME, 0); sb->store_attr_posix(sb, 0700); return FSW_SUCCESS; } static int fsw_hfs_find_block(HFSPlusExtentRecord *exts, fsw_u32 *lbno, fsw_u32 *pbno) { int i; fsw_u32 cur_lbno = *lbno; // DBG("search for lbno=%d\n", *lbno); for (i = 0; i < 8; i++) { fsw_u32 start = be32_to_cpu ((*exts)[i].startBlock); fsw_u32 count = be32_to_cpu ((*exts)[i].blockCount); if (cur_lbno < count) { *pbno = start + cur_lbno; // DBG("found block %d in segment %d\n", *pbno, i); return 1; } cur_lbno -= count; } *lbno = cur_lbno; return 0; } // // Find record offset, numbering starts from the end // static fsw_u32 fsw_hfs_btree_recoffset (struct fsw_hfs_btree *btree, BTNodeDescriptor *node, fsw_u32 index) { //+ fsw_u8 *cnode = (fsw_u8 *)node; fsw_u16 *recptr; fsw_u32 offset = (fsw_u32)be16_to_cpu(node->numRecords); if (index >= offset) { DBG(" index is too large %d > %d for this node\n", index, offset); return 0; //impossible for good run } recptr = (fsw_u16 *)(cnode + btree->node_size - index * 2 - 2); offset = (fsw_u32)be16_to_cpu(*recptr); return offset; } // // Pointer to the key record by index inside the node // static BTreeKey * fsw_hfs_btree_rec (struct fsw_hfs_btree *btree, BTNodeDescriptor *node, fsw_u32 index) { //+ fsw_u8 *cnode = (fsw_u8 *)node; fsw_u32 offset; offset = fsw_hfs_btree_recoffset(btree, node, index); if (offset < sizeof(BTNodeDescriptor) || offset > btree->node_size) { DBG(" wrong offset %d\n", offset); return NULL; } return (BTreeKey *)(cnode + offset); } // // returns pointer to data of the index record // static fsw_u32 fsw_hfs_btree_next_node (BTreeKey *currkey) { //+ fsw_u32 *pointer; pointer = (fsw_u32 *)((char *) currkey + be16_to_cpu (currkey->length16) + 2); return be32_to_cpu_ua(pointer); } static fsw_status_t fsw_hfs_btree_search (struct fsw_hfs_btree *btree, BTreeKey *key, int (*compare_keys) (BTreeKey *key1, BTreeKey *key2), BTNodeDescriptor **result, fsw_u32 *key_offset) { fsw_status_t status; fsw_u8 *buffer = NULL; BTNodeDescriptor *node; fsw_u32 currnode; fsw_u32 recnum; #ifdef VBOXHFS_BTREE_BINSEARCH fsw_u32 lower, upper; #endif currnode = btree->root_node; //always from root? status = fsw_alloc(btree->node_size, &buffer); if (status != FSW_SUCCESS) { fsw_free(buffer); return status; } node = (BTNodeDescriptor *) buffer; for (;;) { //node cycle fsw_s32 cmp = 0; // int match; fsw_u32 count; BTreeKey *currkey; // match = 0; /* Read a node */ if ((fsw_u32)fsw_hfs_read_file(btree->file, MultU64x32(currnode, btree->node_size), btree->node_size, buffer) != btree->node_size) { status = FSW_VOLUME_CORRUPTED; DBG("differ node size while read file\n"); break; } //check record0 pointing to end of descriptor if (be16_to_cpu (*(fsw_u16 *) (buffer + btree->node_size - 2)) != sizeof (BTNodeDescriptor)) { status = FSW_VOLUME_CORRUPTED; DBG("differ BTNodeDescriptor\n"); break; } count = be16_to_cpu (node->numRecords); #ifndef VBOXHFS_BTREE_BINSEARCH //preliminary test for last record. May be not needed look all for this node currkey = fsw_hfs_btree_rec (btree, node, count - 1); cmp = compare_keys (currkey, key); if ((cmp == 0) && (node->kind == kBTLeafNode)) { /* Found! */ *result = node; *key_offset = recnum; hardlink = 0; return FSW_SUCCESS; } else if (cmp < 0) { //all records has key less than searched if (node->kind == kBTIndexNode) { currnode = fsw_hfs_btree_next_node (currkey); } else if (node->fLink) { currnode = be32_to_cpu (node->fLink); } else { status = FSW_NOT_FOUND; break; } continue; } //else search for all records for (recnum = 0; recnum < count; recnum++) { currkey = fsw_hfs_btree_rec (btree, node, recnum); cmp = compare_keys (currkey, key); //fsw_hfs_cmpi_catkey // DBG(": currnode %d rec=%d count %d cmp=%d kind=%d\n", // currnode, recnum, count, cmp, node->kind); /* Leaf node. */ if (node->kind == kBTLeafNode) { if (cmp == 0) { /* Found! */ *result = node; *key_offset = recnum; hardlink = 0; return FSW_SUCCESS; } } else if (node->kind == kBTIndexNode) { if (cmp > 0) break; currnode = fsw_hfs_btree_next_node (currkey); //largest that <= search } } if (node->kind == kBTLeafNode) { status = FSW_NOT_FOUND; break; } if (cmp <= 0 && node->fLink) { currnode = be32_to_cpu (node->fLink); } #else /* Perform binary search */ lower = 0; upper = count - 1; currkey = NULL; if (count == 0) { status = FSW_NOT_FOUND; goto done; } while (lower <= upper) { recnum = (lower + upper) / 2; currkey = fsw_hfs_btree_rec (btree, node, recnum); cmp = compare_keys (currkey, key); //fsw_hfs_cmpi_catkey // DBG(": currnode %d lower/recnum/upper %d/%d/%d (%d) cmp=%d kind=%d\n", // currnode, lower, recnum, upper, count, cmp, node->kind); if (cmp > 0) { upper = recnum - 1; } else if (cmp < 0) { lower = recnum + 1; } else if (cmp == 0) { if (node->kind == kBTLeafNode) { // Found! *result = node; *key_offset = recnum; return FSW_SUCCESS; } else if (node->kind == kBTIndexNode) { currnode = fsw_hfs_btree_next_node (currkey); break; } } } if (cmp > 0) { currkey = fsw_hfs_btree_rec (btree, node, upper); } if (node->kind == kBTIndexNode && currkey != NULL) { currnode = fsw_hfs_btree_next_node (currkey); // DBG(": candidate for the next currnode is %d\n", currnode); } else { status = FSW_NOT_FOUND; break; } #endif } #ifdef VBOXHFS_BTREE_BINSEARCH done: #endif if (buffer != NULL && status != FSW_SUCCESS) fsw_free(buffer); return status; } static void fill_fileinfo ( struct fsw_hfs_volume* vol, HFSPlusCatalogKey* key, file_info_t* finfo ) { fsw_u8* base; fsw_u16 rec_type; fsw_u16 flags; /* for plain HFS "-(keySize & 1)" would be needed */ base = (fsw_u8 *) key + be16_to_cpu (key->keyLength) + 2; rec_type = be16_to_cpu (*(fsw_u16 *) base); /** @todo: read additional info */ switch (rec_type) { case kHFSPlusFolderRecord: { HFSPlusCatalogFolder *info = (HFSPlusCatalogFolder *) base; finfo->id = be32_to_cpu_ua ((fsw_u32*)(void*)&info->folderID); finfo->type = FSW_DNODE_TYPE_DIR; /* @todo: return number of elements, maybe use smth else */ finfo->size = be32_to_cpu (info->valence); //this is wrong because of deleted entries finfo->used = be32_to_cpu (info->valence); finfo->ctime = be32_to_cpu (info->createDate); finfo->mtime = be32_to_cpu (info->contentModDate); // DBG("number of folder entries=%d\n", finfo->size); // DBG(" fileMode=%x\n", finfo->fileMode); // DBG(" flags=%x\n", be16_to_cpu(info->flags)); // DBG(" folderID=%d\n\n", finfo->id); break; } case kHFSPlusFileThreadRecord: { //never happen HFSPlusCatalogThread *info = (HFSPlusCatalogThread *) base; finfo->id = be32_to_cpu_ua((fsw_u32*)(void*)&info->parentID); finfo->type = FSW_DNODE_TYPE_SPECIAL; // DBG(" CatalogThread\n"); // DBG(" folderID=%d\n\n", finfo->id); } case kHFSPlusFileRecord: { HFSPlusCatalogFile *info = (HFSPlusCatalogFile *) base; file_info_t tmp_finfo; fsw_status_t status; finfo->id = be32_to_cpu (info->fileID); flags = be16_to_cpu(info->flags); finfo->creator = be32_to_cpu (info->userInfo.fdCreator); finfo->crtype = be32_to_cpu (info->userInfo.fdType); finfo->type = FSW_DNODE_TYPE_FILE; finfo->ilink = 0; finfo->isfilelink = 0; finfo->isdirlink = 0; DBG("file creator=%x crtype=%x\n", finfo->creator, finfo->crtype); /* Is the file any kind of link? */ if ((finfo->creator == kSymLinkCreator && finfo->crtype == kSymLinkFileType) || (finfo->creator == kHFSPlusCreator && finfo->crtype == kHardLinkFileType)) { // if (finfo->crtype == kSymLinkFileType) { finfo->type = FSW_DNODE_TYPE_SYMLINK; // } finfo->isfilelink = 1; } else if ((flags & kHFSHasLinkChainMask) && (finfo->crtype == kHFSAliasType) && (finfo->creator == kHFSAliasCreator)) { DBG("found dirlink, fileID=%d\n", finfo->id); finfo->isdirlink = 0; //do nothing for now } if ((finfo->isfilelink || finfo->isdirlink)/* && !(flags & HFS_LOOKUP_HARDLINK)*/) { finfo->ilink = be32_to_cpu_ua ((fsw_u32*)(void*)&info->bsdInfo.special.iNodeNum); if (finfo->type != FSW_DNODE_TYPE_SYMLINK) { status = fsw_hfs_dir_lookup_id(vol, finfo->ilink, &tmp_finfo); if (!status) { // DBG("hardlink resolved to fileID=%d\n", finfo->ilink); finfo->id = finfo->ilink; finfo->ilink = 0; finfo->size = tmp_finfo.size; finfo->used = tmp_finfo.used; finfo->ctime = be32_to_cpu (info->createDate); finfo->mtime = be32_to_cpu (info->contentModDate); fsw_memcpy (&finfo->extents, &tmp_finfo.extents, sizeof(HFSPlusExtentRecord)); break; } } } /* DBG("file type="); if (finfo->isfilelink && finfo->crtype == kHardLinkFileType) { DBG("hardlink to fileID=%d\n", finfo->ilink); } else if (finfo->isdirlink) { DBG("dirlink\n"); } else { switch (finfo->type) { case FSW_DNODE_TYPE_SYMLINK: DBG("symlink\n"); break; case FSW_DNODE_TYPE_FILE: DBG("file\n"); break; default: DBG("xxx\n"); break; } } */ finfo->fileMode = be16_to_cpu(info->bsdInfo.fileMode); // DBG(" fileMode=%x\n", finfo->fileMode); // DBG(" flags=%x\n", flags); // DBG(" fileID=%d\n", finfo->id); finfo->size = be64_to_cpu_ua((fsw_u64*)(void*)&info->dataFork.logicalSize); finfo->used = LShiftU64 ((fsw_u64)be32_to_cpu_ua ((fsw_u32*)(void*)&info->dataFork.totalBlocks), vol->block_size_shift); // DBG("file size=%ld, used=%ld\n\n", finfo->size, finfo->used); // if (finfo->size == 0) { //TODO - how to find the real file? // DBG("file info:\n"); // DBG("\tdataFork.logicalSize=%d\n", info->dataFork.logicalSize); // DBG("\tdataFork.totalBlocks=%d\n", info->dataFork.totalBlocks); // DBG("\tresourceFork.logicalSize=%d\n", info->resourceFork.logicalSize); // DBG("\tresourceFork.totalBlocks=%d\n", info->resourceFork.totalBlocks); // } finfo->ctime = be32_to_cpu (info->createDate); finfo->mtime = be32_to_cpu (info->contentModDate); fsw_memcpy (&finfo->extents, &info->dataFork.extents, sizeof(HFSPlusExtentRecord)); break; } default: finfo->type = FSW_DNODE_TYPE_UNKNOWN; break; } } typedef struct { fsw_u32 cur_pos; /* current position */ fsw_u32 parent; struct fsw_hfs_volume *vol; struct fsw_shandle *shandle; /* this one track iterator's state */ file_info_t file_info; } visitor_parameter_t; /// // at input: record = fsw_hfs_btree_rec (btree, node, i) - having // param contains file name and folder ID - wishing // for directory list name=0 // static int fsw_hfs_btree_visit_node(BTreeKey *record, void *param) { visitor_parameter_t *vp = (visitor_parameter_t*)param; fsw_u8 *base = (fsw_u8*)record->rawData + be16_to_cpu(record->length16) + 2; //data next to key fsw_u16 rec_type = be16_to_cpu(*(fsw_u16*)base); HFSPlusCatalogKey *cat_key = (HFSPlusCatalogKey*)record; fsw_u16 name_len; fsw_u16 *name_ptr; fsw_u32 i; struct fsw_string *file_name; i = be32_to_cpu(cat_key->parentID); if (vp->parent != 0 && i != vp->parent) { //in some cases we wish no check parentID // DBG("cat ID=%x while parentID=%x\n", i, vp->parent); // vp->shandle->pos++; return -1; } /* not smth we care about */ if (vp->shandle->pos != vp->cur_pos++) return 0; fill_fileinfo (vp->vol, cat_key, &vp->file_info); switch (rec_type) { case kHFSPlusFolderThreadRecord: case kHFSPlusFileThreadRecord: { // DBG("skip thread\n"); vp->shandle->pos++; return 0; } default: break; } name_len = be16_to_cpu(cat_key->nodeName.length); file_name = vp->file_info.name; file_name->len = name_len; fsw_memdup(&file_name->data, &cat_key->nodeName.unicode[0], 2*name_len); file_name->size = 2*name_len; file_name->type = FSW_STRING_TYPE_UTF16; name_ptr = (fsw_u16*)file_name->data; for (i = 0; i < name_len; i++) { name_ptr[i] = be16_to_cpu(name_ptr[i]); } vp->shandle->pos++; /* DBG(" visited file=", name_ptr); for (i=0; inode_size) { DBG("no node_size\n"); return FSW_NOT_FOUND; } status = fsw_alloc (btree->node_size, &buffer); if (status) { if (buffer != NULL) fsw_free(buffer); return status; } for (;;) { fsw_u32 i; fsw_u32 count = be16_to_cpu (node->numRecords); fsw_u32 next_node; // DBG("first_rec=%d, count=%d, kind=%d\n", first_rec, count, node->kind); status = FSW_NOT_FOUND; // Iterate over all records in this node. i = first_rec; while (i < count) { int rv = callback (fsw_hfs_btree_rec (btree, node, i), param); //fsw_hfs_btree_visit_node // DBG("test record %d, status=%d\n", i, rv); if (rv == 1) { status = FSW_SUCCESS; goto done; } else if (rv == -1) { // DBG("tested record %d, status=%d\n", i, rv); status = FSW_NOT_FOUND; //it means the node has other owner goto done; //no need to test more } /* if callback returned 0 - continue */ // status = FSW_SUCCESS; i++; } next_node = be32_to_cpu(node->fLink); // DBG("next_node=%d\n", next_node); if (!next_node) { status = FSW_NOT_FOUND; // DBG("FSW_NOT_FOUND at next_node\n"); break; } if ((fsw_u32) fsw_hfs_read_file(btree->file, next_node * btree->node_size, btree->node_size, buffer) != btree->node_size) { status = FSW_VOLUME_CORRUPTED; // DBG("differ node size btree->node_size=%d\n", btree->node_size); break; } node = (BTNodeDescriptor *) buffer; first_rec = 0; } done: if (buffer) fsw_free(buffer); return status; } static int fsw_hfs_cmp_extkey(BTreeKey* key1, BTreeKey* key2) { HFSPlusExtentKey* ekey1 = (HFSPlusExtentKey*)key1; HFSPlusExtentKey* ekey2 = (HFSPlusExtentKey*)key2; int result; /* First key is read from the FS data, second is in-memory in CPU endianess */ result = be32_to_cpu(ekey1->fileID) - ekey2->fileID; if (result) return result; result = ekey1->forkType - ekey2->forkType; if (result) return result; result = be32_to_cpu(ekey1->startBlock) - ekey2->startBlock; return result; } static int fsw_hfs_cmp_catkey (BTreeKey *key1, BTreeKey *key2) { HFSPlusCatalogKey *ckey1 = (HFSPlusCatalogKey*)key1; HFSPlusCatalogKey *ckey2 = (HFSPlusCatalogKey*)key2; int apos, bpos, lc; fsw_u16 ac, bc; fsw_u32 parentId1; int key1Len; fsw_u16 *p1; fsw_u16 *p2; parentId1 = be32_to_cpu(ckey1->parentID); if (parentId1 > ckey2->parentID) return 1; if (parentId1 < ckey2->parentID) return -1; p1 = &ckey1->nodeName.unicode[0]; p2 = &ckey2->nodeName.unicode[0]; key1Len = be16_to_cpu (ckey1->nodeName.length); apos = bpos = 0; while(1) { /* get next valid character from ckey1 */ for (lc = 0; lc == 0 && apos < key1Len; apos++) { ac = be16_to_cpu(p1[apos]); lc = ac; } ac = (fsw_u16)lc; /* get next valid character from ckey2 */ for (lc = 0; lc == 0 && bpos < ckey2->nodeName.length; bpos++) { bc = p2[bpos]; lc = bc; } bc = (fsw_u16)lc; if (ac != bc || (ac == 0 && bc == 0)) return ac - bc; } } static int fsw_hfs_cmpi_catkey (BTreeKey *key1, BTreeKey *key2) { HFSPlusCatalogKey *ckey1 = (HFSPlusCatalogKey*)key1; HFSPlusCatalogKey *ckey2 = (HFSPlusCatalogKey*)key2; int apos, bpos, lc = 0; fsw_u16 ac, bc; fsw_u32 parentId1; int key1Len; int key2Len; fsw_u16 *p1; fsw_u16 *p2; parentId1 = be32_to_cpu_ua((fsw_u32 *)(((UINT8 *)ckey1) + OFFSET_OF(HFSPlusCatalogKey, parentID))); // MSC warns about a possibly unaligned result of '&' (C4366) // if (hardlink) { // DBG("parents: %d <-> %d\n", parentId1, ckey2->parentID); // } if (parentId1 > ckey2->parentID) { return 1; } else if (parentId1 < ckey2->parentID) { return -1; } else { key1Len = be16_to_cpu (ckey1->nodeName.length); key2Len = ckey2->nodeName.length; //it is constructed so CPU endiness // if (hardlink) { // DBG("keylen: %d <-> %d\n", key1Len, key2Len); // } if (key1Len == 0 || key2Len == 0) { return key1Len - key2Len; } p1 = &ckey1->nodeName.unicode[0]; p2 = &ckey2->nodeName.unicode[0]; /* if (hardlink) { DBG("name:"); for (apos = 0; apos < key1Len; apos++) { ac = be16_to_cpu(p1[apos]); DBG("%c", ac?ac:L'@'); } DBG(":"); for (apos = 0; apos < key2Len; apos++) { ac = p2[apos]; DBG("%c", ac?ac:L'@'); } DBG(":\n"); } */ apos = bpos = 0; while(1) { /* get next valid character from ckey1 */ for (lc = 0; lc == 0 && apos < key1Len; apos++) { // for (lc = 0; apos < key1Len; apos++) { ac = be16_to_cpu(p1[apos]); lc = ac ? fsw_to_lower(ac) : 0xFFFF; } ac = (fsw_u16)lc; /* get next valid character from ckey2 */ for (lc = 0; lc == 0 && bpos < key2Len; bpos++) { // for (lc = 0; bpos < key2Len; bpos++) { bc = p2[bpos]; lc = bc ? fsw_to_lower(bc) : 0xFFFF;; } bc = (fsw_u16)lc; // if (hardlink) { // DBG("compare: %c:%c\n", ac?ac:L'@', bc?bc:L'@'); // } if (ac != bc) { break; } if (bpos == key1Len) { return (key1Len - key2Len); } } if (ac == bc) return 0; else if (ac < bc) return -1; else return 1; } } // // helper function to compare fileID with node // key1 is catalog key, param contains FileID to find in "parent" field // static int fsw_hfs_cmp_id (BTreeKey *key1, void *param) { visitor_parameter_t *vp = (visitor_parameter_t*)param; HFSPlusCatalogFile *crec1 = (HFSPlusCatalogFile*)((char*)key1 + be16_to_cpu (key1->length16)); fsw_u32 fileID = be32_to_cpu(crec1->fileID); fsw_u32 rec_type = be16_to_cpu(crec1->recordType); if ((rec_type != kHFSPlusFileRecord) && (rec_type != kHFSPlusFolderRecord)){ DBG("bad record type? =%d fileID=%d\n", rec_type, fileID); return 2; } if (fileID != vp->parent) { return 0; } return 1; } /** * Retrieve file data mapping information. This function is called by the core when * fsw_shandle_read needs to know where on the disk the required piece of the file's * data can be found. The core makes sure that fsw_hfs_dnode_fill has been called * on the dnode before. Our task here is to get the physical disk block number for * the requested logical block number. */ static fsw_status_t fsw_hfs_get_extent(struct fsw_hfs_volume * vol, struct fsw_hfs_dnode * dno, struct fsw_extent * extent) { fsw_status_t status; fsw_u32 lbno; HFSPlusExtentRecord *exts; BTNodeDescriptor *node = NULL; struct HFSPlusExtentKey* key; struct HFSPlusExtentKey overflowkey; fsw_u32 ptr; fsw_u32 phys_bno; extent->type = FSW_EXTENT_TYPE_PHYSBLOCK; extent->log_count = 1; lbno = extent->log_start; /* we only care about data forks atm, do we? */ exts = &dno->extents; while (1) { if (fsw_hfs_find_block(exts, &lbno, &phys_bno)) { extent->phys_start = phys_bno + vol->emb_block_off; status = FSW_SUCCESS; break; } /* Find appropriate overflow record */ overflowkey.forkType = 0; //data fork overflowkey.fileID = dno->g.dnode_id; overflowkey.startBlock = extent->log_start - lbno; if (node != NULL) { fsw_free(node); node = NULL; } status = fsw_hfs_btree_search (&vol->extents_tree, (BTreeKey*)&overflowkey, fsw_hfs_cmp_extkey, &node, &ptr); if (status) { break; } key = (struct HFSPlusExtentKey *) fsw_hfs_btree_rec (&vol->extents_tree, node, ptr); exts = (HFSPlusExtentRecord*) (key + 1); } if (node != NULL) fsw_free(node); return status; } static fsw_status_t create_hfs_dnode(struct fsw_hfs_dnode *dno, file_info_t *file_info, struct fsw_hfs_dnode **child_dno_out) { fsw_status_t status; struct fsw_hfs_dnode *baby = NULL; if (!file_info || !child_dno_out) { return FSW_NOT_FOUND; } status = fsw_dnode_create(dno, file_info->id, file_info->type, file_info->name, &baby); if (status || !baby) return status; baby->g.size = file_info->size; baby->used_bytes = file_info->used; baby->ctime = file_info->ctime; baby->mtime = file_info->mtime; /* Fill-in extents info */ if (file_info->type == FSW_DNODE_TYPE_FILE) { fsw_memcpy(baby->extents, &file_info->extents, sizeof(file_info->extents)); } // Fill-in link file info if (file_info->isfilelink) { baby->creator = file_info->creator; baby->crtype = file_info->crtype; baby->ilink = file_info->ilink; fsw_memcpy(baby->extents, &file_info->extents, sizeof file_info->extents); } *child_dno_out = baby; return FSW_SUCCESS; } /** * Lookup a directory's child dnode by name. This function is called on a directory * to retrieve the directory entry with the given name. A dnode is constructed for * this entry and returned. The core makes sure that fsw_hfs_dnode_fill has been called * and the dnode is actually a directory. */ static fsw_status_t fsw_hfs_dir_lookup(struct fsw_hfs_volume * vol, struct fsw_hfs_dnode * dno, struct fsw_string * lookup_name, struct fsw_hfs_dnode ** child_dno_out) { fsw_status_t status; HFSPlusCatalogKey catkey; fsw_u32 ptr; // fsw_u16 rec_type; BTNodeDescriptor * node = NULL; struct fsw_string rec_name; int free_data = 0; //, i; HFSPlusCatalogKey* file_key; file_info_t file_info; fsw_memzero(&file_info, sizeof(file_info_t)); file_info.name = &rec_name; catkey.parentID = dno->g.dnode_id; catkey.nodeName.length = (fsw_u16)lookup_name->len; // no need to allocate anything if (lookup_name->type == FSW_STRING_TYPE_UTF16) { fsw_memcpy(catkey.nodeName.unicode, lookup_name->data, lookup_name->size); // rec_name = *lookup_name; fsw_memcpy(&rec_name, lookup_name, sizeof(struct fsw_string)); } else { status = fsw_strdup_coerce(&rec_name, FSW_STRING_TYPE_UTF16, lookup_name); // nothing allocated so far if (status) goto done; free_data = 1; fsw_memcpy(catkey.nodeName.unicode, rec_name.data, rec_name.size); } catkey.keyLength = (fsw_u16)(6 + rec_name.size); status = fsw_hfs_btree_search (&vol->catalog_tree, (BTreeKey*)&catkey, vol->case_sensitive ? fsw_hfs_cmp_catkey : fsw_hfs_cmpi_catkey, &node, &ptr); if (status) { // DBG("fsw_hfs_btree_search dir lookup status %a\n", fsw_errors[status]); goto done; } file_key = (HFSPlusCatalogKey *)fsw_hfs_btree_rec (&vol->catalog_tree, node, ptr); fill_fileinfo (vol, file_key, &file_info); status = create_hfs_dnode(dno, &file_info, child_dno_out); //&tmp_dno_out); // // if (status) { // DBG("create_hfs_dnode status %a\n", fsw_errors[status]); // goto done; // } done: if (node != NULL) fsw_free(node); if (free_data) fsw_strfree(&rec_name); return status; } /** * Lookup a directory's child dnode by ID. This function is called on a directory * to retrieve the directory entry with the fileID. A dnode is constructed for * this entry and returned. */ static fsw_status_t fsw_hfs_dir_lookup_id(struct fsw_hfs_volume *vol, fsw_u32 lookup_id, file_info_t *file_info) { fsw_status_t status; fsw_u8 *buffer = NULL; fsw_u32 ptr = 0; visitor_parameter_t param; BTNodeDescriptor * node = NULL; //root fsw_u32 currnode = vol->catalog_tree.root_node; fsw_u32 nodeSize = vol->catalog_tree.node_size; // DBG("...lookup for fileID=%d\n", lookup_id); status = fsw_alloc(nodeSize, &buffer); if (status != FSW_SUCCESS) { fsw_free(buffer); goto done; } node = (BTNodeDescriptor *) buffer; /* Read a node */ if ((fsw_u32)fsw_hfs_read_file(vol->catalog_tree.file, MultU64x32(currnode, nodeSize), nodeSize, buffer) != nodeSize) { status = FSW_VOLUME_CORRUPTED; // DBG("differ node size while read file\n"); return status; } fsw_memzero(¶m, sizeof(visitor_parameter_t)); param.parent = lookup_id; status = fsw_hfs_btree_iterate_node (&vol->catalog_tree, node, ptr, fsw_hfs_cmp_id, ¶m); if (status) { goto done; } fsw_memcpy(file_info, ¶m.file_info, sizeof(file_info_t)); // status = create_hfs_dnode(dno, ¶m.file_info, child_dno_out); //&tmp_dno_out); // // if (status) { // DBG("create_hfs_dnode status %a\n", fsw_errors[status]); // goto done; // } done: if (node != NULL) fsw_free(node); // DBG("fsw_hfs_dir_lookup_id return status %a\n", fsw_errors[status]); return status; } /** * Get the next directory entry when reading a directory. This function is called during * directory iteration to retrieve the next directory entry. A dnode is constructed for * the entry and returned. The core makes sure that fsw_hfs_dnode_fill has been called * and the dnode is actually a directory. The shandle provided by the caller is used to * record the position in the directory between calls. */ static fsw_status_t fsw_hfs_dir_read(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno, struct fsw_shandle *shand, struct fsw_hfs_dnode **child_dno_out) { BTNodeDescriptor *node = NULL; fsw_u32 ptr; HFSPlusCatalogKey catkey; fsw_status_t status; visitor_parameter_t param; struct fsw_string rec_name; catkey.parentID = dno->g.dnode_id; catkey.nodeName.length = 0; fsw_memzero(¶m, sizeof(param)); rec_name.type = FSW_STRING_TYPE_EMPTY; param.file_info.name = &rec_name; param.file_info.id = 0; // we are searching a node with name=0 and parentID? status = fsw_hfs_btree_search (&vol->catalog_tree, (BTreeKey*)&catkey, vol->case_sensitive ? fsw_hfs_cmp_catkey : fsw_hfs_cmpi_catkey, &node, &ptr); if (status) { // DBG("fsw_hfs_btree_search dir read status %a\n", fsw_errors[status]); goto done; } /* Iterator updates shand state */ param.vol = vol; param.shandle = shand; param.parent = dno->g.dnode_id; if (dno->ilink != 0) { param.parent = 0; } param.cur_pos = 0; status = fsw_hfs_btree_iterate_node (&vol->catalog_tree, node, ptr, fsw_hfs_btree_visit_node, ¶m); if (!status) { status = create_hfs_dnode(dno, ¶m.file_info, child_dno_out); // if (status) { //never DBG("create_hfs_dnode status %a\n", fsw_errors[status]); // } } else { // DBG("fsw_hfs_btree_iterate_node status %a\n", fsw_errors[status]); } done: if (node) fsw_free(node); fsw_strfree(&rec_name); return status; } static const char metaprefix[] = "/\0\0\0\0HFS+ Private Data/iNode0123456789"; /** * Get the target path of a link. This function is called when a * link needs to be resolved. The core makes sure that the fsw_hfs_dnode_fill has been * called on the dnode. * */ static fsw_status_t fsw_hfs_readlink(struct fsw_hfs_volume *vol, struct fsw_hfs_dnode *dno, struct fsw_string *link_target) { /* * XXX: Hardlinks for directories -- it is alias to folder . * Hex dump visual inspection of Apple hfsplus{32,64}.efi * revealed no signs of directory hardlinks support. finder links, not properly documented kHFSAliasType = 0x66647270, // 'fdrp' - finder alias for folder kHFSAliasCreator = 0x4D414353 // 'MACS' kHFSAliasFile = 0x616C6973 // 'alis' - finder alias for file - not implemented yet */ fsw_u32 sz = 0; // int i; // fsw_u16 ch; fsw_status_t status = FSW_UNSUPPORTED; if(dno->creator == kHFSPlusCreator && dno->crtype == kHardLinkFileType) { #define MPRFSIZE (sizeof (metaprefix)) #define MPRFINUM (MPRFSIZE - 1 - 10) struct fsw_string *tmp_target; status = fsw_alloc(sizeof(struct fsw_string), &tmp_target); if (status) { DBG("can't alloc tmp_target\n"); } tmp_target->type = FSW_STRING_TYPE_ISO88591; tmp_target->size = MPRFSIZE; DBG(" hfs readlink: size=%d, iLink=%d\n", tmp_target->size, dno->ilink); fsw_memdup ((void**)&tmp_target->data, (void*)&metaprefix[0], tmp_target->size); sz = (fsw_u32)AsciiSPrint(((char *)tmp_target->data) + MPRFINUM, 10, "%d", dno->ilink); tmp_target->len = MPRFINUM + sz; // DBG(" iNode name len=%d\n", tmp_target->len); status = fsw_strdup_coerce(link_target, vol->g.host_string_type, tmp_target); hardlink = 1; /* for (i = 0; i < tmp_target->len; i++) { ch = ((fsw_u16 *) link_target->data)[i]; DBG("%c", ch?ch:L'@'); } DBG("\n"); */ fsw_strfree(tmp_target); fsw_free(tmp_target); return status; #undef MPRFINUM #undef MPRFSIZE } else if (dno->creator == kSymLinkCreator && dno->crtype == kSymLinkFileType) { hardlink = 1; return fsw_dnode_readlink_data(dno, link_target); } else if (dno->creator == kHFSAliasCreator && dno->crtype == kHFSAliasType) { CHAR16 inodename[48]; sz = (fsw_u32)UnicodeSPrint(inodename, 48, L".HFS+ Private Directory Data/dir_%d", dno->ilink); link_target->type = FSW_STRING_TYPE_UTF16; link_target->size = 96; link_target->len = sz; fsw_memdup (&link_target->data, &inodename[0], link_target->size); hardlink = 1; return FSW_SUCCESS; } /* Unknown link type */ return FSW_UNSUPPORTED; } // EOF