hfs_open(TSK_IMG_INFO * img_info, TSK_OFF_T offset,
    TSK_FS_TYPE_ENUM ftype, uint8_t test)
{
    HFS_INFO *hfs;
    unsigned int len;
    TSK_FS_INFO *fs;
    ssize_t cnt;
    TSK_FS_FILE *file;          // The root directory, or the metadata directories
    TSK_INUM_T inum;            // The inum (or CNID) of the metadata directories
    int8_t result;              // of tsk_fs_path2inum()

    tsk_error_reset();

    if (TSK_FS_TYPE_ISHFS(ftype) == 0) {
        tsk_error_set_errno(TSK_ERR_FS_ARG);
        tsk_error_set_errstr("Invalid FS Type in hfs_open");
        return NULL;
    }

    if ((hfs = (HFS_INFO *) tsk_fs_malloc(sizeof(HFS_INFO))) == NULL)
        return NULL;

    fs = &(hfs->fs_info);

    fs->ftype = TSK_FS_TYPE_HFS;
    fs->duname = "Allocation Block";
    fs->tag = TSK_FS_INFO_TAG;
    fs->flags = 0;

    fs->img_info = img_info;
    fs->offset = offset;

    /*
     * Read the superblock.
     */
    len = sizeof(hfs_plus_vh);
    if ((hfs->fs = (hfs_plus_vh *) tsk_malloc(len)) == NULL) {
        fs->tag = 0;
        tsk_fs_free((TSK_FS_INFO *)hfs);
        return NULL;
    }

    if (hfs_checked_read_random(fs, (char *) hfs->fs, len,
            (TSK_OFF_T) HFS_VH_OFF)) {
        tsk_error_set_errstr2("hfs_open: superblock");
        fs->tag = 0;
        free(hfs->fs);
        tsk_fs_free((TSK_FS_INFO *)hfs);
        return NULL;
    }

    /*
     * Verify we are looking at an HFS+ image
     */
    if (tsk_fs_guessu16(fs, hfs->fs->signature, HFS_VH_SIG_HFSPLUS) &&
        tsk_fs_guessu16(fs, hfs->fs->signature, HFS_VH_SIG_HFSX) &&
        tsk_fs_guessu16(fs, hfs->fs->signature, HFS_VH_SIG_HFS)) {

        fs->tag = 0;
        free(hfs->fs);
        tsk_fs_free((TSK_FS_INFO *)hfs);
        tsk_error_set_errno(TSK_ERR_FS_MAGIC);
        tsk_error_set_errstr("not an HFS+ file system (magic)");
        return NULL;
    }

    /*
     * Handle an HFS-wrapped HFS+ image, which is a HFS volume that contains
     * the HFS+ volume inside of it.
     */
    if (tsk_getu16(fs->endian, hfs->fs->signature) == HFS_VH_SIG_HFS) {

        hfs_mdb *wrapper_sb = (hfs_mdb *) hfs->fs;

        // Verify that we are setting a wrapper and not a normal HFS volume
        if ((tsk_getu16(fs->endian,
                    wrapper_sb->drEmbedSigWord) == HFS_VH_SIG_HFSPLUS)
            || (tsk_getu16(fs->endian,
                    wrapper_sb->drEmbedSigWord) == HFS_VH_SIG_HFSX)) {

            TSK_FS_INFO *fs_info2;
            // offset in sectors to start of first HFS block
            uint16_t drAlBlSt =
                tsk_getu16(fs->endian, wrapper_sb->drAlBlSt);

            // size of each HFS block
            uint32_t drAlBlkSiz =
                tsk_getu32(fs->endian, wrapper_sb->drAlBlkSiz);

            // start of embedded FS
            uint16_t startBlock = tsk_getu16(fs->endian,
                wrapper_sb->drEmbedExtent_startBlock);

            // calculate the offset; 512 here is intentional.
            // TN1150 says "The drAlBlSt field contains the offset, in
            // 512-byte blocks, of the wrapper's allocation block 0 relative
            // to the start of the volume"
            TSK_OFF_T hfsplus_offset =
                (drAlBlSt * (TSK_OFF_T) 512) +
                (drAlBlkSiz * (TSK_OFF_T) startBlock);

            if (tsk_verbose)
                tsk_fprintf(stderr,
                    "hfs_open: HFS+/HFSX within HFS wrapper at byte offset %"
                    PRIuOFF "\n", hfsplus_offset);

            fs->tag = 0;
            free(hfs->fs);
            tsk_fs_free((TSK_FS_INFO *)hfs);

            /* just re-open with the new offset, then record the offset */
            if (hfsplus_offset == 0) {
                tsk_error_set_errno(TSK_ERR_FS_CORRUPT);
                tsk_error_set_errstr("HFS+ offset is zero");
                return NULL;
            }
            fs_info2 =
                hfs_open(img_info, offset + hfsplus_offset, ftype, test);

            if (fs_info2)
                ((HFS_INFO *) fs_info2)->hfs_wrapper_offset =
                    hfsplus_offset;

            return fs_info2;
        }
        else {
            fs->tag = 0;
            free(hfs->fs);
            tsk_fs_free((TSK_FS_INFO *)hfs);
            tsk_error_set_errno(TSK_ERR_FS_MAGIC);
            tsk_error_set_errstr
                ("HFS file systems (other than wrappers HFS+/HFSX file systems) are not supported");
            return NULL;
        }
    }

    fs->block_count = tsk_getu32(fs->endian, hfs->fs->blk_cnt);
    fs->first_block = 0;
    fs->last_block = fs->last_block_act = fs->block_count - 1;

    /* this isn't really accurate; fs->block_size reports only the size
       of the allocation block; the size of the device block has to be
       found from the device (allocation block size should always be
       larger than device block size and an even multiple of the device
       block size) */
    fs->dev_bsize = fs->block_size =
        tsk_getu32(fs->endian, hfs->fs->blk_sz);

    // determine the last block we have in this image
    if (fs->block_size <= 1) {
        fs->tag = 0;
        free(hfs->fs);
        tsk_fs_free((TSK_FS_INFO *)hfs);
        tsk_error_set_errno(TSK_ERR_FS_CORRUPT);
        tsk_error_set_errstr("HFS+ allocation block size too small");
        return NULL;
    }
    if ((TSK_DADDR_T) ((img_info->size - offset) / fs->block_size) <
        fs->block_count)
        fs->last_block_act =
            (img_info->size - offset) / fs->block_size - 1;

    // Initialize the lock
    tsk_init_lock(&(hfs->metadata_dir_cache_lock));

    /*
     * Set function pointers
     */
    fs->inode_walk = hfs_inode_walk;
    fs->block_walk = hfs_block_walk;
    fs->block_getflags = hfs_block_getflags;
    fs->load_attrs = hfs_load_attrs;
    fs->get_default_attr_type = hfs_get_default_attr_type;

    fs->file_add_meta = hfs_inode_lookup;
    fs->dir_open_meta = hfs_dir_open_meta;
    fs->fsstat = hfs_fsstat;
    fs->fscheck = hfs_fscheck;
    fs->istat = hfs_istat;
    fs->close = hfs_close;

    // lazy loading of block map
    hfs->blockmap_file = NULL;
    hfs->blockmap_attr = NULL;
    hfs->blockmap_cache_start = -1;
    hfs->blockmap_cache_len = 0;

    fs->first_inum = HFS_ROOT_INUM;
    fs->root_inum = HFS_ROOT_INUM;
    fs->last_inum = HFS_FIRST_USER_CNID - 1;    // we will later increase this
    fs->inum_count = fs->last_inum - fs->first_inum + 1;

    /* We will load the extents file data when we need it */
    hfs->extents_file = NULL;
    hfs->extents_attr = NULL;

    if (tsk_getu32(fs->endian,
                hfs->fs->start_file.extents[0].blk_cnt) == 0) {
            if (tsk_verbose)
                tsk_fprintf(stderr,
                    "hfs_open: Optional Startup File is not present.\n");
            hfs->has_startup_file = FALSE;
        }
    else {
        if (tsk_verbose)
            tsk_fprintf(stderr, "hfs_open: Startup File is present.\n");
        hfs->has_startup_file = TRUE;
    }

    if (tsk_getu32(fs->endian, hfs->fs->ext_file.extents[0].blk_cnt) == 0) {
        if (tsk_verbose)
            tsk_fprintf(stderr,
                "hfs_open: Optional Extents File (and Badblocks File) is not present.\n");
        hfs->has_extents_file = FALSE;
    }
    else {
        if (tsk_verbose)
            tsk_fprintf(stderr,
                "hfs_open: Extents File (and BadBlocks File) is present.\n");
        hfs->has_extents_file = TRUE;
    }

    if (tsk_getu32(fs->endian, hfs->fs->attr_file.extents[0].blk_cnt) == 0) {
        if (tsk_verbose)
            tsk_fprintf(stderr,
                "hfs_open: Optional Attributes File is not present.\n");
        hfs->has_attributes_file = FALSE;
    }
    else {
        if (tsk_verbose)
            tsk_fprintf(stderr, "hfs_open: Attributes File is present.\n");
        hfs->has_attributes_file = TRUE;
    }

    /* Load the catalog file though */
    if ((hfs->catalog_file =
            tsk_fs_file_open_meta(fs, NULL,
                HFS_CATALOG_FILE_ID)) == NULL) {
        hfs_close(fs);
        return NULL;
    }

    /* cache the data attribute */
    hfs->catalog_attr =
        tsk_fs_attrlist_get(hfs->catalog_file->meta->attr,
        TSK_FS_ATTR_TYPE_DEFAULT);
    if (!hfs->catalog_attr) {
        hfs_close(fs);
        tsk_error_errstr2_concat
            (" - Data Attribute not found in Catalog File");
        return NULL;
    }

    // cache the catalog file header
    cnt = tsk_fs_attr_read(hfs->catalog_attr, 14,
        (char *) &(hfs->catalog_header),
        sizeof(hfs_btree_header_record), 0);
    if (cnt != sizeof(hfs_btree_header_record)) {
        if (cnt >= 0) {
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS_READ);
        }
        hfs_close(fs);
        tsk_error_set_errstr2("hfs_open: Error reading catalog header");
        return NULL;
    }

    if (tsk_getu16(fs->endian, hfs->fs->version) == HFS_VH_VER_HFSPLUS)
        hfs->is_case_sensitive = 0;
    else if (tsk_getu16(fs->endian, hfs->fs->version) == HFS_VH_VER_HFSX) {
        if (hfs->catalog_header.compType == HFS_BT_HEAD_COMP_SENS)
            hfs->is_case_sensitive = 1;
        else if (hfs->catalog_header.compType == HFS_BT_HEAD_COMP_INSENS)
            hfs->is_case_sensitive = 0;
        else {
            if (tsk_verbose)
                tsk_fprintf(stderr,
                    "hfs_open: invalid value (0x%02" PRIx8
                    ") for key compare type; using case-insensitive\n",
                    hfs->catalog_header.compType);
            hfs->is_case_sensitive = 0;
        }
    }
    else {
        if (tsk_verbose)
            tsk_fprintf(stderr,
                "hfs_open: unknown HFS+/HFSX version (%" PRIu16 "\n",
                tsk_getu16(fs->endian, hfs->fs->version));
        hfs->is_case_sensitive = 0;
    }

    // update the numbers.
    fs->last_inum = hfs_find_highest_inum(hfs);
    fs->inum_count = fs->last_inum + 1;

    snprintf((char *) fs->fs_id, 17, "%08" PRIx32 "%08" PRIx32,
        tsk_getu32(fs->endian, hfs->fs->finder_info[HFS_VH_FI_ID1]),
        tsk_getu32(fs->endian, hfs->fs->finder_info[HFS_VH_FI_ID2]));
    fs->fs_id_used = 16;

    /* journal */
    fs->jblk_walk = hfs_jblk_walk;
    fs->jentry_walk = hfs_jentry_walk;
    fs->jopen = hfs_jopen;
    fs->name_cmp = hfs_name_cmp;
    fs->journ_inum = 0;

    /* Creation Times */

    // First, the root
    file = tsk_fs_file_open_meta(fs, NULL, 2);
    if (file != NULL) {
        hfs->root_crtime = file->meta->crtime;
        hfs->has_root_crtime = TRUE;
        tsk_fs_file_close(file);
    }
    else {
        hfs->has_root_crtime = FALSE;
    }
    file = NULL;

    // disable hard link traversal while finding the hard
    // link directories themselves (to prevent problems if
    // there are hard links in the root directory)
    hfs->meta_inum = 0;
    hfs->meta_dir_inum = 0;

    // Now the (file) metadata directory

    // The metadata directory is a sub-directory of the root.  Its name begins with four nulls, followed
    // by "HFS+ Private Data".  The file system parsing code replaces nulls in filenames with UTF8_NULL_REPLACE.
    // In the released version of TSK, this replacement is the character '^'.
    // NOTE: There is a standard Unicode replacement which is 0xfffd in UTF16 and 0xEF 0xBF 0xBD in UTF8.
    // Systems that require the standard definition can redefine UTF8_NULL_REPLACE and UTF16_NULL_REPLACE
    // in tsk_hfs.h
    hfs->has_meta_crtime = FALSE;
    result =
        tsk_fs_path2inum(fs,
        "/" UTF8_NULL_REPLACE UTF8_NULL_REPLACE UTF8_NULL_REPLACE
        UTF8_NULL_REPLACE "HFS+ Private Data", &inum, NULL);
    if (result == 0) {
        TSK_FS_FILE *file_tmp = tsk_fs_file_open_meta(fs, NULL, inum);
        if (file_tmp != NULL) {
            hfs->meta_crtime = file_tmp->meta->crtime;
            hfs->has_meta_crtime = TRUE;
            hfs->meta_inum = inum;
            tsk_fs_file_close(file_tmp);
        }
    }

    // Now, the directory metadata directory

    // The "directory" metadata directory, where hardlinked directories actually live, is a subdirectory
    // of the root.  The beginning of the name of this directory is ".HFS+ Private Directory Data" which
    // is followed by a carriage return (ASCII 13).
    hfs->has_meta_dir_crtime = FALSE;
    result =
        tsk_fs_path2inum(fs, "/.HFS+ Private Directory Data\r", &inum,
        NULL);
    if (result == 0) {
        TSK_FS_FILE *file_tmp = tsk_fs_file_open_meta(fs, NULL, inum);
        if (file_tmp != NULL) {
            hfs->metadir_crtime = file_tmp->meta->crtime;
            hfs->has_meta_dir_crtime = TRUE;
            hfs->meta_dir_inum = inum;
            tsk_fs_file_close(file_tmp);
        }
    }

    if (hfs->has_root_crtime && hfs->has_meta_crtime
        && hfs->has_meta_dir_crtime) {
        if (tsk_verbose)
            tsk_fprintf(stderr,
                "hfs_open: Creation times for key folders have been read and cached.\n");
    }
    if (!hfs->has_root_crtime) {
        if (tsk_verbose)
            tsk_fprintf(stderr,
                "hfs_open: Warning: Could not open the root directory.  "
                "Hard link detection and some other functions will be impaired\n");
    }
    else if (tsk_verbose) {
        tsk_fprintf(stderr,
            "hfs_open: The root directory is accessible.\n");
    }

    if (tsk_verbose) {
        if (hfs->has_meta_crtime)
            tsk_fprintf(stderr,
                "hfs_open: \"/^^^^HFS+ Private Data\" metadata folder is accessible.\n");
        else
            tsk_fprintf(stderr,
                "hfs_open: Optional \"^^^^HFS+ Private Data\" metadata folder is not accessible, or does not exist.\n");
        if (hfs->has_meta_dir_crtime)
            tsk_fprintf(stderr,
                "hfs_open: \"/HFS+ Private Directory Data^\" metadata folder is accessible.\n");
        else
            tsk_fprintf(stderr,
                "hfs_open: Optional \"/HFS+ Private Directory Data^\" metadata folder is not accessible, or does not exist.\n");
    }

    // These caches will be set, if they are needed.
    hfs->meta_dir = NULL;
    hfs->dir_meta_dir = NULL;

    return fs;
}