/* $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 <Library/MemLogLib.h>
#include <Library/PrintLib.h>


#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; i<s.len; i++) {
      DBG("%02x ", ((fsw_u16*)vol->g.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; i<vn.len; i++) {
            DBG("%02x ", ((fsw_u16*)vol->g.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; i<name_len; i++) {
    DBG("%c", name_ptr[i]?name_ptr[i]:L'@');
  }
  DBG("\n"); */
  return 1;
}

static fsw_status_t
fsw_hfs_btree_iterate_node (struct fsw_hfs_btree *btree,
                            BTNodeDescriptor     *first_node,
                            fsw_u32              first_rec,
                            int                  (*callback) (BTreeKey *record, void* param),
                            void                 *param)
{
  fsw_status_t status;
  /* We modify node, so make a copy */
  BTNodeDescriptor *node = first_node;
  fsw_u8 *buffer = NULL;

  if (!btree || !btree->node_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(&param, sizeof(visitor_parameter_t));
  param.parent = lookup_id;

  status = fsw_hfs_btree_iterate_node (&vol->catalog_tree,
                                       node,
                                       ptr,
                                       fsw_hfs_cmp_id,
                                       &param);

  if (status) {    
    goto done;
  }
  fsw_memcpy(file_info, &param.file_info, sizeof(file_info_t));
//  status = create_hfs_dnode(dno, &param.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(&param, 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,
                                       &param);
  if (!status) {  
    status = create_hfs_dnode(dno, &param.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
