/*
 * hfsutils - tools for reading and writing Macintosh HFS volumes
 * Copyright (C) 1996, 1997 Robert Leslie
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <mconfig.h>
#include <stdxlib.h>
#include <strdefs.h>
#include <errno.h>

#include "internal.h"
#include "data.h"
#include "block.h"
#include "file.h"
#include "btree.h"
#include "node.h"

/*
 * NAME:	btree->getnode()
 * DESCRIPTION:	retrieve a numbered node from a B*-tree file
 */
int bt_getnode(np)
	node	*np;
{
  btree *bt = np->bt;
  block *bp = &np->data;
  unsigned char *ptr;
  int i;

  /* verify the node exists and is marked as in-use */

  if (np->nnum < 0 || (np->nnum > 0 && np->nnum >= bt->hdr.bthNNodes))
    {
      ERROR(EIO, "read nonexistent b*-tree node");
      return -1;
    }

  if (bt->map && ! BMTST(bt->map, np->nnum))
    {
      ERROR(EIO, "read unallocated b*-tree node");
      return -1;
    }

  if (f_getblock(&bt->f, np->nnum, bp) < 0)
    return -1;

  ptr = *bp;

  d_fetchl(&ptr, (long *) &np->nd.ndFLink);
  d_fetchl(&ptr, (long *) &np->nd.ndBLink);
  d_fetchb(&ptr, (char *) &np->nd.ndType);
  d_fetchb(&ptr, (char *) &np->nd.ndNHeight);
  d_fetchw(&ptr, (short *) &np->nd.ndNRecs);
  d_fetchw(&ptr, &np->nd.ndResv2);

  if (np->nd.ndNRecs > HFS_MAXRECS)
    {
      ERROR(EIO, "too many b*-tree node records");
      return -1;
    }

  i = np->nd.ndNRecs + 1;

  ptr = *bp + HFS_BLOCKSZ - (2 * i);

  while (i--)
    d_fetchw(&ptr, (short *) &np->roff[i]);

  return 0;
}

/*
 * NAME:	btree->putnode()
 * DESCRIPTION:	store a numbered node into a B*-tree file
 */
int bt_putnode(np)
	node	*np;
{
  btree *bt = np->bt;
  block *bp = &np->data;
  unsigned char *ptr;
  int i;

  /* verify the node exists and is marked as in-use */

  if (np->nnum && np->nnum >= bt->hdr.bthNNodes)
    {
      ERROR(EIO, "write nonexistent b*-tree node");
      return -1;
    }
  else if (bt->map && ! BMTST(bt->map, np->nnum))
    {
      ERROR(EIO, "write unallocated b*-tree node");
      return -1;
    }

  ptr = *bp;

  d_storel(&ptr, np->nd.ndFLink);
  d_storel(&ptr, np->nd.ndBLink);
  d_storeb(&ptr, np->nd.ndType);
  d_storeb(&ptr, np->nd.ndNHeight);
  d_storew(&ptr, np->nd.ndNRecs);
  d_storew(&ptr, np->nd.ndResv2);

  if (np->nd.ndNRecs > HFS_MAXRECS)
    {
      ERROR(EIO, "too many b*-tree node records");
      return -1;
    }

  i = np->nd.ndNRecs + 1;

  ptr = *bp + HFS_BLOCKSZ - (2 * i);

  while (i--)
    d_storew(&ptr, np->roff[i]);

  if (f_putblock(&bt->f, np->nnum, bp) < 0)
    return -1;

  return 0;
}

/*
 * NAME:	btree->readhdr()
 * DESCRIPTION:	read the header node of a B*-tree
 */
int bt_readhdr(bt)
	btree	*bt;
{
  unsigned char *ptr;
  char *map;
  int i;
  unsigned long nnum;

  bt->hdrnd.bt   = bt;
  bt->hdrnd.nnum = 0;

  if (bt_getnode(&bt->hdrnd) < 0)
    return -1;

  if (bt->hdrnd.nd.ndType != ndHdrNode ||
      bt->hdrnd.nd.ndNRecs != 3 ||
      bt->hdrnd.roff[0] != 0x00e ||
      bt->hdrnd.roff[1] != 0x078 ||
      bt->hdrnd.roff[2] != 0x0f8 ||
      bt->hdrnd.roff[3] != 0x1f8)
    {
      ERROR(EIO, "malformed b*-tree header node");
      return -1;
    }

  /* read header record */

  ptr = HFS_NODEREC(bt->hdrnd, 0);

  d_fetchw(&ptr, (short *) &bt->hdr.bthDepth);
  d_fetchl(&ptr, (long *) &bt->hdr.bthRoot);
  d_fetchl(&ptr, (long *) &bt->hdr.bthNRecs);
  d_fetchl(&ptr, (long *) &bt->hdr.bthFNode);
  d_fetchl(&ptr, (long *) &bt->hdr.bthLNode);
  d_fetchw(&ptr, (short *) &bt->hdr.bthNodeSize);
  d_fetchw(&ptr, (short *) &bt->hdr.bthKeyLen);
  d_fetchl(&ptr, (long *) &bt->hdr.bthNNodes);
  d_fetchl(&ptr, (long *) &bt->hdr.bthFree);

  for (i = 0; i < 76; ++i)
    d_fetchb(&ptr, (char *) &bt->hdr.bthResv[i]);

  if (bt->hdr.bthNodeSize != HFS_BLOCKSZ)
    {
      ERROR(EINVAL, "unsupported b*-tree node size");
      return -1;
    }

  /* read map record; construct btree bitmap */
  /* don't set bt->map until we're done, since getnode() checks it */

  map = ALLOC(char, HFS_MAP1SZ);
  if (map == 0)
    {
      ERROR(ENOMEM, 0);
      return -1;
    }

  memcpy(map, HFS_NODEREC(bt->hdrnd, 2), HFS_MAP1SZ);
  bt->mapsz = HFS_MAP1SZ;

  /* read continuation map records, if any */

  nnum = bt->hdrnd.nd.ndFLink;

  while (nnum)
    {
      node n;
      char *newmap;

      n.bt   = bt;
      n.nnum = nnum;

      if (bt_getnode(&n) < 0)
	{
	  FREE(map);
	  return -1;
	}

      if (n.nd.ndType != ndMapNode ||
	  n.nd.ndNRecs != 1 ||
	  n.roff[0] != 0x00e ||
	  n.roff[1] != 0x1fa)
	{
	  FREE(map);
	  ERROR(EIO, "malformed b*-tree map node");
	  return -1;
	}

      newmap = REALLOC(map, char, bt->mapsz + HFS_MAPXSZ);
      if (newmap == 0)
	{
	  FREE(map);
	  ERROR(ENOMEM, 0);
	  return -1;
	}
      map = newmap;

      memcpy(map + bt->mapsz, HFS_NODEREC(n, 0), HFS_MAPXSZ);
      bt->mapsz += HFS_MAPXSZ;

      nnum = n.nd.ndFLink;
    }

  bt->map = map;

  return 0;
}

/*
 * NAME:	btree->writehdr()
 * DESCRIPTION:	write the header node of a B*-tree
 */
int bt_writehdr(bt)
	btree	*bt;
{
  unsigned char *ptr;
  char *map;
  unsigned long mapsz, nnum;
  int i;

  if (bt->hdrnd.bt != bt ||
      bt->hdrnd.nnum != 0 ||
      bt->hdrnd.nd.ndType != ndHdrNode ||
      bt->hdrnd.nd.ndNRecs != 3)
    abort();

  ptr = HFS_NODEREC(bt->hdrnd, 0);

  d_storew(&ptr, bt->hdr.bthDepth);
  d_storel(&ptr, bt->hdr.bthRoot);
  d_storel(&ptr, bt->hdr.bthNRecs);
  d_storel(&ptr, bt->hdr.bthFNode);
  d_storel(&ptr, bt->hdr.bthLNode);
  d_storew(&ptr, bt->hdr.bthNodeSize);
  d_storew(&ptr, bt->hdr.bthKeyLen);
  d_storel(&ptr, bt->hdr.bthNNodes);
  d_storel(&ptr, bt->hdr.bthFree);

  for (i = 0; i < 76; ++i)
    d_storeb(&ptr, bt->hdr.bthResv[i]);

  memcpy(HFS_NODEREC(bt->hdrnd, 2), bt->map, HFS_MAP1SZ);

  if (bt_putnode(&bt->hdrnd) < 0)
    return -1;

  map   = bt->map   + HFS_MAP1SZ;
  mapsz = bt->mapsz - HFS_MAP1SZ;

  nnum  = bt->hdrnd.nd.ndFLink;

  while (mapsz)
    {
      node n;

      if (nnum == 0)
	{
	  ERROR(EIO, "truncated b*-tree map");
	  return -1;
	}

      n.bt   = bt;
      n.nnum = nnum;

      if (bt_getnode(&n) < 0)
	return -1;

      if (n.nd.ndType != ndMapNode ||
	  n.nd.ndNRecs != 1 ||
	  n.roff[0] != 0x00e ||
	  n.roff[1] != 0x1fa)
	{
	  ERROR(EIO, "malformed b*-tree map node");
	  return -1;
	}

      memcpy(HFS_NODEREC(n, 0), map, HFS_MAPXSZ);

      if (bt_putnode(&n) < 0)
	return -1;

      map   += HFS_MAPXSZ;
      mapsz -= HFS_MAPXSZ;

      nnum = n.nd.ndFLink;
    }

  bt->flags &= ~HFS_UPDATE_BTHDR;

  return 0;
}

/* High-Level B*-Tree Routines ============================================= */

/*
 * NAME:	btree->space()
 * DESCRIPTION:	assert space for new records, or extend the file
 */
int bt_space(bt, nrecs)
	btree		*bt;
	unsigned int	nrecs;
{
  unsigned int nnodes;
  int space;

  nnodes = nrecs * (bt->hdr.bthDepth + 1);

  if (nnodes <= bt->hdr.bthFree)
    return 0;

  /* make sure the extents tree has room too */

  if (bt != &bt->f.vol->ext)
    {
      if (bt_space(&bt->f.vol->ext, 1) < 0)
	return -1;
    }

  space = f_alloc(&bt->f);
  if (space < 0)
    return -1;

  nnodes = space * (bt->f.vol->mdb.drAlBlkSiz / bt->hdr.bthNodeSize);

  bt->hdr.bthNNodes += nnodes;
  bt->hdr.bthFree   += nnodes;

  bt->flags |= HFS_UPDATE_BTHDR;

  bt->f.vol->flags |= HFS_UPDATE_ALTMDB;

  while (bt->hdr.bthNNodes > bt->mapsz * 8)
    {
      char *newmap;
      node mapnd;

      /* extend tree map */

      newmap = REALLOC(bt->map, char, bt->mapsz + HFS_MAPXSZ);
      if (newmap == 0)
	{
	  ERROR(ENOMEM, 0);
	  return -1;
	}

      memset(newmap + bt->mapsz, 0, HFS_MAPXSZ);

      bt->map    = newmap;
      bt->mapsz += HFS_MAPXSZ;

      n_init(&mapnd, bt, ndMapNode, 0);
      if (n_new(&mapnd) < 0)
	return -1;

      /* link the new map node */

      if (bt->hdrnd.nd.ndFLink == 0)
	{
	  bt->hdrnd.nd.ndFLink = mapnd.nnum;
	  mapnd.nd.ndBLink     = 0;
	}
      else
	{
	  node n;

	  n.bt   = bt;
	  n.nnum = bt->hdrnd.nd.ndFLink;

	  while (1)
	    {
	      if (bt_getnode(&n) < 0)
		return -1;

	      if (n.nd.ndFLink == 0)
		break;

	      n.nnum = n.nd.ndFLink;
	    }

	  n.nd.ndFLink     = mapnd.nnum;
	  mapnd.nd.ndBLink = n.nnum;

	  if (bt_putnode(&n) < 0)
	    return -1;
	}

      mapnd.nd.ndNRecs = 1;
      mapnd.roff[1]    = 0x1fa;

      if (bt_putnode(&mapnd) < 0)
	return -1;
    }

  return 0;
}

/*
 * NAME:	btree->insertx()
 * DESCRIPTION:	recursively locate a node and insert a record
 */
int bt_insertx(np, record, reclen)
	node		*np;
	unsigned char	*record;
	int		*reclen;
{
  node child;
  unsigned char *rec;

  if (n_search(np, record))
    {
      ERROR(EIO, "b*-tree record already exists");
      return -1;
    }

  switch ((unsigned char) np->nd.ndType)
    {
    case ndIndxNode:
      if (np->rnum < 0)
	rec = HFS_NODEREC(*np, 0);
      else
	rec = HFS_NODEREC(*np, np->rnum);

      child.bt   = np->bt;
      child.nnum = d_getl(HFS_RECDATA(rec));

      if (bt_getnode(&child) < 0 ||
	  bt_insertx(&child, record, reclen) < 0)
	return -1;

      if (np->rnum < 0)
	{
	  n_index(np->bt, HFS_NODEREC(child, 0), child.nnum, rec, 0);
	  if (*reclen == 0)
	    return bt_putnode(np);
	}

      return *reclen ? n_insert(np, record, reclen) : 0;

    case ndLeafNode:
      return n_insert(np, record, reclen);

    default:
      ERROR(EIO, "unexpected b*-tree node");
      return -1;
    }
}

/*
 * NAME:	btree->insert()
 * DESCRIPTION:	insert a new node record into a tree
 */
int bt_insert(bt, record, reclen)
	btree		*bt;
	unsigned char	*record;
	int		reclen;
{
  node root;

  if (bt->hdr.bthRoot == 0)
    {
      /* create root node */

      n_init(&root, bt, ndLeafNode, 1);
      if (n_new(&root) < 0 ||
	  bt_putnode(&root) < 0)
	return -1;

      bt->hdr.bthDepth = 1;
      bt->hdr.bthRoot  = root.nnum;
      bt->hdr.bthFNode = root.nnum;
      bt->hdr.bthLNode = root.nnum;

      bt->flags |= HFS_UPDATE_BTHDR;
    }
  else
    {
      root.bt   = bt;
      root.nnum = bt->hdr.bthRoot;

      if (bt_getnode(&root) < 0)
	return -1;
    }

  if (bt_insertx(&root, record, &reclen) < 0)
    return -1;

  if (reclen)
    {
      unsigned char oroot[HFS_MAXRECLEN];
      int orootlen;

      /* root node was split; create a new root */

      n_index(bt, HFS_NODEREC(root, 0), root.nnum, oroot, &orootlen);

      n_init(&root, bt, ndIndxNode, root.nd.ndNHeight + 1);
      if (n_new(&root) < 0)
	return -1;

      ++bt->hdr.bthDepth;
      bt->hdr.bthRoot = root.nnum;

      bt->flags |= HFS_UPDATE_BTHDR;

      /* insert index records for new root */

      n_search(&root, oroot);
      n_insertx(&root, oroot, orootlen);

      n_search(&root, record);
      n_insertx(&root, record, reclen);

      if (bt_putnode(&root) < 0)
	return -1;
    }

  ++bt->hdr.bthNRecs;
  bt->flags |= HFS_UPDATE_BTHDR;

  return 0;
}

/*
 * NAME:	btree->deletex()
 * DESCRIPTION:	recursively locate a node and delete a record
 */
int bt_deletex(np, key, record, flag)
	node		*np;
	unsigned char	*key;
	unsigned char	*record;
	int		*flag;
{
  node child;
  unsigned char *rec;
  int found;

  found = n_search(np, key);

  switch ((unsigned char) np->nd.ndType)
    {
    case ndIndxNode:
      if (np->rnum < 0)
	{
	  ERROR(EIO, "b*-tree record not found");
	  return -1;
	}

      rec = HFS_NODEREC(*np, np->rnum);

      child.bt   = np->bt;
      child.nnum = d_getl(HFS_RECDATA(rec));

      if (bt_getnode(&child) < 0 ||
	  bt_deletex(&child, key, rec, flag) < 0)
	return -1;

      if (*flag)
	{
	  *flag = 0;

	  if (HFS_RECKEYLEN(rec) == 0)
	    return n_delete(np, record, flag);

	  if (np->rnum == 0)
	    {
	      n_index(np->bt, HFS_NODEREC(*np, 0), np->nnum, record, 0);
	      *flag = 1;
	    }

	  return bt_putnode(np);
	}

      return 0;

    case ndLeafNode:
      if (found == 0)
	{
	  ERROR(EIO, "b*-tree record not found");
	  return -1;
	}

      return n_delete(np, record, flag);

    default:
      ERROR(EIO, "unexpected b*-tree node");
      return -1;
    }
}

/*
 * NAME:	btree->delete()
 * DESCRIPTION:	remove a node record from a tree
 */
int bt_delete(bt, key)
	btree		*bt;
	unsigned char	*key;
{
  node root;
  unsigned char record[HFS_MAXRECLEN];
  int flag = 0;

  root.bt   = bt;
  root.nnum = bt->hdr.bthRoot;

  if (root.nnum == 0)
    {
      ERROR(EIO, "empty b*-tree");
      return -1;
    }

  if (bt_getnode(&root) < 0 ||
      bt_deletex(&root, key, record, &flag) < 0)
    return -1;

  if (bt->hdr.bthDepth > 1 && root.nd.ndNRecs == 1)
    {
      unsigned char *rec;

      /* chop the root */

      rec = HFS_NODEREC(root, 0);

      --bt->hdr.bthDepth;
      bt->hdr.bthRoot = d_getl(HFS_RECDATA(rec));

      n_free(&root);
    }
  else if (bt->hdr.bthDepth == 1 && root.nd.ndNRecs == 0)
    {
      /* delete the root node */

      bt->hdr.bthDepth = 0;
      bt->hdr.bthRoot  = 0;
      bt->hdr.bthFNode = 0;
      bt->hdr.bthLNode = 0;

      n_free(&root);
    }

  --bt->hdr.bthNRecs;
  bt->flags |= HFS_UPDATE_BTHDR;

  return 0;
}

/*
 * NAME:	btree->search()
 * DESCRIPTION:	locate a data record given a search key
 */
int bt_search(bt, key, np)
	btree		*bt;
	unsigned char	*key;
	node		*np;
{
  np->bt   = bt;
  np->nnum = bt->hdr.bthRoot;

  if (np->nnum == 0)
    {
      ERROR(ENOENT, 0);
      return 0;
    }

  while (1)
    {
      int found;
      unsigned char *rec;

      if (bt_getnode(np) < 0)
	return -1;

      found = n_search(np, key);

      switch ((unsigned char) np->nd.ndType)
	{
	case ndIndxNode:
	  if (np->rnum < 0)
	    {
	      ERROR(ENOENT, 0);
	      return 0;
	    }

	  rec = HFS_NODEREC(*np, np->rnum);
	  np->nnum = d_getl(HFS_RECDATA(rec));
	  break;

	case ndLeafNode:
	  if (! found)
	    ERROR(ENOENT, 0);

	  return found;

	default:
	  ERROR(EIO, "unexpected b*-tree node");
	  return -1;
	}
    }
}
