/*
 * Copyright (c) 1999-2008 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */


#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <paths.h>
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <unistd.h>

#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/stat.h>
#if LINUX
#include <time.h>
#endif

#if !LINUX
#include <IOKit/storage/IOMediaBSDClient.h>
#endif

#include <hfs/hfs_format.h>
#include "newfs_hfs.h"

#if __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

#define	NOVAL       (-1)
#define UMASK       (0755)
#define	ACCESSMASK  (0777)

/*
 * The maximum HFS volume size is calculated thusly:
 * 
 * The maximum allocation block size (which must be a power of 2 value),
 * is 2GB, or 2^31 bytes
 *
 * The maximum number of allocation blocks is 2^32 -1.  
 *
 * Multiplying that out yields 2GB * ( 4GB - 1 ) == 2GB*4GB  - 2GB.
 * More explicitly, 8 exabytes - 2 gigabytes,
 * or 0x7FFFFFFF80000000 bytes.  That gives us our value below.
 */

#define MAXHFSVOLSIZE (0x7FFFFFFF80000000ULL)

#define ROUNDUP(x,y) (((x)+(y)-1)/(y)*(y))

static void getnodeopts __P((char* optlist));
static void getclumpopts __P((char* optlist));
static gid_t a_gid __P((char *));
static uid_t a_uid __P((char *));
static mode_t a_mask __P((char *));
static int hfs_newfs __P((char *device));
static void validate_hfsplus_block_size __P((UInt64 sectorCount, UInt32 sectorSize));
static void hfsplus_params __P((const DriveInfo* dip, hfsparams_t *defaults));
static UInt32 clumpsizecalc __P((UInt32 clumpblocks));
static UInt32 CalcHFSPlusBTreeClumpSize __P((UInt32 blockSize, UInt32 nodeSize, UInt64 sectors, int fileID));
static void usage __P((void));
static int get_high_bit (u_int64_t bitstring);
static int bad_disk_size (u_int64_t numsectors, u_int64_t sectorsize);



char	*progname;
char	gVolumeName[kHFSPlusMaxFileNameChars + 1] = {kDefaultVolumeNameStr};
char	rawdevice[MAXPATHLEN];
char	blkdevice[MAXPATHLEN];
uint32_t gBlockSize = 0;
UInt32	gNextCNID = kHFSFirstUserCatalogNodeID;

time_t  createtime;

int	gNoCreate = FALSE;
int	gUserCatNodeSize = FALSE;
int	gCaseSensitive = FALSE;
int	gUserAttrSize = FALSE;
int gContentProtect = FALSE;

#define JOURNAL_DEFAULT_SIZE (8*1024*1024)
int     gJournaled = FALSE;
char    *gJournalDevice = NULL;
UInt64	gJournalSize = 0;

uid_t	gUserID = (uid_t)NOVAL;
gid_t	gGroupID = (gid_t)NOVAL;
mode_t	gModeMask = (mode_t)NOVAL;

UInt64	gPartitionSize = 0;

UInt32	catnodesiz = 8192;
UInt32	extnodesiz = 4096;
UInt32	atrnodesiz = 8192;

UInt32	catclumpblks = 0;
UInt32	extclumpblks = 0;
UInt32	atrclumpblks = 0;
UInt32	bmclumpblks = 0;
UInt32	rsrclumpblks = 0;
UInt32	datclumpblks = 0;
uint32_t hfsgrowblks = 0;      /* maximum growable size of wrapper */


UInt64
get_num(char *str)
{
    UInt64 num;
    char *ptr;

    num = strtoull(str, &ptr, 0);

    if (*ptr) {
	    char scale = tolower(*ptr);

	    switch(scale) {
	    case 'b':
		    num *= 512ULL;
		    break;
	    case 'p':
		    num *= 1024ULL;
		    /* fall through */
	    case 't':
		    num *= 1024ULL;
		    /* fall through */
	    case 'g':
		    num *= 1024ULL;
		    /* fall through */
	    case 'm':
		    num *= 1024ULL;
		    /* fall through */
	    case 'k':
		    num *= 1024ULL;
		    break;

	    default:
		    num = 0ULL;
		    break;
	}
    }
    return num;
}

int
main(argc, argv)
	int argc;
	char **argv;
{
	extern char *optarg;
	extern int optind;
	int ch;
#if !LINUX
	char *cp, *special;
	struct statfs *mp;
#endif
	int n;
	
	if ((progname = strrchr(*argv, '/')))
		++progname;
	else
		progname = *argv;


	while ((ch = getopt(argc, argv, "G:J:D:M:N:PU:hsb:c:i:n:v:")) != EOF)
		switch (ch) {
		case 'G':
			gGroupID = a_gid(optarg);
			break;

		case 'J':
			gJournaled = TRUE;
			if (isdigit(optarg[0])) {
			    gJournalSize = get_num(optarg);
			    if (gJournalSize < 512*1024) {
					printf("%s: journal size %lldk too small.  Reset to %dk.\n",
						progname, gJournalSize/1024, JOURNAL_DEFAULT_SIZE/1024);
					gJournalSize = JOURNAL_DEFAULT_SIZE;
			    }
			} else {
				/* back up because there was no size argument */
			    optind--;
			}
			break;
			
		case 'D':
			gJournalDevice = (char *)optarg;
			break;
			
		case 'N':
			gNoCreate = TRUE;
			if (isdigit(optarg[0])) {
				gPartitionSize = get_num(optarg);
			} else {
				/* back up because there was no size argument */
				optind--;
			}
			break;

		case 'P':
			gContentProtect = TRUE;
			break;

		case 'M':
			gModeMask = a_mask(optarg);
			break;

		case 'U':
			gUserID = a_uid(optarg);
			break;

		case 'b':
		{
			UInt64 tempBlockSize;
			
			tempBlockSize = get_num(optarg);
			if (tempBlockSize < HFSMINBSIZE)
				fatal("%s: bad allocation block size (too small)", optarg);
			if (tempBlockSize > HFSMAXBSIZE) 
				fatal("%s: bad allocation block size (too large)", optarg);
			gBlockSize = tempBlockSize;
			break;
		}

		case 'c':
			getclumpopts(optarg);
			break;

		case 'i':
			gNextCNID = atoi(optarg);
			/*
			 * make sure its at least kHFSFirstUserCatalogNodeID
			 */
			if (gNextCNID < kHFSFirstUserCatalogNodeID)
				fatal("%s: starting catalog node id too small (must be > 15)", optarg);
			break;

		case 'n':
			getnodeopts(optarg);
			break;

		case 's':
			gCaseSensitive = TRUE;
			break;

		case 'v':
			n = strlen(optarg);
			if (n > (sizeof(gVolumeName) - 1))
				fatal("\"%s\" is too long (%d byte maximum)",
				      optarg, sizeof(gVolumeName) - 1);
			if (n == 0)
				fatal("name required with -v option");
#if LINUX
			strncpy(gVolumeName, optarg, sizeof(gVolumeName));
#else
			strlcpy(gVolumeName, optarg, sizeof(gVolumeName));
#endif
			break;

		case '?':
		default:
			usage();
		}

	argc -= optind;
	argv += optind;

	if (gPartitionSize != 0) {
		/*
		 * If we are given -N, a size, and a device, that's a usage error.
		 */
		if (argc != 0)
			usage();

		rawdevice[0] = blkdevice[0] = 0;
	} else {
		if (argc != 1)
			usage();

#if LINUX
		(void) sprintf(blkdevice, "%s", argv[0]);
#else
		special = argv[0];
		cp = strrchr(special, '/');
		if (cp != 0)
			special = cp + 1;
		if (*special == 'r')
			special++;
		(void) snprintf(rawdevice, sizeof(rawdevice), "%sr%s", _PATH_DEV, special);
		(void) snprintf(blkdevice, sizeof(blkdevice), "%s%s", _PATH_DEV, special);
#endif
	}

	if (gPartitionSize == 0) {
		/*
		 * Check if target device is aready mounted
		 */
#if LINUX
		//FIXME
#else
		n = getmntinfo(&mp, MNT_NOWAIT);
		if (n == 0)
			fatal("%s: getmntinfo: %s", blkdevice, strerror(errno));

		while (--n >= 0) {
			if (strcmp(blkdevice, mp->f_mntfromname) == 0)
				fatal("%s is mounted on %s", blkdevice, mp->f_mntonname);
			++mp;
		}
#endif
	}
	if (hfs_newfs(blkdevice) < 0) {
#if LINUX
		err(1, "cannot create filesystem on %s", blkdevice);
#else
		err(1, "cannot create filesystem on %s", rawdevice);
#endif
	}

	exit(0);
}


static void getnodeopts(char* optlist)
{
	char *strp = optlist;
	char *ndarg;
	char *p;
	UInt32 ndsize;
	
	while((ndarg = strsep(&strp, ",")) != NULL && *ndarg != '\0') {

		p = strchr(ndarg, '=');
		if (p == NULL)
			usage();
	
		ndsize = atoi(p+1);

		switch (*ndarg) {
		case 'c':
			if (ndsize < 4096 || ndsize > 32768 || (ndsize & (ndsize-1)) != 0)
				fatal("%s: invalid catalog b-tree node size", ndarg);
			catnodesiz = ndsize;
			gUserCatNodeSize = TRUE;
			break;

		case 'e':
			if (ndsize < 1024 || ndsize > 32768 || (ndsize & (ndsize-1)) != 0)
				fatal("%s: invalid extents b-tree node size", ndarg);
			extnodesiz = ndsize;
			break;

		case 'a':
			if (ndsize < 4096 || ndsize > 32768 || (ndsize & (ndsize-1)) != 0)
				fatal("%s: invalid atrribute b-tree node size", ndarg);
			atrnodesiz = ndsize;
			break;

		default:
			usage();
		}
	}
}


static void getclumpopts(char* optlist)
{
	char *strp = optlist;
	char *ndarg;
	char *p;
	UInt32 clpblocks;
	
	while((ndarg = strsep(&strp, ",")) != NULL && *ndarg != '\0') {

		p = strchr(ndarg, '=');
		if (p == NULL)
			usage();
			
		clpblocks = atoi(p+1);
		
		switch (*ndarg) {
		case 'a':
			atrclumpblks = clpblocks;
			gUserAttrSize = TRUE;
			break;
		case 'b':
			bmclumpblks = clpblocks;
			break;
		case 'c':
			catclumpblks = clpblocks;
			break;
		case 'd':
			datclumpblks = clpblocks;
			break;
		case 'e':
			extclumpblks = clpblocks;
			break;
		case 'r':
			rsrclumpblks = clpblocks;
			break;

		default:
			usage();
		}
	}
}

gid_t
static a_gid(char *s)
{
	struct group *gr;
	char *gname;
	gid_t gid = 0;

	if ((gr = getgrnam(s)) != NULL)
		gid = gr->gr_gid;
	else {
		for (gname = s; *s && isdigit(*s); ++s);
		if (!*s)
			gid = atoi(gname);
		else
			errx(1, "unknown group id: %s", gname);
	}
	return (gid);
}

static uid_t
a_uid(char *s)
{
	struct passwd *pw;
	char *uname;
	uid_t uid = 0;

	if ((pw = getpwnam(s)) != NULL)
		uid = pw->pw_uid;
	else {
		for (uname = s; *s && isdigit(*s); ++s);
		if (!*s)
			uid = atoi(uname);
		else
			errx(1, "unknown user id: %s", uname);
	}
	return (uid);
}

static mode_t
a_mask(char *s)
{
	int done, rv;
	char *ep;

	done = 0;
	rv = -1;
	if (*s >= '0' && *s <= '7') {
		done = 1;
		rv = strtol(s, &ep, 8);
	}
	if (!done || rv < 0 || *ep)
		errx(1, "invalid access mask: %s", s);
	return (rv);
}

/*
 * Check to see if the volume is too big.
 *
 * Returns:
 *		0 if it is appropriately sized.
 *		1 if HFS+ cannot be formatted onto the disk.
 */

static int bad_disk_size (u_int64_t numsectors, u_int64_t sectorsize) {
	
	u_int32_t maxSectorBits = 0;
	u_int32_t maxSectorSizeBits = 0;
	u_int32_t maxBits = 0;
	u_int64_t bytes;
	
	/*
	 * The essential problem here is that we cannot simply multiply the sector size by the
	 * number of sectors because the product could overflow a 64 bit integer.  We do a cursory 
	 * check and then a longer check once we know the product will not overflow.
	 */ 	
	
	maxSectorBits = get_high_bit (numsectors);
	maxSectorSizeBits = get_high_bit (sectorsize);
	
	/*
	 * We get the number of bits to represent the number of sectors and the sector size.  
	 * Adding the two numbers gives us the number of bits required to represent the product.
	 * If the product is > 63 then it must be too big. 
	 */
	
	maxBits = maxSectorBits + maxSectorSizeBits;
	if (maxBits > 63) {
		return 1;
	}
	
	/* Well, now we know that the two values won't overflow.  Time to multiply */
	bytes = numsectors * sectorsize;
	
	if (bytes > MAXHFSVOLSIZE) {
		/* Too big! */
		return 1;
	}
	
	/* Otherwise, it looks good */
	return 0;
	
}

/* 
 * The allocation block size must be defined as a power of 2 value, with a floor of
 * 512 bytes.  However, we never default to anything less than 4096 bytes, so that
 * gives us 20 block size values from 4kb -> 2GB block size.
 *
 * See inline comments for how this table is used to determine the minimum fs size that
 * will use a specified allocation block size.  
 *
 * The growth boundary is used to figure out if we need a bigger block size than the
 * 4 KB default.  We get the index of the highest bit set in the FS size, then subtract the
 * growth boundary to index into the block allocation size array.
 *
 * Note that 8K appears twice in table since we want to use it for the range 2 TB < 8 TB FS size.
 * This means that when the 2TB bit or the 4TB bit is the high bit set, we prefer the 8K block size.
 */
#define NUM_ALLOC_BLOCKSIZES 21
#define GROWTH_BOUNDARY 41

u_int64_t alloc_blocksize[NUM_ALLOC_BLOCKSIZES] =  {
	/* Block Size*/ /* Min Dflt FS Size */ /* Max FS Size */
	4096,			/* 0 bytes */			/* 16 TB */
	8192,			/* 2 TB */				/* 32 TB */		/* Note that 8K appears twice in table ! */
	8192,			/* 4 TB */				/* 32 TB */		/* Note that 8K appears twice in table ! */
	16384,			/* 8 TB */				/* 64 TB */
	32768,			/* 16 TB */				/* 128 TB */
	65536,			/* 32 TB */				/* 256 TB */
	131072,			/* 64 TB */				/* 512 TB */
	262144,			/* 128 TB */			/* 1 PB */
	524288,			/* 256 TB */			/* 2 PB */
	1048576,		/* 512 TB */			/* 4 PB */
	2097152,		/* 1 PB */				/* 8 PB */
	4194304,		/* 2 PB */				/* 16 PB */
	8388608,		/* 4 PB */				/* 32 PB */
	16777216,		/* 8 PB */				/* 64 PB */	
	33554432,		/* 16 PB */				/* 128 PB */
	67108864,		/* 32 PB */				/* 256 PB */
	134217728,		/* 64 PB */				/* 512 PB */
	268435456,		/* 128 PB */			/* 1 EB */
	536870912,		/* 256 PB */			/* 2 EB */
	1073741824,		/* 512 PB */			/* 4 EB */
	2147483648ULL	/* 1 EB */				/* 8 EB */
};

static int get_high_bit (u_int64_t bitstring) {
	u_int64_t bits = bitstring;
	int counter = 0;
	while (bits) {
		bits = (bits >> 1);
		counter++;
	}
	return counter;
}


/*
 * Validate the HFS Plus allocation block size in gBlockSize.  If none was
 * specified, then calculate a suitable default.
 *
 * Modifies the global variable gBlockSize.
 */
static void validate_hfsplus_block_size(UInt64 sectorCount, UInt32 sectorSize)
{
	if (gBlockSize == 0) {
		
		/* Start by calculating the fs size */
		u_int64_t fs_size = sectorCount * sectorSize;
		
		/* 
		 * Determine the default based on a sliding scale.  The maximum number of 
		 * allocation blocks is always 4294967295 == (32 bits worth).  At 1 bit per 
		 * allocation block, that yields 512 MB of bitmap no matter what size we use 
		 * for the allocation block.
		 *
		 * The general default policy is to allow the filesystem to grow up to 8x the 
		 * current maximum size.  So for a 1.5TB filesystem, an 8x multiplier would be
		 * 12TB.  That means we can use the default size of 4096 bytes.  The boundary begins
		 * at 2TB, since at that point, we can no longer use the default 4096 block size to 
		 * extend the filesystem by 8x.  For a 16KB block size, the max is 64 TB, but the 8x 
		 * multiplier begins at 8 TB.  Thereafter, we increase for every power of 2 that
		 * the current filesystem size grows.
		 */
		
		gBlockSize = DFL_BLKSIZE;	/* Prefer the default of 4K */
		
		int bit_index = get_high_bit (fs_size);
		bit_index -= GROWTH_BOUNDARY;
		
		/*
		 * After subtracting the GROWTH_BOUNDARY to index into the array, we'll
		 * use the values in the static array if we have a non-negative index.  
		 * That means that if the filesystem is >= 1 TB, then we'll use the index 
		 * value. At 2TB, we grow to the 8K block size.
		 */
		if ((bit_index >= 0) && (bit_index < 22)) {
			gBlockSize = alloc_blocksize[bit_index];
		}
		
		if (bit_index >= 22) {
			fatal("Error: Disk Device is too big (%llu sectors, %d bytes per sector", sectorCount, sectorSize);
		}
	} 
	else {
		/* Make sure a user-specified block size is reasonable */
		if ((gBlockSize & (gBlockSize-1)) != 0) {
			fatal("%s: bad HFS Plus allocation block size (must be a power of two)", optarg);
		}
		
		if ((sectorCount / (gBlockSize / sectorSize)) > 0xFFFFFFFF) {
			fatal("%s: block size is too small for %lld sectors", optarg, gBlockSize, sectorCount);
		}
		
		if (gBlockSize < HFSOPTIMALBLKSIZE) {
			warnx("Warning: %u is a non-optimal block size (4096 would be a better choice)", (unsigned int)gBlockSize);
		}
	}
}



static int
hfs_newfs(char *device)
{
	struct stat stbuf;
	DriveInfo dip = { 0 };
	int fso = -1;
	int retval = 0;
	hfsparams_t defaults = {0};
#if !LINUX
	UInt64 maxPhysPerIO = 0;
#endif

	if (gPartitionSize) {
		dip.sectorSize = kBytesPerSector;
		dip.physTotalSectors = dip.totalSectors = gPartitionSize / kBytesPerSector;
		dip.physSectorSize = kBytesPerSector;	/* 512-byte sectors */
		dip.fd = 0;
	} else {
		if (gNoCreate) {
			fso = open( device, O_RDONLY | O_NDELAY, 0 );
		} else {
			fso = open( device, O_RDWR | O_NDELAY, 0 );
		}
		if (fso == -1) {
			return -1;
		}

		dip.fd = fso;
#if !LINUX
		fcntl(fso, F_NOCACHE, 1);
#endif
		if (fso < 0)
			fatal("%s: %s", device, strerror(errno));

		if (fstat( fso, &stbuf) < 0)
			fatal("%s: %s", device, strerror(errno));

#if LINUX
		dip.sectorSize = 512;
		dip.physSectorSize = 512;
		dip.physSectorsPerIO = 1;
#ifndef        BLKGETSIZE
#define        BLKGETSIZE              _IO(0x12,96)
#endif
#ifndef        BLKGETSIZE64
#define BLKGETSIZE64           _IOR(0x12,114,size_t)
#endif
        
		if (S_ISREG(stbuf.st_mode)) {
			dip.totalSectors = stbuf.st_size / 512;
		} 
		else if (S_ISBLK(stbuf.st_mode)) {
			unsigned long size;
			u_int64_t size64;
			if (!ioctl(fso, BLKGETSIZE64, &size64))
				dip.totalSectors = size64 / 512;
			else if (!ioctl(fso, BLKGETSIZE, &size))
				dip.totalSectors = size;
			else
				fatal("%s: %s", device, strerror(errno));
		} 
		else
			fatal("%s: is not a block device", device);
	}
#else
		if (ioctl(fso, DKIOCGETBLOCKSIZE, &dip.physSectorSize) < 0)
			fatal("%s: %s", device, strerror(errno));

		if ((dip.physSectorSize % kBytesPerSector) != 0)
			fatal("%d is an unsupported sector size\n", dip.physSectorSize);

		if (ioctl(fso, DKIOCGETBLOCKCOUNT, &dip.physTotalSectors) < 0)
			fatal("%s: %s", device, strerror(errno));

	}

	dip.physSectorsPerIO = (1024 * 1024) / dip.physSectorSize;  /* use 1M as default */

	if (fso != -1 && ioctl(fso, DKIOCGETMAXBLOCKCOUNTREAD, &maxPhysPerIO) < 0)
		fatal("%s: %s", device, strerror(errno));

	if (maxPhysPerIO)
		dip.physSectorsPerIO = MIN(dip.physSectorsPerIO, maxPhysPerIO);

	if (fso != -1 && ioctl(fso, DKIOCGETMAXBLOCKCOUNTWRITE, &maxPhysPerIO) < 0)
		fatal("%s: %s", device, strerror(errno));

	if (maxPhysPerIO)
		dip.physSectorsPerIO = MIN(dip.physSectorsPerIO, maxPhysPerIO);

	if (fso != -1 && ioctl(fso, DKIOCGETMAXBYTECOUNTREAD, &maxPhysPerIO) < 0)
		fatal("%s: %s", device, strerror(errno));

	if (maxPhysPerIO)
		dip.physSectorsPerIO = MIN(dip.physSectorsPerIO, maxPhysPerIO / dip.physSectorSize);

	if (fso != -1 && ioctl(fso, DKIOCGETMAXBYTECOUNTWRITE, &maxPhysPerIO) < 0)
		fatal("%s: %s", device, strerror(errno));

	if (maxPhysPerIO)
		dip.physSectorsPerIO = MIN(dip.physSectorsPerIO, maxPhysPerIO / dip.physSectorSize);

	dip.sectorSize = kBytesPerSector;
	dip.totalSectors = dip.physTotalSectors * dip.physSectorSize / dip.sectorSize;
#endif

	dip.sectorOffset = 0;
	time(&createtime);
	
	/* Check to see if the disk is too big */
	u_int64_t secsize = (u_int64_t) dip.sectorSize;
	if (bad_disk_size(dip.totalSectors, secsize)) {
		fatal("%s: partition is too big (maximum is %llu KB)", device, MAXHFSVOLSIZE/1024);
	}
	
	/*
	 * If we're going to make an HFS Plus disk (with or without a wrapper), validate the
	 * HFS Plus allocation block size.  This will also calculate a default allocation
	 * block size if none (or zero) was specified.
	 */
	validate_hfsplus_block_size(dip.totalSectors, dip.sectorSize);

	/* Make an HFS Plus disk */	

	if ((dip.totalSectors * dip.sectorSize ) < kMinHFSPlusVolumeSize)
		fatal("%s: partition is too small (minimum is %d KB)", device, kMinHFSPlusVolumeSize/1024);

	hfsplus_params(&dip, &defaults);
	if (gNoCreate == 0) {
		retval = make_hfsplus(&dip, &defaults);
		if (retval == 0) {
			printf("Initialized %s as a ", device);
			if (dip.totalSectors > 2048ULL*1024*1024)
				printf("%ld TB",
						(long)((dip.totalSectors + (1024ULL*1024*1024))/(2048ULL*1024*1024)));
			else if (dip.totalSectors > 2048*1024)
				printf("%ld GB",
						(long)((dip.totalSectors + (1024*1024))/(2048*1024)));
			else if (dip.totalSectors > 2048)
				printf("%ld MB",
						(long)((dip.totalSectors + 1024)/2048));
			else
				printf("%ld KB",
						(long)((dip.totalSectors + 1)/2));
			if (gJournaled)
				printf(" HFS Plus volume with a %uk journal\n",
						(u_int32_t)defaults.journalSize/1024);
			else
				printf(" HFS Plus volume\n");
		}
	}

	if (retval)
		fatal("%s: %s", device, strerror(errno));

	if ( fso > 0 ) {
		close(fso);
	}

	return retval;
}

/*
 typedef struct block_info {
	off_t       bnum;		//64 bit
	union {
		_blk_info   bi;		//64 bit
		struct buf *bp;		//64 bit on K64, 32 bit on K32
	} u;
 }__attribute__((__packed__)) block_info;
 
 total size == 16 bytes 
 */

#define BLOCK_INFO_SIZE 16

static void hfsplus_params (const DriveInfo* dip, hfsparams_t *defaults)
{
	UInt64  sectorCount = dip->totalSectors;
	UInt32  sectorSize = dip->sectorSize;
	uint32_t totalBlocks;
	UInt32	minClumpSize;
	UInt32	clumpSize;
	UInt32	oddBitmapBytes;
	
	defaults->flags = 0;
	defaults->blockSize = gBlockSize;
	defaults->nextFreeFileID = gNextCNID;
	defaults->createDate = createtime + MAC_GMT_FACTOR;     /* Mac OS GMT time */
	defaults->hfsAlignment = 0;
	defaults->journaledHFS = gJournaled;
	defaults->journalDevice = gJournalDevice;

	/*
	 * If any of the owner, group or mask are set then
	 * make sure they're all valid and pass them down.
	 */
	if (gUserID != (uid_t)NOVAL  ||
	    gGroupID != (gid_t)NOVAL ||
	    gModeMask != (mode_t)NOVAL) {
		defaults->owner = (gUserID == (uid_t)NOVAL) ? geteuid() : gUserID;
		defaults->group = (gGroupID == (gid_t)NOVAL) ? getegid() : gGroupID;
		defaults->mask = (gModeMask == (mode_t)NOVAL) ? UMASK : (gModeMask & ACCESSMASK);
		
		defaults->flags |= kUseAccessPerms;
	}

	/*
	 * We want at least 8 megs of journal for each 100 gigs of
	 * disk space.  We cap the size at 512 megs (64x default), unless
	 * the allocation block size is larger, in which case we use one
	 * allocation block.
	 *
	 * Only scale if it's the default, otherwise just take what
	 * the user specified, with the caveat below.
	 */
	if (gJournaled) { 
		
		if (gJournalSize != 0) {
			/* 
			 * Check to ensure the journal size is not too small relative to the
			 * sector size of the device.  This is the check in the kernel:
				if (tr->blhdr && (tr->blhdr->max_blocks <= 0 || 
				tr->blhdr->max_blocks > (tr->jnl->jhdr->size/tr->jnl->jhdr->jhdr_size))) 
			 * We assume that there will be a block header and that there will be a 
			 * non-negative max_blocks value.  
			 * 
			 * The 2nd check is the problematic one.  We cannot have a journal that's too 
			 * small relative to the sector size.  max_blocks == (blhdr_size / 16).  However, 
			 * this only matters where the current block header size is smaller than the current 
			 * sector size. So, assume that the blhdr_size == sector size for now.  We look 
			 * at the condition above to get the rest of the equation -- (journal size / sector size).  
			 * Then, it's simple algebra to figure out what the new minimum journal size 
			 * should be:
			 * 
			 *	(sector_size / 16) > (journal_size / sector_size)
			 *	(sector_size / 16) = (journal_size / sector_size)
			 *	(sector_size / 16) * sector_size = (journal_size / sector_size) * sector_size
			 *	(sector_size / 16) * sector_size = journal_size
			 *
			 *  This becomes our new floor for the journal_size. 
			 */
			if (dip->physSectorSize != 0) {
				u_int32_t min_size = 0;
				min_size = dip->physSectorSize * (dip->physSectorSize / BLOCK_INFO_SIZE);
				
				if (gJournalSize < min_size) {
					printf("%s: journal size %lldk too small.  Reset to %dk.\n",
						   progname, gJournalSize/1024, JOURNAL_DEFAULT_SIZE/1024);
					gJournalSize = 0;
				}
			}
			/* defaults->journalSize will get reset below if it is 0 */
			defaults->journalSize = gJournalSize;
		}

		if ((gJournalSize == 0) || (defaults->journalSize == 0)) {
			UInt32 jscale;

			jscale = (sectorCount * sectorSize) / ((UInt64)100 * 1024 * 1024 * 1024);
			if (jscale > 64) {
				jscale = 64;
			}
			defaults->journalSize = JOURNAL_DEFAULT_SIZE * (jscale + 1);
		} 

		if (defaults->journalSize > 512 * 1024 * 1024) {
			defaults->journalSize = 512 * 1024 * 1024;
		}

		if (defaults->journalSize < defaults->blockSize) {
			defaults->journalSize = defaults->blockSize;
		}
	}
	
	// volumes that are 128 megs or less in size have such
	// a small bitmap (one 4k-block) and inherhently such
	// a small btree that we can get by with a much smaller
	// journal.  even in a worst case scenario of a catalog
	// filled with very long korean file names we should
	// never touch more than 256k of meta-data for a single
	// transaction.  therefore we'll make the journal 512k
	// which is safe and doesn't waste much space.
	//
	if (sectorCount * sectorSize < 128*1024*1024) {
		defaults->journalSize = 512 * 1024;
	}

	strncpy((char *)defaults->volumeName, gVolumeName, sizeof(defaults->volumeName) - 1);
	defaults->volumeName[sizeof(defaults->volumeName) - 1] = '\0';

	if (rsrclumpblks == 0) {
		if (gBlockSize > DFL_BLKSIZE)
			defaults->rsrcClumpSize = ROUNDUP(kHFSPlusRsrcClumpFactor * DFL_BLKSIZE, gBlockSize);
		else
			defaults->rsrcClumpSize = kHFSPlusRsrcClumpFactor * gBlockSize;
	} else
		defaults->rsrcClumpSize = clumpsizecalc(rsrclumpblks);

	if (datclumpblks == 0) {
		if (gBlockSize > DFL_BLKSIZE)
			defaults->dataClumpSize = ROUNDUP(kHFSPlusRsrcClumpFactor * DFL_BLKSIZE, gBlockSize);
		else
			defaults->dataClumpSize = kHFSPlusRsrcClumpFactor * gBlockSize;
	} else
		defaults->dataClumpSize = clumpsizecalc(datclumpblks);

	/*
	 * The default  b-tree node size is 8K.  However, if the
	 * volume is small (< 1 GB) we use 4K instead.
	 */
	if (!gUserCatNodeSize) {
		if ((gBlockSize < HFSOPTIMALBLKSIZE) ||
		    ((UInt64)(sectorCount * sectorSize) < (UInt64)0x40000000))
			catnodesiz = 4096;
	}

	if (catclumpblks == 0) {
		clumpSize = CalcHFSPlusBTreeClumpSize(gBlockSize, catnodesiz, sectorCount, kHFSCatalogFileID);
	}
	else {
		clumpSize = clumpsizecalc(catclumpblks);
		
		if (clumpSize % catnodesiz != 0)
			fatal("c=%ld: clump size is not a multiple of node size\n", clumpSize/gBlockSize);
	}
	defaults->catalogClumpSize = clumpSize;
	defaults->catalogNodeSize = catnodesiz;
	if (gBlockSize < 4096 && gBlockSize < catnodesiz)
		warnx("Warning: block size %u is less than catalog b-tree node size %u", (unsigned int)gBlockSize, (unsigned int)catnodesiz);

	if (extclumpblks == 0) {
		clumpSize = CalcHFSPlusBTreeClumpSize(gBlockSize, extnodesiz, sectorCount, kHFSExtentsFileID);
	}
	else {
		clumpSize = clumpsizecalc(extclumpblks);
		if (clumpSize % extnodesiz != 0)
			fatal("e=%ld: clump size is not a multiple of node size\n", clumpSize/gBlockSize);
	}
	defaults->extentsClumpSize = clumpSize;
	defaults->extentsNodeSize = extnodesiz;
	if (gBlockSize < extnodesiz)
		warnx("Warning: block size %u is less than extents b-tree node size %u", (unsigned int)gBlockSize, (unsigned int)extnodesiz);

	if (atrclumpblks == 0) {
		if (gUserAttrSize) {
			clumpSize = 0;
		}
		else {
			clumpSize = CalcHFSPlusBTreeClumpSize(gBlockSize, atrnodesiz, sectorCount, kHFSAttributesFileID);
		}
	}
	else {
		clumpSize = clumpsizecalc(atrclumpblks);
		if (clumpSize % atrnodesiz != 0)
			fatal("a=%ld: clump size is not a multiple of node size\n", clumpSize/gBlockSize);
	}
	defaults->attributesClumpSize = clumpSize;
	defaults->attributesNodeSize = atrnodesiz;

	/*
	 * Calculate the number of blocks needed for bitmap (rounded up to a multiple of the block size).
	 */
	
	/*
	 * Figure out how many bytes we need for the given totalBlocks
	 * Note: this minimum value may be too large when it counts the
	 * space used by the wrapper
	 */
	totalBlocks = sectorCount / (gBlockSize / sectorSize);

	minClumpSize = totalBlocks >> 3;	/* convert bits to bytes by dividing by 8 */
	if (totalBlocks & 7)
		++minClumpSize;	/* round up to whole bytes */
	
	/* Round up to a multiple of blockSize */
	if ((oddBitmapBytes = minClumpSize % gBlockSize))
		minClumpSize = minClumpSize - oddBitmapBytes + gBlockSize;

	if (bmclumpblks == 0) {
		clumpSize = minClumpSize;
	}
	else {
		clumpSize = clumpsizecalc(bmclumpblks);

		if (clumpSize < minClumpSize)
			fatal("b=%ld: bitmap clump size is too small\n", clumpSize/gBlockSize);
	}
	defaults->allocationClumpSize = clumpSize;
	
	if (gCaseSensitive)
		defaults->flags |= kMakeCaseSensitive;
	
	if (gContentProtect)
		defaults->flags |= kMakeContentProtect;
	
	if (gNoCreate) {
		if (gPartitionSize == 0)
			printf("%llu sectors (%u bytes per sector)\n", dip->physTotalSectors, dip->physSectorSize);
		printf("HFS Plus format parameters:\n");
		printf("\tvolume name: \"%s\"\n", gVolumeName);
		printf("\tblock-size: %u\n", defaults->blockSize);
		printf("\ttotal blocks: %u\n", totalBlocks);
		if (gJournaled)
			printf("\tjournal-size: %uk\n", defaults->journalSize/1024);
		printf("\tfirst free catalog node id: %u\n", defaults->nextFreeFileID);
		printf("\tcatalog b-tree node size: %u\n", defaults->catalogNodeSize);
		printf("\tinitial catalog file size: %u\n", defaults->catalogClumpSize);
		printf("\textents b-tree node size: %u\n", defaults->extentsNodeSize);
		printf("\tinitial extents file size: %u\n", defaults->extentsClumpSize);
		printf("\tattributes b-tree node size: %u\n", defaults->attributesNodeSize);
		printf("\tinitial attributes file size: %u\n", defaults->attributesClumpSize);
		printf("\tinitial allocation file size: %u (%u blocks)\n",
			defaults->allocationClumpSize, defaults->allocationClumpSize / gBlockSize);
		printf("\tdata fork clump size: %u\n", defaults->dataClumpSize);
		printf("\tresource fork clump size: %u\n", defaults->rsrcClumpSize);
		if (defaults->flags & kUseAccessPerms) {
			printf("\tuser ID: %d\n", (int)defaults->owner);
			printf("\tgroup ID: %d\n", (int)defaults->group);
			printf("\taccess mask: %o\n", (int)defaults->mask);
		}
	}
}


static UInt32
clumpsizecalc(UInt32 clumpblocks)
{
	UInt64 clumpsize;

	clumpsize = (UInt64)clumpblocks * (UInt64)gBlockSize;
		
	if (clumpsize & (UInt64)(0xFFFFFFFF00000000ULL))
		fatal("=%ld: too many blocks for clump size!", clumpblocks);

	return ((UInt32)clumpsize);
}


#define CLUMP_ENTRIES	15

short clumptbl[CLUMP_ENTRIES * 3] = {
/*
 *	    Volume	Attributes	 Catalog	 Extents
 *	     Size	Clump (MB)	Clump (MB)	Clump (MB)
 */
	/*   1GB */	  4,		  4,		 4,
	/*   2GB */	  6,		  6,		 4,
	/*   4GB */	  8,		  8,		 4,
	/*   8GB */	 11,		 11,		 5,
	/*
	 * For volumes 16GB and larger, we want to make sure that a full OS
	 * install won't require fragmentation of the Catalog or Attributes
	 * B-trees.  We do this by making the clump sizes sufficiently large,
	 * and by leaving a gap after the B-trees for them to grow into.
	 *
	 * For SnowLeopard 10A298, a FullNetInstall with all packages selected
	 * results in:
	 * Catalog B-tree Header
	 *	nodeSize:          8192
	 *	totalNodes:       31616
	 *	freeNodes:         1978
	 * (used = 231.55 MB)
	 * Attributes B-tree Header
	 *	nodeSize:          8192
	 *	totalNodes:       63232
	 *	freeNodes:          958
	 * (used = 486.52 MB)
	 *
	 * We also want Time Machine backup volumes to have a sufficiently
	 * large clump size to reduce fragmentation.
	 *
	 * The series of numbers for Catalog and Attribute form a geometric series.
	 * For Catalog (16GB to 512GB), each term is 8**(1/5) times the previous
	 * term.  For Attributes (16GB to 512GB), each term is 4**(1/5) times
	 * the previous term.  For 1TB to 16TB, each term is 2**(1/5) times the
	 * previous term.
	 */
	/*  16GB */	 64,		 32,		 5,
	/*  32GB */	 84,		 49,		 6,
	/*  64GB */	111,		 74,		 7,
	/* 128GB */	147,		111,		 8,
	/* 256GB */	194,		169,		 9,
	/* 512GB */	256,		256,		11,
	/*   1TB */	294,		294,		14,
	/*   2TB */	338,		338,		16,
	/*   4TB */	388,		388,		20,
	/*   8TB */	446,		446,		25,
	/*  16TB */	512,		512,		32
};

/*
 * CalcHFSPlusBTreeClumpSize
 *	
 * This routine calculates the file clump size for either
 * the catalog file or the extents overflow file.
 */
static UInt32
CalcHFSPlusBTreeClumpSize(UInt32 blockSize, UInt32 nodeSize, UInt64 sectors, int fileID)
{
	UInt32 mod = MAX(nodeSize, blockSize);
	UInt32 clumpSize;
	int column;
	int i;

	/* Figure out which column of the above table to use for this file. */
	switch (fileID) {
		case kHFSAttributesFileID:
			column = 0;
			break;
		case kHFSCatalogFileID:
			column = 1;
			break;
		default:
			column = 2;
			break;
	}
	
	/*
	 * The default clump size is 0.8% of the volume size. And
	 * it must also be a multiple of the node and block size.
	 */
	if (sectors < 0x200000) {
		clumpSize = sectors << 2;	/*  0.8 %  */
		if (clumpSize < (8 * nodeSize))
			clumpSize = 8 * nodeSize;
	} else {
		/*
		 * XXX This should scale more smoothly!
		 */
		/* turn exponent into table index... */
		for (i = 0, sectors = sectors >> 22;
		     sectors && (i < CLUMP_ENTRIES-1);
		     ++i, sectors = sectors >> 1);
		
		clumpSize = clumptbl[column + (i) * 3] * 1024 * 1024;
	}
	
	/*
	 * Round the clump size to a multiple of node and block size.
	 * NOTE: This rounds down.
	 */
	clumpSize /= mod;
	clumpSize *= mod;
	
	/*
	 * Rounding down could have rounded down to 0 if the block size was
	 * greater than the clump size.  If so, just use one block or node.
	 */
	if (clumpSize == 0)
		clumpSize = mod;
		
	return (clumpSize);
}


/* VARARGS */
void
#if __STDC__
fatal(const char *fmt, ...)
#else
fatal(fmt, va_alist)
	char *fmt;
	va_dcl
#endif
{
	va_list ap;

#if __STDC__
	va_start(ap, fmt);
#else
	va_start(ap);
#endif
	if (fcntl(STDERR_FILENO, F_GETFL) < 0) {
		openlog(progname, LOG_CONS, LOG_DAEMON);
		vsyslog(LOG_ERR, fmt, ap);
		closelog();
	} else {
		vwarnx(fmt, ap);
	}
	va_end(ap);
	exit(1);
	/* NOTREACHED */
}


void usage()
{
	fprintf(stderr, "usage: %s [-N [partition-size]] [hfsplus-options] special-device\n", progname);

	fprintf(stderr, "  options:\n");
	fprintf(stderr, "\t-N do not create file system, just print out parameters\n");
	fprintf(stderr, "\t-s use case-sensitive filenames (default is case-insensitive)\n");

	fprintf(stderr, "  where hfsplus-options are:\n");
	fprintf(stderr, "\t-J [journal-size] make this HFS+ volume journaled\n");
	fprintf(stderr, "\t-D journal-dev use 'journal-dev' for an external journal\n");
	fprintf(stderr, "\t-G group-id (for root directory)\n");
	fprintf(stderr, "\t-U user-id (for root directory)\n");
	fprintf(stderr, "\t-M octal access-mask (for root directory)\n");
	fprintf(stderr, "\t-b allocation block size (4096 optimal)\n");
	fprintf(stderr, "\t-c clump size list (comma separated)\n");
	fprintf(stderr, "\t\ta=blocks (attributes file)\n");
	fprintf(stderr, "\t\tb=blocks (bitmap file)\n");
	fprintf(stderr, "\t\tc=blocks (catalog file)\n");
	fprintf(stderr, "\t\td=blocks (user data fork)\n");
	fprintf(stderr, "\t\te=blocks (extents file)\n");
	fprintf(stderr, "\t\tr=blocks (user resource fork)\n");
	fprintf(stderr, "\t-i starting catalog node id\n");
	fprintf(stderr, "\t-n b-tree node size list (comma separated)\n");
	fprintf(stderr, "\t\te=size (extents b-tree)\n");
	fprintf(stderr, "\t\tc=size (catalog b-tree)\n");
	fprintf(stderr, "\t\ta=size (attributes b-tree)\n");
	fprintf(stderr, "\t-v volume name (in ascii or UTF-8)\n");

	fprintf(stderr, "  examples:\n");
	fprintf(stderr, "\t%s -v Untitled /dev/rdisk0s7 \n", progname);
	fprintf(stderr, "\t%s -v Untitled -n c=4096,e=1024 /dev/rdisk0s7 \n", progname);
	fprintf(stderr, "\t%s -v Untitled -c b=64,c=1024 /dev/rdisk0s7 \n\n", progname);

	exit(1);
}
