/*
    libparted - a library for manipulating disk partitions
    Copyright (C) 2000, 2003-2005, 2007, 2009-2014, 2019-2021 Free Software
    Foundation, Inc.

    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 3 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, see <http://www.gnu.org/licenses/>.
*/

/*
   Author : Guillaume Knispel <k_guillaume@libertysurf.fr>
   Report bug to <bug-parted@gnu.org>
*/

#include <config.h>

#include <parted/parted.h>
#include <parted/endian.h>
#include <parted/debug.h>
#include <stdint.h>

#if ENABLE_NLS
#  include <libintl.h>
#  define _(String) dgettext (PACKAGE, String)
#else
#  define _(String) (String)
#endif /* ENABLE_NLS */

#include "hfs.h"
#include "probe.h"

uint8_t* hfs_block = NULL;
uint8_t* hfsp_block = NULL;
unsigned hfs_block_count;
unsigned hfsp_block_count;

#define HFS_BLOCK_SIZES       ((int[2]){512, 0})
#define HFSP_BLOCK_SIZES       ((int[2]){512, 0})
#define HFSX_BLOCK_SIZES       ((int[2]){512, 0})

#ifndef DISCOVER_ONLY
#include "file.h"
#include "reloc.h"
#include "advfs.h"

static PedFileSystemType hfs_type;
static PedFileSystemType hfsplus_type;


/* ----- HFS ----- */

PedFileSystem *
hfs_open (PedGeometry* geom)
{
	uint8_t			buf[PED_SECTOR_SIZE_DEFAULT];
	PedFileSystem*		fs;
	HfsMasterDirectoryBlock* mdb;
	HfsPrivateFSData* 	priv_data;

	if (!hfsc_can_use_geom (geom))
		return NULL;

	/* Read MDB */
	if (!ped_geometry_read (geom, buf, 2, 1))
		return NULL;

	/* Allocate memory */
	fs = (PedFileSystem*) ped_malloc (sizeof (PedFileSystem));
	if (!fs) goto ho;
	mdb = (HfsMasterDirectoryBlock*)
		ped_malloc (sizeof (HfsMasterDirectoryBlock));
	if (!mdb) goto ho_fs;
	priv_data = (HfsPrivateFSData*)
		ped_malloc (sizeof (HfsPrivateFSData));
	if (!priv_data) goto ho_mdb;

	memcpy (mdb, buf, sizeof (HfsMasterDirectoryBlock));

	/* init structures */
	priv_data->mdb = mdb;
	priv_data->bad_blocks_loaded = 0;
	priv_data->bad_blocks_xtent_nb = 0;
	priv_data->bad_blocks_xtent_list = NULL;
	priv_data->extent_file =
	    hfs_file_open (fs, PED_CPU_TO_BE32 (HFS_XTENT_ID),
			   mdb->extents_file_rec,
			   PED_CPU_TO_BE32 (mdb->extents_file_size)
			   / PED_SECTOR_SIZE_DEFAULT);
	if (!priv_data->extent_file) goto ho_pd;
	priv_data->catalog_file =
	    hfs_file_open (fs, PED_CPU_TO_BE32 (HFS_CATALOG_ID),
			   mdb->catalog_file_rec,
			   PED_CPU_TO_BE32 (mdb->catalog_file_size)
			   / PED_SECTOR_SIZE_DEFAULT);
	if (!priv_data->catalog_file) goto ho_ce;
	/* Read allocation blocks */
	if (!ped_geometry_read(geom, priv_data->alloc_map,
			       PED_BE16_TO_CPU (mdb->volume_bitmap_block),
			       ( PED_BE16_TO_CPU (mdb->total_blocks)
			         + PED_SECTOR_SIZE_DEFAULT * 8 - 1 )
			       / (PED_SECTOR_SIZE_DEFAULT * 8) ) )
		goto ho_cf;

	fs->type = &hfs_type;
	fs->geom = ped_geometry_duplicate (geom);
	if (!fs->geom) goto ho_cf;
	fs->type_specific = (void*) priv_data;
	fs->checked = ( PED_BE16_TO_CPU (mdb->volume_attributes)
			>> HFS_UNMOUNTED ) & 1;

	return fs;

/*--- clean error handling ---*/
ho_cf:	hfs_file_close(priv_data->catalog_file);
ho_ce:	hfs_file_close(priv_data->extent_file);
ho_pd:	free(priv_data);
ho_mdb: free(mdb);
ho_fs:	free(fs);
ho:	return NULL;
}

int
hfs_close (PedFileSystem *fs)
{
	HfsPrivateFSData* priv_data = (HfsPrivateFSData*) fs->type_specific;

	hfs_file_close (priv_data->extent_file);
	hfs_file_close (priv_data->catalog_file);
	if (priv_data->bad_blocks_loaded)
		hfs_free_bad_blocks_list (priv_data->bad_blocks_xtent_list);
	free (priv_data->mdb);
	free (priv_data);
	ped_geometry_destroy (fs->geom);
	free (fs);

	return 1;
}

PedConstraint *
hfs_get_resize_constraint (const PedFileSystem *fs)
{
	PedDevice*	dev = fs->geom->dev;
	PedAlignment	start_align;
	PedGeometry	start_sector;
	PedGeometry	full_dev;
	PedSector	min_size;

	if (!ped_alignment_init (&start_align, fs->geom->start, 0))
		return NULL;
	if (!ped_geometry_init (&start_sector, dev, fs->geom->start, 1))
		return NULL;
	if (!ped_geometry_init (&full_dev, dev, 0, dev->length - 1))
		return NULL;
	/* 2 = last two sectors (alternate MDB and unused sector) */
	min_size = hfs_get_empty_end(fs) + 2;
	if (min_size == 2) return NULL;

	return ped_constraint_new (&start_align, ped_alignment_any,
				   &start_sector, &full_dev, min_size,
				   fs->geom->length);
}

int
hfs_resize (PedFileSystem* fs, PedGeometry* geom, PedTimer* timer)
{
	uint8_t			buf[PED_SECTOR_SIZE_DEFAULT];
	unsigned int		nblock, nfree;
	unsigned int		block, to_free;
	HfsPrivateFSData* 	priv_data;
	HfsMasterDirectoryBlock* mdb;
	int			resize = 1;
	unsigned int		hfs_sect_block;
	PedSector		hgee;

	/* check preconditions */
	PED_ASSERT (fs != NULL);
	PED_ASSERT (fs->geom != NULL);
	PED_ASSERT (geom != NULL);
#ifdef DEBUG
        PED_ASSERT ((hgee = hfs_get_empty_end(fs)) != 0);
#else
        if ((hgee = hfs_get_empty_end(fs)) == 0)
                return 0;
#endif

	PED_ASSERT ((hgee = hfs_get_empty_end(fs)) != 0);

	if (ped_geometry_test_equal(fs->geom, geom))
		return 1;

	priv_data = (HfsPrivateFSData*) fs->type_specific;
	mdb = priv_data->mdb;
	hfs_sect_block = PED_BE32_TO_CPU (mdb->block_size)
			 / PED_SECTOR_SIZE_DEFAULT;

	if (fs->geom->start != geom->start
	    || geom->length > fs->geom->length
	    || geom->length < hgee + 2) {
		ped_exception_throw (
			PED_EXCEPTION_NO_FEATURE,
			PED_EXCEPTION_CANCEL,
			_("Sorry, HFS cannot be resized that way yet."));
		return 0;
	}

	/* Flush caches */
	if (!ped_geometry_sync(fs->geom))
		return 0;

	/* Clear the unmounted bit */
	mdb->volume_attributes &= PED_CPU_TO_BE16 (~( 1 << HFS_UNMOUNTED ));
	if (!ped_geometry_read (fs->geom, buf, 2, 1))
		return 0;
	memcpy (buf, mdb, sizeof (HfsMasterDirectoryBlock));
	if (   !ped_geometry_write (fs->geom, buf, 2, 1)
	    || !ped_geometry_sync  (fs->geom))
		return 0;

	ped_timer_reset (timer);
	ped_timer_set_state_name(timer, _("shrinking"));
	ped_timer_update(timer, 0.0);
	/* relocate data */
	to_free = ( fs->geom->length - geom->length
		    + hfs_sect_block - 1 )
		  / hfs_sect_block ;
	block = hfs_find_start_pack (fs, to_free);
	if (!hfs_pack_free_space_from_block (fs, block,	timer, to_free)) {
		resize = 0;
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("Data relocation has failed."));
		goto write_MDB;
	}

	/* Calculate new block number and other MDB field */
	nblock = ( geom->length - (PED_BE16_TO_CPU (mdb->start_block) + 2) )
		 / hfs_sect_block;
	nfree = PED_BE16_TO_CPU (mdb->free_blocks)
		- ( PED_BE16_TO_CPU (mdb->total_blocks) - nblock );

	/* Check that all block after future end are really free */
	for (block = nblock;
	     block < PED_BE16_TO_CPU (mdb->total_blocks);
	     block++) {
		if (TST_BLOC_OCCUPATION(priv_data->alloc_map,block)) {
			resize = 0;
			ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_CANCEL,
				_("Data relocation left some data in the end "
				  "of the volume."));
			goto write_MDB;
		}
	}

	/* Mark out of volume blocks as used
	(broken implementations compatibility) */
	for ( block = nblock; block < (1 << 16); ++block)
		SET_BLOC_OCCUPATION(priv_data->alloc_map,block);

	/* save the allocation map
	I do not write until start of allocation blocks
	but only until pre-resize end of bitmap blocks
	because the specifications do _not_ assert that everything
	until allocation blocks is boot, mdb and alloc */
	ped_geometry_write(fs->geom, priv_data->alloc_map,
		PED_BE16_TO_CPU (priv_data->mdb->volume_bitmap_block),
		( PED_BE16_TO_CPU (priv_data->mdb->total_blocks)
		  + PED_SECTOR_SIZE_DEFAULT * 8 - 1)
		/ (PED_SECTOR_SIZE_DEFAULT * 8));

	/* Update geometry */
	if (resize) {
		/* update in fs structure */
		if (PED_BE16_TO_CPU (mdb->next_allocation) >= nblock)
			mdb->next_allocation = PED_CPU_TO_BE16 (0);
		mdb->total_blocks = PED_CPU_TO_BE16 (nblock);
		mdb->free_blocks = PED_CPU_TO_BE16 (nfree);
		/* update parted structure */
		fs->geom->length = geom->length;
		fs->geom->end = fs->geom->start + geom->length - 1;
	}

	/* Set the unmounted bit */
	mdb->volume_attributes |= PED_CPU_TO_BE16 ( 1 << HFS_UNMOUNTED );

	/* Effective write */
    write_MDB:
	ped_timer_set_state_name(timer,_("writing HFS Master Directory Block"));

	if (!hfs_update_mdb(fs)) {
		ped_geometry_sync(geom);
		return 0;
	}

	if (!ped_geometry_sync(geom))
		return 0;

	ped_timer_update(timer, 1.0);

	return (resize);
}

/* ----- HFS+ ----- */

#include "file_plus.h"
#include "advfs_plus.h"
#include "reloc_plus.h"
#include "journal.h"

int
hfsplus_close (PedFileSystem *fs)
{
	HfsPPrivateFSData* 	priv_data = (HfsPPrivateFSData*)
						fs->type_specific;

	if (priv_data->bad_blocks_loaded)
		hfsplus_free_bad_blocks_list(priv_data->bad_blocks_xtent_list);
	free(priv_data->alloc_map);
	free(priv_data->dirty_alloc_map);
	hfsplus_file_close (priv_data->allocation_file);
	hfsplus_file_close (priv_data->attributes_file);
	hfsplus_file_close (priv_data->catalog_file);
	hfsplus_file_close (priv_data->extents_file);
	if (priv_data->free_geom) ped_geometry_destroy (priv_data->plus_geom);
	if (priv_data->wrapper) hfs_close(priv_data->wrapper);
	ped_geometry_destroy (fs->geom);
	free(priv_data->vh);
	free(priv_data);
	free(fs);

	return 1;
}

PedFileSystem*
hfsplus_open (PedGeometry* geom)
{
	uint8_t			buf[PED_SECTOR_SIZE_DEFAULT];
	PedFileSystem*		fs;
	HfsPVolumeHeader*	vh;
	HfsPPrivateFSData* 	priv_data;
	PedGeometry*		wrapper_geom;
	unsigned int		map_sectors;

	if (!hfsc_can_use_geom (geom))
		return NULL;

	fs = (PedFileSystem*) ped_malloc (sizeof (PedFileSystem));
	if (!fs) goto hpo;
	vh = (HfsPVolumeHeader*) ped_malloc (sizeof (HfsPVolumeHeader));
	if (!vh) goto hpo_fs;
	priv_data = (HfsPPrivateFSData*)ped_malloc (sizeof (HfsPPrivateFSData));
	if (!priv_data) goto hpo_vh;

	fs->geom = ped_geometry_duplicate (geom);
	if (!fs->geom) goto hpo_pd;
	fs->type_specific = (void*) priv_data;

	if ((wrapper_geom = hfs_and_wrapper_probe (geom))) {
		HfsPrivateFSData* 	hfs_priv_data;
		PedSector		abs_sect, length;
		unsigned int		bs;

		ped_geometry_destroy (wrapper_geom);
		priv_data->wrapper = hfs_open(geom);
		if (!priv_data->wrapper) goto hpo_gm;
		hfs_priv_data = (HfsPrivateFSData*)
			priv_data->wrapper->type_specific;
		bs = PED_BE32_TO_CPU (hfs_priv_data->mdb->block_size)
		     / PED_SECTOR_SIZE_DEFAULT;
		abs_sect = (PedSector) geom->start
			   + (PedSector) PED_BE16_TO_CPU (
					    hfs_priv_data->mdb->start_block)
			   + (PedSector) PED_BE16_TO_CPU (
					    hfs_priv_data->mdb->old_new
					    .embedded.location.start_block )
			                 * bs;
		length = (PedSector) PED_BE16_TO_CPU (
					    hfs_priv_data->mdb->old_new
					    .embedded.location.block_count)
				     * bs;
		priv_data->plus_geom = ped_geometry_new (geom->dev, abs_sect,
							 length);
		if (!priv_data->plus_geom) goto hpo_wr;
		priv_data->free_geom = 1;
	} else {
		priv_data->wrapper = NULL;
		priv_data->plus_geom = fs->geom;
		priv_data->free_geom = 0;
	}

	if (!ped_geometry_read (priv_data->plus_geom, buf, 2, 1)) goto hpo_pg;
	memcpy (vh, buf, sizeof (HfsPVolumeHeader));
	priv_data->vh = vh;

	if (vh->signature != PED_CPU_TO_BE16(HFSP_SIGNATURE)
	    && vh->signature != PED_CPU_TO_BE16(HFSX_SIGNATURE)) {
		ped_exception_throw (
			PED_EXCEPTION_BUG,
			PED_EXCEPTION_CANCEL,
			_("No valid HFS[+X] signature has been found while "
			  "opening."));
		goto hpo_pg;
	}

	if (vh->signature == PED_CPU_TO_BE16(HFSP_SIGNATURE)
	    && vh->version != PED_CPU_TO_BE16(HFSP_VERSION)) {
		if (ped_exception_throw (
			PED_EXCEPTION_NO_FEATURE,
			PED_EXCEPTION_IGNORE_CANCEL,
			_("Version %d of HFS+ isn't supported."),
			PED_BE16_TO_CPU(vh->version))
				!= PED_EXCEPTION_IGNORE)
			goto hpo_pg;
	}

	if (vh->signature == PED_CPU_TO_BE16(HFSX_SIGNATURE)
	    && vh->version != PED_CPU_TO_BE16(HFSX_VERSION)) {
		if (ped_exception_throw (
			PED_EXCEPTION_NO_FEATURE,
			PED_EXCEPTION_IGNORE_CANCEL,
			_("Version %d of HFSX isn't supported."),
			PED_BE16_TO_CPU(vh->version))
				!= PED_EXCEPTION_IGNORE)
			goto hpo_pg;
	}

	priv_data->jib_start_block = 0;
	priv_data->jl_start_block = 0;
	if (vh->attributes & PED_CPU_TO_BE32(1<<HFSP_JOURNALED)) {
		if (!hfsj_replay_journal(fs))
			goto hpo_pg;
	}

	priv_data->bad_blocks_loaded = 0;
	priv_data->bad_blocks_xtent_nb = 0;
	priv_data->bad_blocks_xtent_list = NULL;
	priv_data->extents_file =
		hfsplus_file_open (fs, PED_CPU_TO_BE32 (HFS_XTENT_ID),
				   vh->extents_file.extents,
				   PED_BE64_TO_CPU (
					vh->extents_file.logical_size )
				   / PED_SECTOR_SIZE_DEFAULT);
	if (!priv_data->extents_file) goto hpo_pg;
	priv_data->catalog_file =
		hfsplus_file_open (fs, PED_CPU_TO_BE32 (HFS_CATALOG_ID),
				   vh->catalog_file.extents,
				   PED_BE64_TO_CPU (
					vh->catalog_file.logical_size )
				   / PED_SECTOR_SIZE_DEFAULT);
	if (!priv_data->catalog_file) goto hpo_ce;
	priv_data->attributes_file =
		hfsplus_file_open (fs, PED_CPU_TO_BE32 (HFSP_ATTRIB_ID),
				   vh->attributes_file.extents,
				   PED_BE64_TO_CPU (
					vh->attributes_file.logical_size)
				   / PED_SECTOR_SIZE_DEFAULT);
	if (!priv_data->attributes_file) goto hpo_cc;

	map_sectors = ( PED_BE32_TO_CPU (vh->total_blocks)
	                + PED_SECTOR_SIZE_DEFAULT * 8 - 1 )
		      / (PED_SECTOR_SIZE_DEFAULT * 8);
	priv_data->dirty_alloc_map = (uint8_t*)
		ped_malloc ((map_sectors + 7) / 8);
	if (!priv_data->dirty_alloc_map) goto hpo_cl;
	memset(priv_data->dirty_alloc_map, 0, (map_sectors + 7) / 8);
	priv_data->alloc_map = (uint8_t*)
		ped_malloc (map_sectors * PED_SECTOR_SIZE_DEFAULT);
	if (!priv_data->alloc_map) goto hpo_dm;

	priv_data->allocation_file =
		hfsplus_file_open (fs, PED_CPU_TO_BE32 (HFSP_ALLOC_ID),
				   vh->allocation_file.extents,
				   PED_BE64_TO_CPU (
					vh->allocation_file.logical_size)
				   / PED_SECTOR_SIZE_DEFAULT);
	if (!priv_data->allocation_file) goto hpo_am;
	if (!hfsplus_file_read (priv_data->allocation_file,
				priv_data->alloc_map, 0, map_sectors)) {
		hfsplus_close(fs);
		return NULL;
	}

	fs->type = &hfsplus_type;
	fs->checked = ((PED_BE32_TO_CPU (vh->attributes) >> HFS_UNMOUNTED) & 1)
	      && !((PED_BE32_TO_CPU (vh->attributes) >> HFSP_INCONSISTENT) & 1);

	return fs;

/*--- clean error handling ---*/
hpo_am: free(priv_data->alloc_map);
hpo_dm: free(priv_data->dirty_alloc_map);
hpo_cl: hfsplus_file_close (priv_data->attributes_file);
hpo_cc:	hfsplus_file_close (priv_data->catalog_file);
hpo_ce:	hfsplus_file_close (priv_data->extents_file);
hpo_pg: if (priv_data->free_geom) ped_geometry_destroy (priv_data->plus_geom);
hpo_wr: if (priv_data->wrapper) hfs_close(priv_data->wrapper);
hpo_gm: ped_geometry_destroy (fs->geom);
hpo_pd: free(priv_data);
hpo_vh: free(vh);
hpo_fs: free(fs);
hpo:	return NULL;
}

PedConstraint *
hfsplus_get_resize_constraint (const PedFileSystem *fs)
{
	PedDevice*	dev = fs->geom->dev;
	PedAlignment	start_align;
	PedGeometry	start_sector;
	PedGeometry	full_dev;
	PedSector	min_size;

	if (!ped_alignment_init (&start_align, fs->geom->start, 0))
		return NULL;
	if (!ped_geometry_init (&start_sector, dev, fs->geom->start, 1))
		return NULL;
	if (!ped_geometry_init (&full_dev, dev, 0, dev->length - 1))
		return NULL;

	min_size = hfsplus_get_min_size (fs);
	if (!min_size) return NULL;

	return ped_constraint_new (&start_align, ped_alignment_any,
				   &start_sector, &full_dev, min_size,
				   fs->geom->length);
}

static int
hfsplus_volume_resize (PedFileSystem* fs, PedGeometry* geom, PedTimer* timer)
{
	uint8_t			buf[PED_SECTOR_SIZE_DEFAULT];
	unsigned int		nblock, nfree, mblock;
	unsigned int		block, to_free, old_blocks;
	HfsPPrivateFSData* 	priv_data = (HfsPPrivateFSData*)
						fs->type_specific;
	HfsPVolumeHeader* 	vh = priv_data->vh;
	int			resize = 1;
	unsigned int		hfsp_sect_block =
				    ( PED_BE32_TO_CPU (vh->block_size)
				      / PED_SECTOR_SIZE_DEFAULT );
	unsigned int		map_sectors;

	old_blocks = PED_BE32_TO_CPU (vh->total_blocks);

	/* Flush caches */
	if (!ped_geometry_sync(priv_data->plus_geom))
		return 0;

	/* Clear the unmounted bit */
	/* and set the implementation code (Apple Creator Code) */
	vh->attributes &= PED_CPU_TO_BE32 (~( 1 << HFS_UNMOUNTED ));
	vh->last_mounted_version = PED_CPU_TO_BE32(HFSP_IMPL_Shnk);
	if (!ped_geometry_read (priv_data->plus_geom, buf, 2, 1))
		return 0;
	memcpy (buf, vh, sizeof (HfsPVolumeHeader));
	if (   !ped_geometry_write (priv_data->plus_geom, buf, 2, 1)
	    || !ped_geometry_sync (priv_data->plus_geom))
		return 0;

	ped_timer_reset (timer);
	ped_timer_set_state_name(timer, _("shrinking"));
	ped_timer_update(timer, 0.0);
	/* relocate data */
	to_free = ( priv_data->plus_geom->length
	          - geom->length + hfsp_sect_block
		  - 1 ) / hfsp_sect_block;
	block = hfsplus_find_start_pack (fs, to_free);
	if (!hfsplus_pack_free_space_from_block (fs, block, timer, to_free)) {
		resize = 0;
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("Data relocation has failed."));
		goto write_VH;
	}

	/* Calculate new block number and other VH field */
	/* nblock must be rounded _down_ */
	nblock = geom->length / hfsp_sect_block;
	nfree = PED_BE32_TO_CPU (vh->free_blocks)
		- (old_blocks - nblock);
	/* free block readjustement is only needed when incorrect nblock
	   was used by my previous implementation, so detect the case */
	if (priv_data->plus_geom->length < old_blocks
					   * ( PED_BE32_TO_CPU (vh->block_size)
					       / PED_SECTOR_SIZE_DEFAULT) ) {
		if (priv_data->plus_geom->length % hfsp_sect_block == 1)
			nfree++;
	}

	/* Check that all block after future end are really free */
	mblock = ( priv_data->plus_geom->length - 2 )
		 / hfsp_sect_block;
	if (mblock > old_blocks - 1)
		mblock = old_blocks - 1;
	for ( block = nblock;
	      block < mblock;
	      block++ ) {
		if (TST_BLOC_OCCUPATION(priv_data->alloc_map,block)) {
			resize = 0;
			ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_CANCEL,
				_("Data relocation left some data at the end "
				  "of the volume."));
			goto write_VH;
		}
	}

	/* Mark out of volume blocks as used */
	map_sectors = ( ( old_blocks + PED_SECTOR_SIZE_DEFAULT * 8 - 1 )
	                / (PED_SECTOR_SIZE_DEFAULT * 8) )
		      * (PED_SECTOR_SIZE_DEFAULT * 8);
	for ( block = nblock; block < map_sectors; ++block)
		SET_BLOC_OCCUPATION(priv_data->alloc_map, block);

	/* Update geometry */
	if (resize) {
		/* update in fs structure */
		if (PED_BE32_TO_CPU (vh->next_allocation) >= nblock)
			vh->next_allocation = PED_CPU_TO_BE32 (0);
		vh->total_blocks = PED_CPU_TO_BE32 (nblock);
		vh->free_blocks = PED_CPU_TO_BE32 (nfree);
		/* update parted structure */
		priv_data->plus_geom->length = geom->length;
		priv_data->plus_geom->end = priv_data->plus_geom->start
					    + geom->length - 1;
	}

	/* Effective write */
    write_VH:
    	/* lasts two sectors are allocated by the alternate VH
	   and a reserved sector, and last block is always reserved */
	block = (priv_data->plus_geom->length - 1) / hfsp_sect_block;
	if (block < PED_BE32_TO_CPU (vh->total_blocks))
		SET_BLOC_OCCUPATION(priv_data->alloc_map, block);
	block = (priv_data->plus_geom->length - 2) / hfsp_sect_block;
	if (block < PED_BE32_TO_CPU (vh->total_blocks))
		SET_BLOC_OCCUPATION(priv_data->alloc_map, block);
	SET_BLOC_OCCUPATION(priv_data->alloc_map,
			    PED_BE32_TO_CPU (vh->total_blocks) - 1);

	/* Write the _old_ area to set out of volume blocks as used */
	map_sectors = ( old_blocks + PED_SECTOR_SIZE_DEFAULT * 8 - 1 )
	              / (PED_SECTOR_SIZE_DEFAULT * 8);
	if (!hfsplus_file_write (priv_data->allocation_file,
				 priv_data->alloc_map, 0, map_sectors)) {
		resize = 0;
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("Error while writing the allocation file."));
	} else {
	/* Write remaining part of allocation bitmap */
	/* This is necessary to handle pre patch-11 and third party */
	/* implementations */
		memset(buf, 0xFF, PED_SECTOR_SIZE_DEFAULT);
		for (block = map_sectors;
		     block < priv_data->allocation_file->sect_nb;
		     ++block) {
			if (!hfsplus_file_write_sector (
					priv_data->allocation_file,
					buf, block)) {
				ped_exception_throw (
					PED_EXCEPTION_WARNING,
					PED_EXCEPTION_IGNORE,
					_("Error while writing the "
					  "compatibility part of the "
					  "allocation file."));
				break;
			}
		}
	}
	ped_geometry_sync (priv_data->plus_geom);

	if (resize) {
		/* Set the unmounted bit and clear the inconsistent bit */
		vh->attributes |= PED_CPU_TO_BE32 ( 1 << HFS_UNMOUNTED );
		vh->attributes &= ~ PED_CPU_TO_BE32 ( 1 << HFSP_INCONSISTENT );
	}

	ped_timer_set_state_name(timer, _("writing HFS+ Volume Header"));
	if (!hfsplus_update_vh(fs)) {
		ped_geometry_sync(priv_data->plus_geom);
		return 0;
	}

	if (!ped_geometry_sync(priv_data->plus_geom))
		return 0;

	ped_timer_update(timer, 1.0);

	return (resize);
}

/* Update the HFS wrapper mdb and bad blocks file to reflect
   the new geometry of the embedded HFS+ volume */
static int
hfsplus_wrapper_update (PedFileSystem* fs)
{
	uint8_t			node[PED_SECTOR_SIZE_DEFAULT];
	HfsCPrivateLeafRec	ref;
	HfsExtentKey		key;
	HfsNodeDescriptor*	node_desc = (HfsNodeDescriptor*) node;
	HfsExtentKey*		ret_key;
	HfsExtDescriptor*	ret_data;
	unsigned int		i;
	HfsPPrivateFSData* 	priv_data = (HfsPPrivateFSData*)
						fs->type_specific;
	HfsPrivateFSData* 	hfs_priv_data = (HfsPrivateFSData*)
					    priv_data->wrapper->type_specific;
	unsigned int		hfs_sect_block =
			PED_BE32_TO_CPU (hfs_priv_data->mdb->block_size)
			/ PED_SECTOR_SIZE_DEFAULT ;
	PedSector		hfsplus_sect = (PedSector)
			PED_BE32_TO_CPU (priv_data->vh->total_blocks)
			* ( PED_BE32_TO_CPU (priv_data->vh->block_size)
			    / PED_SECTOR_SIZE_DEFAULT );
	unsigned int		hfs_blocks_embedded =
				    (hfsplus_sect + hfs_sect_block - 1)
				    / hfs_sect_block;
	unsigned int		hfs_blocks_embedded_old;

	/* update HFS wrapper MDB */
	hfs_blocks_embedded_old = PED_BE16_TO_CPU (
					hfs_priv_data->mdb->old_new
					.embedded.location.block_count );
	hfs_priv_data->mdb->old_new.embedded.location.block_count =
		PED_CPU_TO_BE16 (hfs_blocks_embedded);
	/* maybe macOS will boot with this */
	/* update : yes it does \o/ :) */
	hfs_priv_data->mdb->free_blocks =
	    PED_CPU_TO_BE16 ( PED_BE16_TO_CPU (hfs_priv_data->mdb->free_blocks)
	                    + hfs_blocks_embedded_old
			    - hfs_blocks_embedded );

	if (!hfs_update_mdb(priv_data->wrapper))
		return 0;

	/* force reload bad block list */
	if (hfs_priv_data->bad_blocks_loaded) {
		hfs_free_bad_blocks_list (hfs_priv_data->bad_blocks_xtent_list);
		hfs_priv_data->bad_blocks_xtent_list = NULL;
		hfs_priv_data->bad_blocks_xtent_nb = 0;
		hfs_priv_data->bad_blocks_loaded = 0;
	}

	/* clean HFS wrapper allocation map */
	for (i = PED_BE16_TO_CPU (
			hfs_priv_data->mdb->old_new.embedded
			.location.start_block )
		 + hfs_blocks_embedded;
	     i < PED_BE16_TO_CPU (
	    		hfs_priv_data->mdb->old_new.embedded
			.location.start_block )
		 + hfs_blocks_embedded_old;
	     i++ ) {
		CLR_BLOC_OCCUPATION(hfs_priv_data->alloc_map, i);
	}
	/* and save it */
	if (!ped_geometry_write (fs->geom, hfs_priv_data->alloc_map,
				 PED_BE16_TO_CPU (
				      hfs_priv_data->mdb->volume_bitmap_block ),
				 ( PED_BE16_TO_CPU (
				        hfs_priv_data->mdb->total_blocks )
				   + PED_SECTOR_SIZE_DEFAULT * 8 - 1 )
				 / (PED_SECTOR_SIZE_DEFAULT * 8)))
		return 0;
	if (!ped_geometry_sync (fs->geom))
		return 0;

	/* search and update the bad blocks file */
	key.key_length = sizeof(key) - 1;
	key.type = HFS_DATA_FORK;
	key.file_ID = PED_CPU_TO_BE32 (HFS_BAD_BLOCK_ID);
	key.start = 0;
	if (!hfs_btree_search (hfs_priv_data->extent_file,
			       (HfsPrivateGenericKey*) &key, NULL, 0, &ref)) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("An error occurred while looking for the mandatory "
			  "bad blocks file."));
		return 0;
	}
	if (!hfs_file_read_sector (hfs_priv_data->extent_file, node,
				   ref.node_number))
		return 0;
	ret_key = (HfsExtentKey*) (node + ref.record_pos);
	ret_data = (HfsExtDescriptor*) ( node + ref.record_pos
					 + sizeof (HfsExtentKey) );

	while (ret_key->type == key.type && ret_key->file_ID == key.file_ID) {
		for (i = 0; i < HFS_EXT_NB; i++) {
			if ( ret_data[i].start_block
			     == hfs_priv_data->mdb->old_new
			        .embedded.location.start_block) {
				ret_data[i].block_count =
				    hfs_priv_data->mdb->old_new
				    .embedded.location.block_count;
				/* found ! : update */
				if (!hfs_file_write_sector (
					  hfs_priv_data->extent_file,
					  node, ref.node_number)
				    || !ped_geometry_sync(fs->geom))
					return 0;
				return 1;
			}
		}

		if (ref.record_number < PED_BE16_TO_CPU (node_desc->rec_nb)) {
			ref.record_number++;
		} else {
			ref.node_number = PED_BE32_TO_CPU (node_desc->next);
			if (!ref.node_number
			    || !hfs_file_read_sector(hfs_priv_data->extent_file,
						     node, ref.node_number))
				goto bb_not_found;
			ref.record_number = 1;
		}

		uint16_t value;
		memcpy(&value, node+PED_SECTOR_SIZE_DEFAULT - (2*ref.record_number), sizeof(uint16_t));
		ref.record_pos = PED_BE16_TO_CPU(value);
		ret_key = (HfsExtentKey*) (node + ref.record_pos);
		ret_data = (HfsExtDescriptor*) (node + ref.record_pos
						 + sizeof (HfsExtentKey) );
	}

bb_not_found:
	/* not found : not a valid hfs+ wrapper : failure */
	ped_exception_throw (
		PED_EXCEPTION_ERROR,
		PED_EXCEPTION_CANCEL,
		_("It seems there is an error in the HFS wrapper: the bad "
		  "blocks file doesn't contain the embedded HFS+ volume."));
	return 0;
}

int
hfsplus_resize (PedFileSystem* fs, PedGeometry* geom, PedTimer* timer)
{
	HfsPPrivateFSData* 	priv_data;
	PedTimer*		timer_plus;
	PedGeometry*		embedded_geom;
	PedSector		hgms;

	/* check preconditions */
	PED_ASSERT (fs != NULL);
	PED_ASSERT (fs->geom != NULL);
	PED_ASSERT (geom != NULL);
	PED_ASSERT (fs->geom->dev == geom->dev);
#ifdef DEBUG
        PED_ASSERT ((hgms = hfsplus_get_min_size (fs)) != 0);
#else
        if ((hgms = hfsplus_get_min_size (fs)) == 0)
                return 0;
#endif

	if (ped_geometry_test_equal(fs->geom, geom))
		return 1;

	priv_data = (HfsPPrivateFSData*) fs->type_specific;

	if (fs->geom->start != geom->start
	    || geom->length > fs->geom->length
	    || geom->length < hgms) {
		ped_exception_throw (
			PED_EXCEPTION_NO_FEATURE,
			PED_EXCEPTION_CANCEL,
			_("Sorry, HFS+ cannot be resized that way yet."));
		return 0;
	}

	if (priv_data->wrapper) {
		PedSector		red, hgee;
		HfsPrivateFSData* 	hfs_priv_data = (HfsPrivateFSData*)
					    priv_data->wrapper->type_specific;
		unsigned int		hfs_sect_block =
			    PED_BE32_TO_CPU (hfs_priv_data->mdb->block_size)
			    / PED_SECTOR_SIZE_DEFAULT;

		/* There is a wrapper so we must calculate the new geometry
		   of the embedded HFS+ volume */
		red = ( (fs->geom->length - geom->length + hfs_sect_block - 1)
			/ hfs_sect_block ) * hfs_sect_block;
		/* Can't we shrink the hfs+ volume by the desired size ? */
		hgee = hfsplus_get_empty_end (fs);
		if (!hgee) return 0;
		if (red > priv_data->plus_geom->length - hgee) {
			/* No, shrink hfs+ by the greatest possible value */
			hgee = ((hgee + hfs_sect_block - 1) / hfs_sect_block)
			       * hfs_sect_block;
			red = priv_data->plus_geom->length - hgee;
		}
		embedded_geom = ped_geometry_new (geom->dev,
						  priv_data->plus_geom->start,
						  priv_data->plus_geom->length
						  - red);

		/* There is a wrapper so the resize process is a two stages
		   process (embedded resizing then wrapper resizing) :
		   we create a sub timer */
		ped_timer_reset (timer);
		ped_timer_set_state_name (timer,
					  _("shrinking embedded HFS+ volume"));
		ped_timer_update(timer, 0.0);
		timer_plus = ped_timer_new_nested (timer, 0.98);
	} else {
		/* No wrapper : the desired geometry is the desired
		   HFS+ volume geometry */
		embedded_geom = geom;
		timer_plus = timer;
	}

	/* Resize the HFS+ volume */
	if (!hfsplus_volume_resize (fs, embedded_geom, timer_plus)) {
		if (timer_plus != timer) ped_timer_destroy_nested (timer_plus);
		if (priv_data->wrapper) ped_geometry_destroy (embedded_geom);
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("Resizing the HFS+ volume has failed."));
		return 0;
	}

	if (priv_data->wrapper) {
		ped_geometry_destroy (embedded_geom);
		ped_timer_destroy_nested (timer_plus);
		ped_timer_set_state_name(timer, _("shrinking HFS wrapper"));
		timer_plus = ped_timer_new_nested (timer, 0.02);
		/* There's a wrapper : second stage = resizing it */
		if (!hfsplus_wrapper_update (fs)
		    || !hfs_resize (priv_data->wrapper, geom, timer_plus)) {
			ped_timer_destroy_nested (timer_plus);
			ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_CANCEL,
				_("Updating the HFS wrapper has failed."));
			return 0;
		}
		ped_timer_destroy_nested (timer_plus);
	}
	ped_timer_update(timer, 1.0);

	return 1;
}

#ifdef HFS_EXTRACT_FS
/* The following is for debugging purpose only, NOT for packaging */

#include <stdio.h>

uint8_t* extract_buffer = NULL;

static int
hfs_extract_file(const char* filename, HfsPrivateFile* hfs_file)
{
	FILE*		fout;
	PedSector	sect;

	fout = fopen(filename, "w");
	if (!fout) return 0;

	for (sect = 0; sect < hfs_file->sect_nb; ++sect) {
		if (!hfs_file_read_sector(hfs_file, extract_buffer, sect))
			goto err_close;
		if (!fwrite(extract_buffer, PED_SECTOR_SIZE_DEFAULT, 1, fout))
			goto err_close;
	}

	return (fclose(fout) == 0 ? 1 : 0);

err_close:
	fclose(fout);
	return 0;
}

static int
hfs_extract_bitmap(const char* filename, PedFileSystem* fs)
{
	HfsPrivateFSData*		priv_data = (HfsPrivateFSData*)
						fs->type_specific;
	HfsMasterDirectoryBlock*	mdb = priv_data->mdb;
	unsigned int 	count;
	FILE*		fout;
	PedSector	sect;

	fout = fopen(filename, "w");
	if (!fout) return 0;

	for (sect = PED_BE16_TO_CPU(mdb->volume_bitmap_block);
	     sect < PED_BE16_TO_CPU(mdb->start_block);
	     sect += count) {
		uint16_t st_block = PED_BE16_TO_CPU(mdb->start_block);
		count = (st_block-sect) < BLOCK_MAX_BUFF ?
			(st_block-sect) : BLOCK_MAX_BUFF;
		if (!ped_geometry_read(fs->geom, extract_buffer, sect, count))
			goto err_close;
		if (!fwrite (extract_buffer, count * PED_SECTOR_SIZE_DEFAULT,
			     1, fout))
			goto err_close;
	}

	return (fclose(fout) == 0 ? 1 : 0);

err_close:
	fclose(fout);
	return 0;
}

static int
hfs_extract_mdb (const char* filename, PedFileSystem* fs)
{
	FILE*		fout;

	fout = fopen(filename, "w");
	if (!fout) return 0;

	if (!ped_geometry_read(fs->geom, extract_buffer, 2, 1))
		goto err_close;
	if (!fwrite(extract_buffer, PED_SECTOR_SIZE_DEFAULT, 1, fout))
		goto err_close;

	return (fclose(fout) == 0 ? 1 : 0);

err_close:
	fclose(fout);
	return 0;
}

static int
hfs_extract (PedFileSystem* fs, PedTimer* timer)
{
	HfsPrivateFSData*	priv_data = (HfsPrivateFSData*)
						fs->type_specific;

	ped_exception_throw (
		PED_EXCEPTION_INFORMATION,
		PED_EXCEPTION_OK,
		_("This is not a real %s check.  This is going to extract "
		  "special low level files for debugging purposes."),
		"HFS");

	extract_buffer = ped_malloc(BLOCK_MAX_BUFF * PED_SECTOR_SIZE_DEFAULT);
	if (!extract_buffer) return 0;

	hfs_extract_mdb(HFS_MDB_FILENAME, fs);
	hfs_extract_file(HFS_CATALOG_FILENAME, priv_data->catalog_file);
	hfs_extract_file(HFS_EXTENTS_FILENAME, priv_data->extent_file);
	hfs_extract_bitmap(HFS_BITMAP_FILENAME, fs);

	free(extract_buffer); extract_buffer = NULL;
	return 0; /* nothing has been fixed by us ! */
}

static int
hfsplus_extract_file(const char* filename, HfsPPrivateFile* hfsp_file)
{
	FILE*		fout;
	unsigned int	cp_sect;
	PedSector	rem_sect;

	fout = fopen(filename, "w");
	if (!fout) return 0;

	for (rem_sect = hfsp_file->sect_nb; rem_sect; rem_sect -= cp_sect) {
		cp_sect = rem_sect < BLOCK_MAX_BUFF ? rem_sect : BLOCK_MAX_BUFF;
		if (!hfsplus_file_read(hfsp_file, extract_buffer,
				       hfsp_file->sect_nb - rem_sect, cp_sect))
			goto err_close;
		if (!fwrite (extract_buffer, cp_sect * PED_SECTOR_SIZE_DEFAULT,
			     1, fout))
			goto err_close;
	}

	return (fclose(fout) == 0 ? 1 : 0);

err_close:
	fclose(fout);
	return 0;
}

static int
hfsplus_extract_vh (const char* filename, PedFileSystem* fs)
{
	HfsPPrivateFSData* 	priv_data = (HfsPPrivateFSData*)
						fs->type_specific;
	FILE*		fout;
	PedGeometry*	geom = priv_data->plus_geom;


	fout = fopen(filename, "w");
	if (!fout) return 0;

	if (!ped_geometry_read(geom, extract_buffer, 2, 1))
		goto err_close;
	if (!fwrite(extract_buffer, PED_SECTOR_SIZE_DEFAULT, 1, fout))
		goto err_close;

	return (fclose(fout) == 0 ? 1 : 0);

err_close:
	fclose(fout);
	return 0;
}

/* TODO : use the timer to report what is happening */
/* TODO : use exceptions to report errors */
static int
hfsplus_extract (PedFileSystem* fs, PedTimer* timer)
{
	HfsPPrivateFSData* 	priv_data = (HfsPPrivateFSData*)
						fs->type_specific;
	HfsPVolumeHeader*	vh = priv_data->vh;
	HfsPPrivateFile*	startup_file;

	if (priv_data->wrapper) {
		/* TODO : create nested timer */
		hfs_extract (priv_data->wrapper, timer);
	}

	ped_exception_throw (
		PED_EXCEPTION_INFORMATION,
		PED_EXCEPTION_OK,
		_("This is not a real %s check.  This is going to extract "
		  "special low level files for debugging purposes."),
		"HFS+");

	extract_buffer = ped_malloc(BLOCK_MAX_BUFF * PED_SECTOR_SIZE_DEFAULT);
	if (!extract_buffer) return 0;

	hfsplus_extract_vh(HFSP_VH_FILENAME, fs);
	hfsplus_extract_file(HFSP_CATALOG_FILENAME, priv_data->catalog_file);
	hfsplus_extract_file(HFSP_EXTENTS_FILENAME, priv_data->extents_file);
	hfsplus_extract_file(HFSP_ATTRIB_FILENAME, priv_data->attributes_file);
	hfsplus_extract_file(HFSP_BITMAP_FILENAME, priv_data->allocation_file);

	startup_file = hfsplus_file_open(fs, PED_CPU_TO_BE32(HFSP_STARTUP_ID),
					vh->startup_file.extents,
					PED_BE64_TO_CPU (
					   vh->startup_file.logical_size)
					/ PED_SECTOR_SIZE_DEFAULT);
	if (startup_file) {
		hfsplus_extract_file(HFSP_STARTUP_FILENAME, startup_file);
		hfsplus_file_close(startup_file); startup_file = NULL;
	}

	free(extract_buffer); extract_buffer = NULL;
	return 0; /* nothing has been fixed by us ! */
}
#endif /* HFS_EXTRACT_FS */

#endif /* !DISCOVER_ONLY */
