From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-qk0-x244.google.com (mail-qk0-x244.google.com [IPv6:2607:f8b0:400d:c09::244]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ml01.01.org (Postfix) with ESMTPS id 30F7C8034F for ; Mon, 6 Mar 2017 19:14:36 -0800 (PST) Received: by mail-qk0-x244.google.com with SMTP id n141so25731482qke.3 for ; Mon, 06 Mar 2017 19:14:36 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=LpB4pNoW0dhKWasMJlYa9GNAhcDV98a8NklsaMI54r0=; b=GJ5CInTttmT6knpqgrrrPI3or/U2DXt7L92Wvn2TQl6Q2hIgBO40KiInFeOuHdINUc Y5Zyk9e01BCwgcr1h1oMyj4hM+qIzJ1MDkHqe0w8MiwxtL2QyWOC+Z9CjtFVabKezqkL gDzi3fZK3qqRI8ByV9XQ+ID+2GbZrP+Wm1iZZhYJksUe396BPyzogW2sk7nmMyCCQ0Au xc7+Tjn4i1QT0aaSAGm1h/WZJMibUfiF5CzQh/Md9N1iuxB4UWkoSZ5uHbnmVMheNrFn KfLGLwBez/NcXJ3zhQxcibSvLczROIswmgceioIXeJZ0zWRJ7K0WngleVmpcODh1v4ly tpoA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=LpB4pNoW0dhKWasMJlYa9GNAhcDV98a8NklsaMI54r0=; b=kNygLE5nGNx5vALfXmQCCjS7V3oZDrRtDpskg5C5bTyEzpnEbXPdn+5m5GaR7D0BY0 KZ3rbOI5cQGlqg/G8V5UOJPdR9kWrJxQ1/XTNQjkk0JAzLMYqOsdfo6qIZmC70J4IJsu VSwIJJwiHGPgR/miZ0MJMT6+6tpYP8YBQ/UXWBBh0KCOIsAMVN929ARVQvEJag6qzDn9 sLcGLqStpg0d+xBTQSELop+lSYnej1eV+T5XyN6BC3Y6cidMezvg9U+kt9V8NIG+4xJ7 Z0zK7vNZ/D0hut1Yj2v9Joj43DO19xhvo5HOvbAkV5cuhrOEOx0bYazlwbtlmkJH/gMz 4dMw== X-Gm-Message-State: AMke39l2JSRBXououg8yPbTOcl0CHFN9XNUepj174uGrd/fgIKQ7cQkZuaeDldkvMnsIQA== X-Received: by 10.200.41.42 with SMTP id y39mr18492879qty.37.1488856474738; Mon, 06 Mar 2017 19:14:34 -0800 (PST) Received: from foober.ini.cmu.edu (pool-108-39-248-175.pitbpa.fios.verizon.net. [108.39.248.175]) by smtp.gmail.com with ESMTPSA id v26sm2901013qtc.13.2017.03.06.19.14.33 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 06 Mar 2017 19:14:34 -0800 (PST) From: "Gabriel L. Somlo" To: edk2-devel@ml01.01.org Cc: lersek@redhat.com, jordan.l.justen@intel.com, reza.jelveh@tuhh.de, agraf@suse.de, kraxel@redhat.com Date: Mon, 6 Mar 2017 22:14:22 -0500 Message-Id: <1488856465-8965-4-git-send-email-gsomlo@gmail.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1488856465-8965-1-git-send-email-gsomlo@gmail.com> References: <1488856465-8965-1-git-send-email-gsomlo@gmail.com> Subject: [RFC PATCH 3/6] FswHfsPlus: implement FSW driver for the HFS+ file system X-BeenThere: edk2-devel@lists.01.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: EDK II Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 07 Mar 2017 03:14:36 -0000 Implement read-only HFS+ access methods within the FSW framework. Based on the HFS+ Volume Format spec (TN1150), available online at https://developer.apple.com/legacy/library/technotes/tn/tn1150.html Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Gabriel Somlo --- OvmfPkg/FswHfsPlus/FswHfsPlus.inf | 57 ++++ OvmfPkg/FswHfsPlus/fsw_hfsplus.c | 535 ++++++++++++++++++++++++++++++++++++++ OvmfPkg/FswHfsPlus/fsw_hfsplus.h | 238 +++++++++++++++++ 3 files changed, 830 insertions(+) create mode 100644 OvmfPkg/FswHfsPlus/FswHfsPlus.inf create mode 100644 OvmfPkg/FswHfsPlus/fsw_hfsplus.c create mode 100644 OvmfPkg/FswHfsPlus/fsw_hfsplus.h diff --git a/OvmfPkg/FswHfsPlus/FswHfsPlus.inf b/OvmfPkg/FswHfsPlus/FswHfsPlus.inf new file mode 100644 index 0000000..a3581a9 --- /dev/null +++ b/OvmfPkg/FswHfsPlus/FswHfsPlus.inf @@ -0,0 +1,57 @@ +## @file +# File System Wrapper (FSW) based HFS+ driver. +# +# Copyright (C) 2017, Gabriel L. Somlo +# +# This program and the accompanying materials are licensed and made +# available under the terms and conditions of the BSD License which +# accompanies this distribution. The full text of the license may +# be found at http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" +# BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER +# EXPRESS OR IMPLIED. +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = FswHfsPlus + FILE_GUID = F474802F-26BB-4E4F-A923-2FC568E94125 + MODULE_TYPE = UEFI_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = fsw_efi_main + +[Sources] + fsw_base.h + fsw_efi_base.h + fsw_efi_edk2_base.h + fsw_strfunc.h + fsw_core.h + fsw_core.c + fsw_efi.h + fsw_efi.c + fsw_lib.c + fsw_efi_lib.c + fsw_hfsplus.h + fsw_hfsplus.c + +[BuildOptions] + GCC:*_*_*_CC_FLAGS = -DHOST_EFI -DFSTYPE=hfsplus + +[Packages] + MdePkg/MdePkg.dec + OvmfPkg/OvmfPkg.dec + +[LibraryClasses] + UefiLib + UefiDriverEntryPoint + +[Guids] + gEfiFileSystemInfoGuid + gEfiFileSystemVolumeLabelInfoIdGuid + gEfiFileInfoGuid + +[Protocols] + gEfiBlockIoProtocolGuid + gEfiDiskIoProtocolGuid + gEfiSimpleFileSystemProtocolGuid diff --git a/OvmfPkg/FswHfsPlus/fsw_hfsplus.c b/OvmfPkg/FswHfsPlus/fsw_hfsplus.c new file mode 100644 index 0000000..ef17cc9 --- /dev/null +++ b/OvmfPkg/FswHfsPlus/fsw_hfsplus.c @@ -0,0 +1,535 @@ +/** @file + HFS+ file system driver. + + Copyright (C) 2017, Gabriel L. Somlo + + This program and the accompanying materials are licensed and made + available under the terms and conditions of the BSD License which + accompanies this distribution. The full text of the license may + be found at http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER + EXPRESS OR IMPLIED. +**/ + +#include "fsw_hfsplus.h" + + +/* Given dnode 'd' and offset 'pos' into the data file it represents, + * retrieve 'len' bytes of data into buffer 'buf'; + * Return FSW_SUCCESS or error code. + */ +static fsw_status_t +fsw_hfsplus_read(struct fsw_hfsplus_dnode *d, fsw_u64 pos, + fsw_u32 len, void *buf) +{ + struct fsw_shandle sh; + fsw_u32 buflen; + fsw_status_t status; + + status = fsw_shandle_open(d, &sh); + if (status) + return status; + + sh.pos = pos; + buflen = len; + status = fsw_shandle_read(&sh, &buflen, buf); + if (!status && buflen != len) + status = FSW_IO_ERROR; + + fsw_shandle_close(&sh); + return status; +} + +/* Given the volume being mounted ('v'), and the ID & fork data of the B-Tree + * file being set up ('dn_id' and 'f', respectively), populate a cached, + * in-memory record of the B-Tree file at the location pointed to by 'btp'; + * Return FSW_SUCCESS or error code. + */ +static fsw_status_t +fsw_hfsplus_btf_setup(struct fsw_hfsplus_volume *v, + fsw_u32 dn_id, HFSPlusForkData *f, + struct fsw_hfsplus_dnode **btp) +{ + BTHeaderRec hdr_rec; + fsw_status_t status; + + status = fsw_dnode_create_root(v, dn_id, btp); + if (status) + return status; + + (*btp)->g.size = fsw_u64_be_swap(f->logicalSize); + fsw_memcpy((*btp)->extents, f->extents, sizeof(HFSPlusExtentRecord)); + + // read header record (from node 0, immediately past the node descriptor) + status = fsw_hfsplus_read(*btp, sizeof(BTNodeDescriptor), + sizeof(BTHeaderRec), &hdr_rec); + if (status) + return status; + + // grab root node index and node size from header record + (*btp)->bt_root = fsw_u32_be_swap(hdr_rec.rootNode); + (*btp)->bt_ndsz = fsw_u16_be_swap(hdr_rec.nodeSize); + + return FSW_SUCCESS; +} + +/* Mount an HFS+ volume. Read volume header (equivalent of superblock), + * and set up dnodes for the root folder and B-Tree file(s). + * Return FSW_SUCCESS or error code. + */ +static fsw_status_t +fsw_hfsplus_vol_mount(struct fsw_hfsplus_volume *v) +{ + void *buf; + fsw_u32 bs; + fsw_status_t status; + + // allocate memory for vol. header + status = fsw_alloc(sizeof(HFSPlusVolumeHeader), &v->vh); + if (status) + return status; + + // read vol. header into buffer + fsw_set_blocksize(v, kHFSBlockSize, kHFSBlockSize); + status = fsw_block_get(v, kMasterDirectoryBlock, 0, &buf); + if (status) + return status; + fsw_memcpy(v->vh, buf, sizeof(HFSPlusVolumeHeader)); + fsw_block_release(v, kMasterDirectoryBlock, buf); + + // check vol. header + if (fsw_u16_be_swap(v->vh->signature) != kHFSPlusSigWord) + return FSW_UNSUPPORTED; + + // use block size specified by vol. header + bs = fsw_u32_be_swap(v->vh->blockSize); + fsw_set_blocksize(v, bs, bs); + + // set up catalog B-Tree file: + status = fsw_hfsplus_btf_setup(v, kHFSCatalogFileID, &v->vh->catalogFile, + &v->catf); + if (status) + return status; + + // set up root folder: + status = fsw_dnode_create_root(v, kHFSRootFolderID, &v->g.root); + if (status) + return status; + + return FSW_SUCCESS; +} + +/* Free the volume data structure. Called by the core after an unmount or + * unsuccessful mount, to release the memory used by the file system type + * specific part of the volume structure. + */ +static void +fsw_hfsplus_vol_free(struct fsw_hfsplus_volume *v) +{ + if (v->vh) + fsw_free(v->vh); + if (v->catf) + fsw_dnode_release((struct fsw_dnode *)v->catf); +} + +/* Get in-depth information on a volume. + */ +static fsw_status_t +fsw_hfsplus_vol_stat(struct fsw_hfsplus_volume *v, struct fsw_volume_stat *s) +{ + // FIXME: not yet supported! + return FSW_UNSUPPORTED; +} + +/* 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_hfsplus_dno_fill(struct fsw_hfsplus_volume *v, struct fsw_hfsplus_dnode *d) +{ + // NOTE: not applicable to HFS+ dnodes! + 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_hfsplus_dno_free(struct fsw_hfsplus_volume *v, struct fsw_hfsplus_dnode *d) +{ + // NOTE: not applicable to HFS+ dnodes! + return; +} + +/* Get in-depth dnode information. The core ensures fsw_hfsplus_dno_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_hfsplus_dno_stat(struct fsw_hfsplus_volume *v, struct fsw_hfsplus_dnode *d, + struct fsw_dnode_stat *s) +{ + // FIXME: not yet supported! + return FSW_SUCCESS; +} + +/* Given a B-Tree node pointed to by 'btnode', with node size 'size', + * locate the record given by its record number 'rnum'; + * Return a pointer to the B-Tree key at the beginning of the record. + */ +static HFSPlusBTKey * +fsw_hfsplus_bt_get_rec(BTNodeDescriptor* btnode, fsw_u16 size, fsw_u16 rnum) +{ + fsw_u16 *off = (fsw_u16 *)((void *)btnode + size) - 1 - rnum; + return (HFSPlusBTKey *)((void *)btnode + fsw_u16_be_swap(*off)); +} + +/* Given a B-Tree record pointer 'k', return a pointer to the data + * immediately following the key record; IOW, skip the key record which + * prefixes the record data payload. + */ +static void * +fsw_hfsplus_bt_rec_skip_key(HFSPlusBTKey *k) +{ + return (void *)k + sizeof(k->keyLength) + fsw_u16_be_swap(k->keyLength); +} + +/* Return the child node number immediately following the key record 'k' of + * an index node + */ +static fsw_u32 +fsw_hfsplus_bt_idx_get_child(HFSPlusBTKey *k) +{ + fsw_u32 *child; + child = (fsw_u32 *)fsw_hfsplus_bt_rec_skip_key(k); + return fsw_u32_be_swap(*child); +} + +/* key comparison procedure type */ +typedef int (*k_cmp_t)(HFSPlusBTKey*, HFSPlusBTKey*); + +/* Search an HFS+ special file's B-Tree (given by 'bt'), for a search key + * matching 'sk', using comparison procedure 'k_cmp' to determine when a key + * match occurs; Fill a caller-provided B-Tree node buffer ('btnode'), and + * return a pointer to the matching record data inside 'btnode' via 'data_ptr'; + * On error, set fsw_status_t return code acoordingly. + * + * NOTE: A HFS+ volume has a few "special" files, linked directly from the + * volume header. For the purpose of this driver, we mainly care about + * two of them: the "catalog" and "extents" files. All of these files + * are organized as B-Tree structures. This means that, overlaid on + * top of the linear span of each file there is an array of nodes of + * a given size (node_size), internally cross-linked with "pointers" + * to parent/child/sibling nodes, which are essentially the "index" + * (or 'node-number') of the target node in this overlaid array. + * Ultimately, (node-number * node-size) is a byte offset into the + * file, to the location where the referenced node's data begins. + * Each B-Tree file's "dnode" information is available in the HFS+ + * volume header. The node at the very beginning of each file (at + * index or node-number == 0) contains a "header node", which provides + * the 'node-number' of the B-Tree's "root" node, as well as the + * 'node-size' of all nodes in that B-Tree file. + */ +static fsw_status_t +fsw_hfsplus_bt_search(struct fsw_hfsplus_dnode *bt, + HFSPlusBTKey *sk, k_cmp_t k_cmp, + BTNodeDescriptor *btnode, void **data_ptr) +{ + fsw_u32 node; + fsw_u16 rec, lo, hi; + HFSPlusBTKey *tk; // trial key + int cmp; + fsw_status_t status; + + // start searching from the B-Tree root node: + node = bt->bt_root; + + for (;;) { + // load data for current node into caller-provided buffer 'btnode' + status = fsw_hfsplus_read(bt, (fsw_u64)node * bt->bt_ndsz, + bt->bt_ndsz, btnode); + if (status) + return status; + + // sanity check: record 0 located immediately after node descriptor + if ((void *)btnode + sizeof(BTNodeDescriptor) != + (void *)fsw_hfsplus_bt_get_rec(btnode, bt->bt_ndsz, 0)) + return FSW_VOLUME_CORRUPTED; + + // search records within current node + lo = 0; + hi = fsw_u16_be_swap(btnode->numRecords) - 1; + while (lo <= hi) { + // access record data, then compare to search key 'sk' + rec = (lo + hi) >> 1; + tk = fsw_hfsplus_bt_get_rec(btnode, bt->bt_ndsz, rec); + cmp = k_cmp(tk, sk); + + if (cmp < 0) // (tk < sk) + lo = rec + 1; + else if (cmp > 0) // (tk > sk) + hi = rec - 1; + else { // (tk == sk) + if (btnode->kind != kBTLeafNode) { + hi = rec; + break; + } + // success: return pointer to data immediately past trial key + *data_ptr = fsw_hfsplus_bt_rec_skip_key(tk); + return FSW_SUCCESS; + } + } + + // NOTE: following the binary search, 'hi' now points at the + // record with the largest 'tk' for which (tk <= sk) + + if (btnode->kind != kBTIndexNode) + break; + + // on an index node, so descend to child + tk = fsw_hfsplus_bt_get_rec(btnode, bt->bt_ndsz, hi); + node = fsw_hfsplus_bt_idx_get_child(tk); + } + + // search key 'sk' not found + return FSW_NOT_FOUND; +} + + +/* Compare unsigned integers 'a' and 'b'; + * Return -1/0/1 if 'a' is less/equal/greater than 'b'. + */ +static int +fsw_hfsplus_int_cmp(fsw_u32 a, fsw_u32 b) +{ + return (a < b) ? -1 : (a > b) ? 1 : 0; +} + +/* Basic latin unicode lowercase + */ +static fsw_u16 +fsw_hfsplus_ucblatin_tolower(fsw_u16 c) +{ + if (c == 0) + return 0xFFFF; + if (c == 0x00C6 || c == 0x00D0 || c == 0x00D8 || c == 0x00DE || + (c >= 0x0041 && c <= 0x005A)) + return c + 0x0020; + return c; + // FIXME: does edk2 have its own built-in function we could use here? +} + +/* Compare an on-disk catalog B-Tree trial key ('tk') with an in-memory + * search key ('sk'). Precedence is parentID, nodeName (keyLength does not + * factor into the comparison). + * Return -1/0/1 if 'tk'is smaller/equal/larger than 'sk', respectively. + * NOTE: all 'tk' fields are stored as big-endian values and must be + * converted to CPU endianness before any comparison to corresponding + * fields in 'sk'. + */ +static int +fsw_hfsplus_cat_cmp(HFSPlusBTKey *tk, HFSPlusBTKey *sk) +{ + fsw_u16 *t_str, *s_str; + fsw_u16 t_len, s_len; + fsw_u16 t_char, s_char; + int ret; + + // compare parent IDs: if unequal, we're done! + ret = fsw_hfsplus_int_cmp(fsw_u32_be_swap(tk->catKey.parentID), + sk->catKey.parentID); + if (ret) + return ret; + + // unicode string pointers and lengths: + t_len = fsw_u16_be_swap(tk->catKey.nodeName.length); + t_str = tk->catKey.nodeName.unicode; + s_len = sk->catKey.nodeName.length; + s_str = sk->catKey.nodeName.unicode; + + for (;;) { + // start by assuming strings are empty: + t_char = s_char = 0; + + // find next valid char from on-disk key string: + while (t_char == 0 && t_len > 0) { + t_char = fsw_hfsplus_ucblatin_tolower(fsw_u16_be_swap(*t_str)); + t_len--; + t_str++; + } + + // find next valid char from memory key string: + while (s_char == 0 && s_len > 0) { + s_char = fsw_hfsplus_ucblatin_tolower(*s_str); + s_len--; + s_str++; + } + + // stop if difference or both strings exhausted: + ret = fsw_hfsplus_int_cmp(t_char, s_char); + if (ret || s_char == 0) + break; + } + + return ret; +} + +/* 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_hfsplus_dno_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. + * NOTE: logical and physical block sizes are the same (see mount method). + */ +static fsw_status_t +fsw_hfsplus_get_ext(struct fsw_hfsplus_volume *v, struct fsw_hfsplus_dnode *d, + struct fsw_extent *e) +{ + fsw_u32 off, bc; + HFSPlusExtentRecord *er; + int i; + + // set initial offset to provided starting logical block number: + off = e->log_start; + + // start with dnode's initial extent record: + er = &d->extents; + + // search extent record: + for (i = 0; i < kHFSPlusExtentDensity; i++) { + // get block count for current extent descriptor: + bc = fsw_u32_be_swap((*er)[i].blockCount); + + // have we exhausted all available extents? + if (bc == 0) + return FSW_NOT_FOUND; + + // offset is relative to current extent's physical startBlock: + if (off < bc) { + e->type = FSW_EXTENT_TYPE_PHYSBLOCK; + e->phys_start = fsw_u32_be_swap((*er)[i].startBlock) + off; + e->log_count = bc - off; + return FSW_SUCCESS; + } + + // update offset to NEXT extent descriptor: + off -= bc; + } + + // FIXME: more than 8 fragments not yet supported! + return FSW_UNSUPPORTED; +} + + +/* 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_hfsplus_dno_fill has been called and the dnode is actually a directory. + */ +static fsw_status_t +fsw_hfsplus_dir_get(struct fsw_hfsplus_volume *v, struct fsw_hfsplus_dnode *d, + struct fsw_string *name, struct fsw_hfsplus_dnode **d_out) +{ + BTNodeDescriptor *btnode; + fsw_s16 child_rec_type; + fsw_u32 child_dno_id, child_dno_type; + HFSPlusCatalogKey k; + HFSPlusCatalogRecord *rec; + fsw_status_t status; + + // we only support FSW_STRING_TYPE_UTF16 names: + if (name->type != FSW_STRING_TYPE_UTF16 || + name->size > sizeof(k.nodeName.unicode)) + return FSW_UNSUPPORTED; + + // pre-allocate bt-node buffer for use by search function: + status = fsw_alloc(v->catf->bt_ndsz, &btnode); + if (status) + return status; + + // search catalog file for child named by 'name': + k.parentID = d->g.dnode_id; + k.nodeName.length = name->len; + fsw_memcpy(k.nodeName.unicode, name->data, name->size); + // NOTE: keyLength not used in search, setting only for completeness: + k.keyLength = sizeof(k.parentID) + sizeof(k.nodeName.length) + name->size; + status = fsw_hfsplus_bt_search(v->catf, + (HFSPlusBTKey *)&k, fsw_hfsplus_cat_cmp, + btnode, (void **)&rec); + if (status) + goto done; + + // child record immediately follows the record key data: + child_rec_type = fsw_u16_be_swap(rec->recordType); + if (child_rec_type == kHFSPlusFolderRecord) { + child_dno_id = fsw_u32_be_swap(rec->folderRecord.folderID); + child_dno_type = FSW_DNODE_TYPE_DIR; + } else if (child_rec_type == kHFSPlusFileRecord) { + child_dno_id = fsw_u32_be_swap(rec->fileRecord.fileID); + child_dno_type = FSW_DNODE_TYPE_FILE; + } else { + child_dno_id = 0; + child_dno_type = FSW_DNODE_TYPE_UNKNOWN; + } + status = fsw_dnode_create(d, child_dno_id, child_dno_type, name, d_out); + if (status) + goto done; + + // if child node is a file, set size and initial extents: + if (child_rec_type == kHFSPlusFileRecord) { + (*d_out)->g.size = + fsw_u64_be_swap(rec->fileRecord.dataFork.logicalSize); + fsw_memcpy((*d_out)->extents, &rec->fileRecord.dataFork.extents, + sizeof(HFSPlusExtentRecord)); + } + +done: + fsw_free(btnode); + 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_hfsplus_dno_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_hfsplus_dir_read(struct fsw_hfsplus_volume *v, struct fsw_hfsplus_dnode *d, + struct fsw_shandle *sh, struct fsw_hfsplus_dnode **d_out) +{ + // FIXME: not yet supported! + return FSW_UNSUPPORTED; +} + +/* Get the target path of a symbolic link. This function is called when a + * symbolic link needs to be resolved. The core makes sure that the + * fsw_hfsplus_dno_fill has been called on the dnode and that it really is a + * symlink. + */ +static fsw_status_t +fsw_hfsplus_readlink(struct fsw_hfsplus_volume *v, struct fsw_hfsplus_dnode *d, + struct fsw_string *lnk_tgt) +{ + // FIXME: not yet supported! + return FSW_UNSUPPORTED; +} + + +/* HFS+ FSW Method Dispatch Table + */ +struct fsw_fstype_table FSW_FSTYPE_TABLE_NAME(hfsplus) = { + { FSW_STRING_TYPE_ISO88591, 4, 4, "hfsplus" }, + sizeof(struct fsw_hfsplus_volume), sizeof(struct fsw_hfsplus_dnode), + fsw_hfsplus_vol_mount, fsw_hfsplus_vol_free, fsw_hfsplus_vol_stat, + fsw_hfsplus_dno_fill, fsw_hfsplus_dno_free, fsw_hfsplus_dno_stat, + fsw_hfsplus_get_ext, fsw_hfsplus_dir_get, fsw_hfsplus_dir_read, + fsw_hfsplus_readlink, +}; diff --git a/OvmfPkg/FswHfsPlus/fsw_hfsplus.h b/OvmfPkg/FswHfsPlus/fsw_hfsplus.h new file mode 100644 index 0000000..38f4631 --- /dev/null +++ b/OvmfPkg/FswHfsPlus/fsw_hfsplus.h @@ -0,0 +1,238 @@ +/** @file + HFS+ file system driver header. + + Copyright (C) 2017, Gabriel L. Somlo + + This program and the accompanying materials are licensed and made + available under the terms and conditions of the BSD License which + accompanies this distribution. The full text of the license may + be found at http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER + EXPRESS OR IMPLIED. +**/ + +#ifndef _FSW_HFSPLUS_H_ +#define _FSW_HFSPLUS_H_ + +#define VOLSTRUCTNAME fsw_hfsplus_volume +#define DNODESTRUCTNAME fsw_hfsplus_dnode +#include "fsw_core.h" + +/*============= HFS+ constants and data types from Apple TN1150 =============*/ +#pragma pack(1) + +#define kHFSBlockSize 512 // Minimum block size to transfer Vol.Hdr. +#define kMasterDirectoryBlock 2 // Vol.Hdr. disk offset (x kHFSBlockSize) + +#define kHFSPlusSigWord 0x482B // HFS+ volume signature (ASCII for 'H+') + +#define kHFSPlusMaxFileNameChars 255 // Max. length of HFS+ folder or filename + +#define kHFSPlusExtentDensity 8 // Number of extent descriptors per record + +#define kHFSPlusDataFork 0x00 // data fork type +#define kHFSPlusResourceFork 0xFF // resource fork type + +#define kHFSRootFolderID 2 // ID of the root folder +#define kHFSExtentsFileID 3 // ID of the extent overflow file +#define kHFSCatalogFileID 4 // ID of the catalog file + +#define kHFSPlusFolderRecord 1 // catalog folder record type +#define kHFSPlusFileRecord 2 // catalog file record type + +#define kBTLeafNode -1 // B-Tree leaf node type +#define kBTIndexNode 0 // B-Tree index node type +#define kBTHeaderNode 1 // B-Tree header node type + +// file/folder name unicode string type +typedef struct { + fsw_u16 length; // character count + fsw_u16 unicode[kHFSPlusMaxFileNameChars]; // character string +} HFSUniStr255; + +// extent descriptor type +typedef struct { + fsw_u32 startBlock; // first allocation block in the extent + fsw_u32 blockCount; // extent length, in allocation blocks +} HFSPlusExtentDescriptor; + +// extent record type +typedef HFSPlusExtentDescriptor HFSPlusExtentRecord[kHFSPlusExtentDensity]; + +// fork-data information type +typedef struct { + fsw_u64 logicalSize; // size of valid fork data, in bytes + fsw_u32 clumpSize; // fork-specific clump size + fsw_u32 totalBlocks; // total blocks across all extents in fork + HFSPlusExtentRecord extents; // initial extents for the fork data +} HFSPlusForkData; + +// volume header type +typedef struct { + fsw_u16 signature; // should be kHFSPlusSigWord + fsw_u16 version; // should be kHFSPlusVersion + fsw_u32 attributes; // volume attributes + fsw_u32 lastMountedVersion; // unique ID of software that last wrote volume + fsw_u32 journalInfoBlock; // block no. of journal info + fsw_u32 createDate; // volume creation timestamp (date and time) + fsw_u32 modifyDate; // volume last modification timestamp + fsw_u32 backupDate; // timestamp of last backup + fsw_u32 checkedDate; // timestamp of last consistency check + fsw_u32 fileCount; // total number of files on volume + fsw_u32 folderCount; // total number of folders on volume + fsw_u32 blockSize; // allocation block size, in bytes + fsw_u32 totalBlocks; // total number of allocation blocks + fsw_u32 freeBlocks; // total number of unused allocation blocks + fsw_u32 nextAllocation; // block number to start next allocation search + fsw_u32 rsrcClumpSize; // dflt. resource fork clump size (in bytes) + fsw_u32 dataClumpSize; // dflt. data fork clump size (in bytes) + fsw_u32 nextCatalogID; // next unused catalog ID + fsw_u32 writeCount; // incr. each time volume is write-mounted + fsw_u64 encodingsBitmap; // keep track of text encodings used in names + fsw_u8 finderInfo[32]; // information used by the OS X Finder + HFSPlusForkData allocationFile; // info re. size & location of alloc. file + HFSPlusForkData extentsFile; // info re. size & location of extents file + HFSPlusForkData catalogFile; // info re. size & location of catalog file + HFSPlusForkData attributesFile; // info re. size & location of attr. file + HFSPlusForkData startupFile; // info re. size & location of startup file +} HFSPlusVolumeHeader; + +// file permissions type +typedef struct { + fsw_u32 ownerID; // owner UID (or hard-link previous-link) + fsw_u32 groupID; // owner GID (or hard-link next-link) + fsw_u8 adminFlags; // flags changeable by root only + fsw_u8 ownerFlags; // flags changeable by owner + fsw_u16 fileMode; // BSD file type and mode bits + union { + fsw_u32 iNodeNum; // link reference number, if hard-link + fsw_u32 linkCount; // ref-count of hard-links, if indirect node file + fsw_u32 rawDevice; // device number, if block/char special dev. file + } special; // reserved for directories and most files +} HFSPlusBSDInfo; + +// finder info types +typedef struct { + fsw_u8 opaque[16]; +} FndrFileInfo, FndrDirInfo, FndrOpaqueInfo; + +// catalog folder record type +typedef struct { + fsw_s16 recordType; // should be kHFSPlusFolderRecord + fsw_u16 flags; // bit flags about the folder (reserved) + fsw_u32 valence; // items directly contained by this folder + fsw_u32 folderID; // CNID of this folder + fsw_u32 createDate; // folder creation timestamp (date and time) + fsw_u32 contentModDate; // folder content last modification timestamp + fsw_u32 attributeModDate; // ctime (last change to a catalog record field) + fsw_u32 accessDate; // atime (last access timestamp) + fsw_u32 backupDate; // timestamp of last backup + HFSPlusBSDInfo permissions; // folder permissions + FndrDirInfo userInfo; // information used by the OS X Finder + FndrOpaqueInfo finderInfo; // additional information for the OS X Finder + fsw_u32 textEncoding; // hint re. folder name text encoding + fsw_u32 reserved; // reserved field +} HFSPlusCatalogFolder; + +// catalog file record type +typedef struct { + fsw_s16 recordType; // should be kHFSPlusFileRecord + fsw_u16 flags; // bit flags about the file (reserved) + fsw_u32 reserved1; // reserved field + fsw_u32 fileID; // CNID of this file + fsw_u32 createDate; // file creation timestamp (date and time) + fsw_u32 contentModDate; // file content last modification timestamp + fsw_u32 attributeModDate; // ctime (last change to a catalog record field) + fsw_u32 accessDate; // atime (last access timestamp) + fsw_u32 backupDate; // timestamp of last backup + HFSPlusBSDInfo permissions; // file permissions + FndrFileInfo userInfo; // information used by the OS X Finder + FndrOpaqueInfo finderInfo; // additional information for the OS X Finder + fsw_u32 textEncoding; // hint re. file name text encoding + fsw_u32 reserved2; // reserved field + HFSPlusForkData dataFork; // info re. size & location of data fork + HFSPlusForkData resourceFork; // info re. size & location of resource fork +} HFSPlusCatalogFile; + +// generic (union) catalog record type +typedef union { + fsw_s16 recordType; // set to kHFSPlus[Folder|File]Record + HFSPlusCatalogFolder folderRecord; // catalog folder record fields + HFSPlusCatalogFile fileRecord; // catalog file record fields +} HFSPlusCatalogRecord; + +// B-Tree node descriptor found at the start of each node +typedef struct { + fsw_u32 fLink; // number of next node of this type (0 if we're last) + fsw_u32 bLink; // number of prev. node of this type (0 if we're first) + fsw_s8 kind; // node type (leaf, index, header, map) + fsw_u8 height; // node depth in B-Tree hierarchy (0 for header) + fsw_u16 numRecords; // number of records contained in this node + fsw_u16 reserved; // reserved field +} BTNodeDescriptor; + +// B-Tree header record type (first record of a B-Tree header node) +typedef struct { + fsw_u16 treeDepth; // current B-Tree depth (always == rootNode.height) + fsw_u32 rootNode; // node number of B-Tree root node + fsw_u32 leafRecords; // total number of records across all leaf nodes + fsw_u32 firstLeafNode; // node number of first leaf node + fsw_u32 lastLeafNode; // node number of last leaf node + fsw_u16 nodeSize; // node size (in bytes) + fsw_u16 maxKeyLength; // max. length of a key in index/leaf node + fsw_u32 totalNodes; // total number of nodes in the B-Tree + fsw_u32 freeNodes; // number of unused nodes in the B-Tree + fsw_u16 reserved1; // reserved field + fsw_u32 clumpSize; // reserved field (deprecated) + fsw_u8 btreeType; // reserved (0 for catalog, extents, attrib. file) + fsw_u8 keyCompareType; // case-sensitive string comparison (HFSX only) + fsw_u32 attributes; // B-Tree attributes + fsw_u32 reserved3[16]; // reserved field +} BTHeaderRec; + +// extent overflow file key type +typedef struct { + fsw_u16 keyLength; // key length (excluding this field) + fsw_u8 forkType; // data or resource fork + fsw_u8 pad; // ensure 32-bit alignment for subsequent fields + fsw_u32 fileID; // CNID of file to which this extent record applies + fsw_u32 startBlock; // start block of first extent described by this record +} HFSPlusExtentKey; + +// catalog file key type +typedef struct { + fsw_u16 keyLength; // key length (excluding this field) + fsw_u32 parentID; // ID of parent folder (or CNID if thread record) + HFSUniStr255 nodeName; // basename of file or folder +} HFSPlusCatalogKey; + +// generic (union) B-Tree record key type +typedef union { + fsw_u16 keyLength; // key length (excluding this field) + HFSPlusExtentKey extKey; // extent key fields + HFSPlusCatalogKey catKey; // catalog key fields +} HFSPlusBTKey; + +#pragma pack() +/*========= end HFS+ constants and data types from Apple TN1150 =============*/ + + +// FSW: HFS+ specific dnode +struct fsw_hfsplus_dnode { + struct fsw_dnode g; // Generic (parent) dnode structure + HFSPlusExtentRecord extents; // HFS+ initial extent record + fsw_u32 bt_root; // root node index (if B-Tree file) + fsw_u16 bt_ndsz; // node size (if B-Tree file) +}; + + +// FSW: HFS+ specific volume +struct fsw_hfsplus_volume { + struct fsw_volume g; // Generic (parent) volume structure + HFSPlusVolumeHeader *vh; // Raw HFS+ Volume Header + struct fsw_hfsplus_dnode *catf; // Catalog file dnode +}; + +#endif // _FSW_HFSPLUS_H_ -- 2.7.4