/*
 * libhfs - library for reading and writing Macintosh HFS volumes
 *
 * Code to acces the basic volume information of a HFS+ volume.
 *
 * Copyright (C) 2000 Klaus Halfmann <khalfmann@libra.de>
 * Original work by 1996-1998 Robert Leslie <rob@mars.org>
 * other work 2000 from Brad Boyer (flar@pants.nu)
 *
 * 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., 51 Franklin Street - Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 * $Id: volume.c,v 1.21 2000/10/25 05:43:04 hasi Exp $
 */

#include "config.h"
#include "libhfsp.h"
#include "volume.h"
#include "record.h"
#include "btree.h"
#include "blockiter.h"
#include "os.h"
#include "swab.h"
#include "hfstime.h"


/* Fill a given buffer with the given block in volume.
 */
int
volume_readinbuf(volume * vol,void* buf, long block)
{
	UInt16 blksize_bits;
	ASSERT( block < vol->maxblocks);

	blksize_bits = vol->blksize_bits;
	block	+= vol->startblock;
	if( os_seek(vol->os_fd, block, blksize_bits) == block)
		if( 1 == os_read(vol->os_fd, buf, 1, blksize_bits))
			return 0;
	return -1;
}

/* read multiple blocks into given memory.
 *
 * returns given pinter or NULL on failure.
 */
void*
volume_readfromfork(volume* vol, void* buf,
		hfsp_fork_raw* f, UInt32 block,
		UInt32 count, UInt8 forktype, UInt32 fileId)
{
	blockiter iter;
	char *cbuf = buf;

	blockiter_init(&iter, vol, f, forktype, fileId);
	if( blockiter_skip(&iter, block))
		return NULL;

	while( count > 0) {
		--count;
		if( volume_readinbuf(vol, cbuf, blockiter_curr(&iter)))
			return NULL;
		cbuf += vol->blksize;
		if( count > 0 && blockiter_next(&iter))
			return NULL;
	}
	return buf;
}


/* Read a raw hfsp_extent_rec from memory.
 *
 * return pointer right after the structure.
 */
void*
volume_readextent(void *p, hfsp_extent_rec er)
{
	int 		i;
	hfsp_extent	*e;

	for( i=0; i < 8; i++) {
		e = &er[i];
		e->start_block = bswabU32_inc(p);
		e->block_count = bswabU32_inc(p);
	}
	return p;
}

/* Read a raw hfsp_fork from memory.
 *
 * return pointer right after the structure.
 */
void*
volume_readfork(void *p, hfsp_fork_raw* f)
{
	f->total_size   = bswabU64_inc(p);
	f->clump_size   = bswabU32_inc(p);
	f->total_blocks = bswabU32_inc(p);

	return volume_readextent(p, f->extents);
}

/* Read the volume from the given buffer and swap the bytes.
 *
 * ToDo: add more consitency checks.
 */
static int
volume_readbuf(hfsp_vh* vh, char * p)
{
	if(  (vh->signature = bswabU16_inc(p)) != HFSP_VOLHEAD_SIG)
		HFSP_ERROR(-1, "This is not a HFS+ volume");

	vh->version		= bswabU16_inc(p);
	vh->attributes   	= bswabU32_inc(p);
	vh->last_mount_vers	= bswabU32_inc(p);
	vh->reserved		= bswabU32_inc(p);
	vh->create_date		= bswabU32_inc(p);
	vh->modify_date		= bswabU32_inc(p);
	vh->backup_date		= bswabU32_inc(p);
	vh->checked_date	= bswabU32_inc(p);
	vh->file_count		= bswabU32_inc(p);
	vh->folder_count	= bswabU32_inc(p);
	vh->blocksize		= bswabU32_inc(p);
	vh->total_blocks	= bswabU32_inc(p);
	vh->free_blocks		= bswabU32_inc(p);
	vh->next_alloc		= bswabU32_inc(p);
	vh->rsrc_clump_sz	= bswabU32_inc(p);
	vh->data_clump_sz	= bswabU32_inc(p);
	vh->next_cnid		= bswabU32_inc(p);
	vh->write_count		= bswabU32_inc(p);
	vh->encodings_bmp	= bswabU64_inc(p);
	memcpy(vh->finder_info, p, 32);
	p += 32; // So finderinfo must be swapped later, ***
	p = volume_readfork(p, &vh->alloc_file );
	p = volume_readfork(p, &vh->ext_file   );
	p = volume_readfork(p, &vh->cat_file   );
	p = volume_readfork(p, &vh->attr_file  );
        volume_readfork(p, &vh->start_file );
	return 0;
  fail:
	return -1;
}

/* Read the volume from the given block */
static int
volume_read(volume * vol, hfsp_vh* vh, UInt32 block)
{
	char buf[vol->blksize];

	if( volume_readinbuf(vol, buf, block))
		return -1;
        return volume_readbuf(vh, buf);
}

/* Find out wether the volume is wrapped and unwrap it eventually */
static int
volume_read_wrapper(volume * vol, hfsp_vh* vh)
{
	UInt16  signature;
	char	buf[vol->blksize];
        char    *p = buf;

	if( volume_readinbuf(vol, buf, 2) ) // Wrapper or volume header starts here
		return -1;

	signature = bswabU16_inc(p);
	if( signature == HFS_VOLHEAD_SIG) {		/* Wrapper */
		UInt32  drAlBlkSiz;			/* size (in bytes) of allocation blocks */
		UInt32	sect_per_block;			/* how may block build an hfs sector */
		UInt16  drAlBlSt;			/* first allocation block in volume */
		UInt16	embeds, embedl;			/* Start/lenght of embedded area in blocks */

		p += 0x12;			/* skip unneded HFS vol fields */
		drAlBlkSiz = bswabU32_inc(p);		/* offset 0x14 */
		p += 0x4;			/* skip unneded HFS vol fields */
		drAlBlSt = bswabU16_inc(p);		/* offset 0x1C */

		p += 0x5E;			/* skip unneded HFS vol fields */
		signature = bswabU16_inc(p);		/* offset 0x7C, drEmbedSigWord */
		if( signature != HFSP_VOLHEAD_SIG)
			HFSP_ERROR(-1, "This looks like a normal HFS volume");
		embeds = bswabU16_inc(p);
		embedl = bswabU16_inc(p);
		sect_per_block =  (drAlBlkSiz / HFSP_BLOCKSZ);
		// end is absolute (not relative to HFS+ start)
		vol->maxblocks = embedl * sect_per_block;
		vol->startblock = drAlBlSt + embeds * sect_per_block;
		/* Now we can try to read the embedded HFS+ volume header */
		return volume_read(vol,vh,2);
	}
	else if( signature == HFSP_VOLHEAD_SIG) { /* Native HFS+ volume */
		p = buf; // Restore to begin of block
                return volume_readbuf(vh, p);
	} else
		 HFSP_ERROR(-1, "Neither Wrapper nor native HFS+ volume header found");
fail:
	return -1;
}


/* Open the device, read and verify the volume header
   (and its backup) */
int
volume_open( volume* vol, int os_fd )
{
	hfsp_vh backup;	/* backup volume found at second to last block */
	long	sect_per_block;
	int	shift;

	vol->blksize_bits	= HFSP_BLOCKSZ_BITS;
	vol->blksize		= HFSP_BLOCKSZ;
	vol->startblock		= 0;
	vol->maxblocks		= 3;
		/* this should be enough until we find the volume descriptor */
	vol->extents		= NULL; /* Thanks to Jeremias Sauceda */

	btree_reset(&vol->catalog);
	vol->os_fd = os_fd;

	// vol->maxblocks = os_seek(vol->os_fd, -1, HFSP_BLOCKSZ_BITS);
	// This wont work for /dev/... but we do not really need it

	if( volume_read_wrapper(vol, &vol->vol))
		return -1;
	if( volume_read(vol, &backup, vol->maxblocks - 2))
		return -1;

	/* Now switch blksize from HFSP_BLOCKSZ (512) to value given in header
	   and adjust depend values accordingly, after that a block always
	   means a HFS+ allocation size */

	/* Usually 4096 / 512  == 8 */
	sect_per_block = vol->vol.blocksize / HFSP_BLOCKSZ;
	shift = 0;
	if( sect_per_block > 1) {
		shift = 1;
		while( sect_per_block > 2) {
			sect_per_block >>=1;
			shift++;
		}		/* shift = 3 */
	}
	vol -> blksize_bits += shift;
	vol -> blksize = 1 << vol->blksize_bits;
	vol -> startblock >>= shift;
	vol -> maxblocks = vol->vol.total_blocks;	/* cant calculate via shift ? */

	if( btree_init_cat(&vol->catalog, vol, &vol->vol.cat_file))
		return -1;

	return 0;
}

/* Write back all data eventually cached and close the device */
int
volume_close(volume* vol)
{
	btree_close(&vol->catalog);
	if( vol->extents) {
		btree_close(vol->extents);
		FREE(vol->extents);
	}
	return 0;
}

/* internal fucntion used to create the extents btree,
   is called by inline function when needed */
void
volume_create_extents_tree(volume* vol)
{
	btree* result = (btree*) ALLOC(btree*, sizeof(btree));
	if( !result)
		HFSP_ERROR(ENOMEM, "No memory for extents btree");
	if( !btree_init_extent(result, vol, &vol->vol.ext_file)) {
		vol->extents = result;
		return;
	}
  fail:
	vol->extents = NULL;
}

/* Determine whether the volume is a HFS-plus volume */
int
volume_probe(int fd, long long offset)
{
	UInt16 *vol;
	int ret = 0;

	vol = (UInt16 *)malloc(2 * 1 << HFSP_BLOCKSZ_BITS);
	os_seek_offset( fd, 2 * (1 << HFSP_BLOCKSZ_BITS) + offset );
	os_read(fd, vol, 2, HFSP_BLOCKSZ_BITS);

	if (__be16_to_cpu(vol[0]) == HFS_VOLHEAD_SIG &&
		__be16_to_cpu(vol[0x7c]) == HFSP_VOLHEAD_SIG) {
		ret = -1;
	} else if (__be16_to_cpu(vol[0]) == HFSP_VOLHEAD_SIG) {
		ret = -1;
	}

	free(vol);
	return ret;
}

