/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2005,2006,2007,2008,2009,2012,2013  Free Software Foundation, Inc.
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/types.h>
#include <grub/disk.h>

#define GRUB_HFSPLUS_MAGIC 0x482B
#define GRUB_HFSPLUSX_MAGIC 0x4858
#define GRUB_HFSPLUS_SBLOCK 2

/* A HFS+ extent.  */
struct grub_hfsplus_extent
{
  /* The first block of a file on disk.  */
  grub_uint32_t start;
  /* The amount of blocks described by this extent.  */
  grub_uint32_t count;
} GRUB_PACKED;

/* The descriptor of a fork.  */
struct grub_hfsplus_forkdata
{
  grub_uint64_t size;
  grub_uint32_t clumpsize;
  grub_uint32_t blocks;
  struct grub_hfsplus_extent extents[8];
} GRUB_PACKED;

/* The HFS+ Volume Header.  */
struct grub_hfsplus_volheader
{
  grub_uint16_t magic;
  grub_uint16_t version;
  grub_uint32_t attributes;
  grub_uint8_t unused1[12];
  grub_uint32_t utime;
  grub_uint8_t unused2[16];
  grub_uint32_t blksize;
  grub_uint8_t unused3[36];

  grub_uint32_t ppc_bootdir;
  grub_uint32_t intel_bootfile;
  /* Folder opened when disk is mounted. Unused by GRUB. */
  grub_uint32_t showfolder;
  grub_uint32_t os9folder;
  grub_uint8_t unused4[4];
  grub_uint32_t osxfolder;

  grub_uint64_t num_serial;
  struct grub_hfsplus_forkdata allocations_file;
  struct grub_hfsplus_forkdata extents_file;
  struct grub_hfsplus_forkdata catalog_file;
  struct grub_hfsplus_forkdata attr_file;
  struct grub_hfsplus_forkdata startup_file;
} GRUB_PACKED;

struct grub_hfsplus_compress_index
{
  grub_uint32_t start;
  grub_uint32_t size;
};

struct grub_hfsplus_file
{
  struct grub_hfsplus_data *data;
  struct grub_hfsplus_extent extents[8];
  struct grub_hfsplus_extent resource_extents[8];
  grub_uint64_t size;
  grub_uint64_t resource_size;
  grub_uint32_t fileid;
  grub_int32_t mtime;
  int compressed;
  char *cbuf;
  void *file;
  struct grub_hfsplus_compress_index *compress_index;
  grub_uint32_t cbuf_block;
  grub_uint32_t compress_index_size;
};

struct grub_hfsplus_btree
{
  grub_uint32_t root;
  grub_size_t nodesize;

  /* Catalog file node.  */
  struct grub_hfsplus_file file;
};

/* Information about a "mounted" HFS+ filesystem.  */
struct grub_hfsplus_data
{
  struct grub_hfsplus_volheader volheader;
  grub_disk_t disk;

  unsigned int log2blksize;

  struct grub_hfsplus_btree catalog_tree;
  struct grub_hfsplus_btree extoverflow_tree;
  struct grub_hfsplus_btree attr_tree;

  struct grub_hfsplus_file dirroot;
  struct grub_hfsplus_file opened_file;

  /* This is the offset into the physical disk for an embedded HFS+
     filesystem (one inside a plain HFS wrapper).  */
  grub_disk_addr_t embedded_offset;
  int case_sensitive;
};

/* Internal representation of a catalog key.  */
struct grub_hfsplus_catkey_internal
{
  grub_uint32_t parent;
  const grub_uint16_t *name;
  grub_size_t namelen;
};

/* Internal representation of an extent overflow key.  */
struct grub_hfsplus_extkey_internal
{
  grub_uint32_t fileid;
  grub_uint32_t start;
  grub_uint8_t type;
};

struct grub_hfsplus_attrkey
{
  grub_uint16_t keylen;
  grub_uint16_t unknown1[1];
  grub_uint32_t cnid;
  grub_uint16_t unknown2[2];
  grub_uint16_t namelen;
  grub_uint16_t name[0];
} GRUB_PACKED;

struct grub_hfsplus_attrkey_internal
{
  grub_uint32_t cnid;
  const grub_uint16_t *name;
  grub_size_t namelen;
};

struct grub_hfsplus_key_internal
{
  union
  {
    struct grub_hfsplus_extkey_internal extkey;
    struct grub_hfsplus_catkey_internal catkey;
    struct grub_hfsplus_attrkey_internal attrkey;
  };
};

/* The on disk layout of a catalog key.  */
struct grub_hfsplus_catkey
{
  grub_uint16_t keylen;
  grub_uint32_t parent;
  grub_uint16_t namelen;
  grub_uint16_t name[0];
} GRUB_PACKED;

/* The on disk layout of an extent overflow file key.  */
struct grub_hfsplus_extkey
{
  grub_uint16_t keylen;
  grub_uint8_t type;
  grub_uint8_t unused;
  grub_uint32_t fileid;
  grub_uint32_t start;
} GRUB_PACKED;

struct grub_hfsplus_key
{
  union
  {
    struct grub_hfsplus_extkey extkey;
    struct grub_hfsplus_catkey catkey;
    struct grub_hfsplus_attrkey attrkey;
    grub_uint16_t keylen;
  };
} GRUB_PACKED;

struct grub_hfsplus_btnode
{
  grub_uint32_t next;
  grub_uint32_t prev;
  grub_int8_t type;
  grub_uint8_t height;
  grub_uint16_t count;
  grub_uint16_t unused;
} GRUB_PACKED;

/* Return the offset of the record with the index INDEX, in the node
   NODE which is part of the B+ tree BTREE.  */
static inline grub_uint16_t
grub_hfsplus_btree_recoffset (struct grub_hfsplus_btree *btree,
			   struct grub_hfsplus_btnode *node, unsigned index)
{
  char *cnode = (char *) node;
  void *recptr;
  if (btree->nodesize < index * sizeof (grub_uint16_t) + 2)
    index = 0;
  recptr = (&cnode[btree->nodesize - index * sizeof (grub_uint16_t) - 2]);
  return grub_be_to_cpu16 (grub_get_unaligned16 (recptr));
}

/* Return a pointer to the record with the index INDEX, in the node
   NODE which is part of the B+ tree BTREE.  */
static inline struct grub_hfsplus_key *
grub_hfsplus_btree_recptr (struct grub_hfsplus_btree *btree,
			   struct grub_hfsplus_btnode *node, unsigned index)
{
  char *cnode = (char *) node;
  grub_uint16_t offset;
  offset = grub_hfsplus_btree_recoffset (btree, node, index);
  if (offset > btree->nodesize - sizeof (struct grub_hfsplus_key))
    offset = 0;
  return (struct grub_hfsplus_key *) &cnode[offset];
}

extern grub_err_t (*grub_hfsplus_open_compressed) (struct grub_hfsplus_file *node);
extern grub_ssize_t (*grub_hfsplus_read_compressed) (struct grub_hfsplus_file *node,
						     grub_off_t pos,
						     grub_size_t len,
						     char *buf);

grub_ssize_t
grub_hfsplus_read_file (struct grub_hfsplus_file *node,
			grub_disk_read_hook_t read_hook, void *read_hook_data,
			grub_off_t pos, grub_size_t len, char *buf);
grub_err_t
grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree,
			   struct grub_hfsplus_key_internal *key,
			   int (*compare_keys) (struct grub_hfsplus_key *keya,
						struct grub_hfsplus_key_internal *keyb),
			   struct grub_hfsplus_btnode **matchnode, 
			   grub_off_t *keyoffset);
grub_err_t
grub_mac_bless_inode (grub_device_t dev, grub_uint32_t inode, int is_dir,
		      int intel);
grub_err_t
grub_mac_bless_file (grub_device_t dev, const char *path_in, int intel);
