[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Tool for blessing HFS+ filesystems required



On 2/3/21 4:21 PM, John Paul Adrian Glaubitz wrote:
> On 2/3/21 2:52 PM, John Paul Adrian Glaubitz wrote:
>> I quickly hacked that together now, but it's not really working yet as some size
>> calculations are incorrect:
> 
> Works:

In case someone is interested, attaching the preliminary patch.

Adrian

-- 
 .''`.  John Paul Adrian Glaubitz
: :' :  Debian Developer - glaubitz@debian.org
`. `'   Freie Universitaet Berlin - glaubitz@physik.fu-berlin.de
  `-    GPG: 62FF 8A75 84E0 2956 9546  0006 7426 3B37 F5B5 F913

diff --git a/newfs_hfs.tproj/makehfs.c b/newfs_hfs.tproj/makehfs.c
index 7315dbc..e4abc56 100644
--- a/newfs_hfs.tproj/makehfs.c
+++ b/newfs_hfs.tproj/makehfs.c
@@ -84,7 +84,7 @@ extern Boolean _CFStringGetFileSystemRepresentation(CFStringRef string, UInt8 *b
 #include "hfs_endian.h"
 
 #include "newfs_hfs.h"
-
+#include "readme.h"
 
 #define HFS_BOOT_DATA	"/usr/share/misc/hfsbootdata"
 
@@ -105,15 +105,23 @@ struct filefork {
 
 struct filefork	gDTDBFork, gSystemFork, gReadMeFork;
 
+static void WriteMDB __P((const DriveInfo *driveInfo, HFS_MDB *mdbp));
+static void InitMDB __P((hfsparams_t *defaults, UInt32 driveBlocks, HFS_MDB *mdbp));
+
 static void WriteVH __P((const DriveInfo *driveInfo, HFSPlusVolumeHeader *hp));
 static void InitVH __P((hfsparams_t *defaults, UInt64 sectors,
 		HFSPlusVolumeHeader *header));
 
+static void WriteBitmap __P((const DriveInfo *dip, UInt32 startingSector,
+        UInt32 alBlksUsed, UInt8 *buffer));
+
 static void AllocateExtent(UInt8 *buffer, UInt32 startBlock, UInt32 blockCount);
 
 static void WriteExtentsFile __P((const DriveInfo *dip, UInt64 startingSector,
         const hfsparams_t *dp, HFSExtentDescriptor *bbextp, void *buffer,
         UInt32 *bytesUsed, UInt32 *mapNodes));
+static void InitExtentsRoot __P((UInt16 btNodeSize, HFSExtentDescriptor *bbextp,
+		void *buffer));
 
 static void WriteAttributesFile(const DriveInfo *driveInfo, UInt64 startingSector,
         const hfsparams_t *dp, HFSExtentDescriptor *bbextp, void *buffer,
@@ -126,7 +134,18 @@ static int  WriteJournalInfo(const DriveInfo *driveInfo, UInt64 startingSector,
 			     const hfsparams_t *dp, HFSPlusVolumeHeader *header,
 			     void *buffer);
 static void InitCatalogRoot_HFSPlus __P((const hfsparams_t *dp, const HFSPlusVolumeHeader *header, void * buffer));
-
+static void InitCatalogRoot_HFS __P((const hfsparams_t *dp, void * buffer));
+static void InitFirstCatalogLeaf __P((const hfsparams_t *dp, void * buffer,
+		int wrapper));
+static void InitSecondCatalogLeaf __P((const hfsparams_t *dp, void *buffer));
+
+static void WriteDesktopDB(const hfsparams_t *dp, const DriveInfo *driveInfo,
+        UInt32 startingSector, void *buffer, UInt32 *mapNodes);
+
+static void WriteSystemFile __P((const DriveInfo *dip, UInt32 startingSector,
+		UInt32 *filesize));
+static void WriteReadMeFile __P((const DriveInfo *dip, UInt32 startingSector,
+		UInt32 *filesize));
 static void WriteMapNodes __P((const DriveInfo *driveInfo, UInt64 diskStart,
 		UInt32 firstMapNode, UInt32 mapNodes, UInt16 btNodeSize, void *buffer));
 static void WriteBuffer __P((const DriveInfo *driveInfo, UInt64 startingSector,
@@ -142,6 +161,8 @@ static UInt32 GetDefaultEncoding();
 
 static UInt32 UTCToLocal __P((UInt32 utcTime));
 
+static UInt32 DivideAndRoundUp __P((UInt32 numerator, UInt32 denominator));
+
 static int ConvertUTF8toUnicode __P((const UInt8* source, size_t bufsize,
 		UniChar* unibuf, UInt16 *charcount));
 
@@ -203,6 +224,146 @@ dowipefs(int fd)
 }
 #endif
 
+/*
+ * make_hfs
+ *	
+ * This routine writes an initial HFS volume structure onto a volume.
+ * It is assumed that the disk has already been formatted and verified.
+ * 
+ * For information on the HFS volume format see "Data Organization on Volumes"
+ * in "Inside Macintosh: Files" (p. 2-52).
+ * 
+ */
+int
+make_hfs(const DriveInfo *driveInfo,
+	  hfsparams_t *defaults,
+	  UInt32 *plusSectors,
+	  UInt32 *plusOffset)
+{
+	UInt32 sector;
+	UInt32 diskBlocksUsed;
+	UInt32 mapNodes;
+	UInt32 sectorsPerBlock;
+	void *nodeBuffer = NULL;
+	HFS_MDB	*mdbp = NULL;
+	UInt32 bytesUsed;
+	
+	*plusSectors = 0;
+	*plusOffset = 0;
+
+	/* assume sectorSize <= blockSize */
+	sectorsPerBlock = defaults->blockSize / driveInfo->sectorSize;
+	
+
+	/*--- CREATE A MASTER DIRECTORY BLOCK:  */
+
+	mdbp = (HFS_MDB*)malloc((size_t)kBytesPerSector);
+	nodeBuffer = malloc(8192);  /* max bitmap bytes is 8192 bytes */
+	if (nodeBuffer == NULL || mdbp == NULL) 
+		err(1, NULL);
+
+	defaults->encodingHint = getencodinghint(defaults->volumeName);
+
+	/* MDB Initialized in native byte order */
+	InitMDB(defaults, driveInfo->totalSectors, mdbp);
+
+	
+	/*--- ZERO OUT BEGINNING OF DISK (bitmap and b-trees):  */
+	
+	diskBlocksUsed = (mdbp->drAlBlSt + 1) +
+		(mdbp->drXTFlSize + mdbp->drCTFlSize) / kBytesPerSector;
+	if (defaults->flags & kMakeHFSWrapper) {
+		diskBlocksUsed += MAX(kDTDB_Size, mdbp->drAlBlkSiz) / kBytesPerSector;
+		diskBlocksUsed += MAX(sizeof(hfswrap_readme), mdbp->drAlBlkSiz) / kBytesPerSector;
+		diskBlocksUsed += MAX(24 * 1024, mdbp->drAlBlkSiz) / kBytesPerSector;
+	}
+	WriteBuffer(driveInfo, 0, diskBlocksUsed * kBytesPerSector, NULL);
+	/* also clear out last 8 sectors (4K) */
+	WriteBuffer(driveInfo, driveInfo->totalSectors - 8, 4 * 1024, NULL);
+
+	/* If this is a wrapper, add boot files... */
+	if (defaults->flags & kMakeHFSWrapper) {
+
+		sector = mdbp->drAlBlSt + 
+			 mdbp->drXTFlSize/kBytesPerSector +
+			 mdbp->drCTFlSize/kBytesPerSector;
+
+		WriteDesktopDB(defaults, driveInfo, sector, nodeBuffer, &mapNodes);
+
+		if (mapNodes > 0)
+			WriteMapNodes(driveInfo, (sector + 1), 1, mapNodes, kHFSNodeSize, nodeBuffer);
+
+		gDTDBFork.logicalSize = MAX(kDTDB_Size, mdbp->drAlBlkSiz);
+		gDTDBFork.startBlock = (sector - mdbp->drAlBlSt) / sectorsPerBlock;
+		gDTDBFork.blockCount = BYTESTOBLKS(gDTDBFork.logicalSize, mdbp->drAlBlkSiz);
+		gDTDBFork.physicalSize = gDTDBFork.blockCount * mdbp->drAlBlkSiz;
+
+		sector += gDTDBFork.physicalSize / kBytesPerSector;
+
+		WriteReadMeFile(driveInfo, sector, &gReadMeFork.logicalSize);
+		gReadMeFork.startBlock = gDTDBFork.startBlock + gDTDBFork.blockCount;
+		gReadMeFork.blockCount = BYTESTOBLKS(gReadMeFork.logicalSize, mdbp->drAlBlkSiz);
+		gReadMeFork.physicalSize = gReadMeFork.blockCount * mdbp->drAlBlkSiz;
+
+		sector += gReadMeFork.physicalSize / kBytesPerSector;
+
+		WriteSystemFile(driveInfo, sector, &gSystemFork.logicalSize);
+		gSystemFork.startBlock = gReadMeFork.startBlock + gReadMeFork.blockCount;
+		gSystemFork.blockCount = BYTESTOBLKS(gSystemFork.logicalSize, mdbp->drAlBlkSiz);
+		gSystemFork.physicalSize = gSystemFork.blockCount * mdbp->drAlBlkSiz;
+
+		mdbp->drFreeBks -= gDTDBFork.blockCount + gReadMeFork.blockCount + gSystemFork.blockCount;
+		mdbp->drEmbedExtent.startBlock = mdbp->drNmAlBlks - (UInt16)mdbp->drFreeBks;
+		mdbp->drEmbedExtent.blockCount = (UInt16)mdbp->drFreeBks;
+		mdbp->drFreeBks = 0;
+	}
+
+
+	/*--- WRITE ALLOCATION BITMAP TO DISK:  */
+
+	WriteBitmap(driveInfo, mdbp->drVBMSt, mdbp->drNmAlBlks - (UInt16)mdbp->drFreeBks, nodeBuffer);
+
+
+	/*--- WRITE FILE EXTENTS B*-TREE TO DISK:  */
+
+	sector = mdbp->drAlBlSt;	/* reset */
+	WriteExtentsFile(driveInfo, sector, defaults, &mdbp->drEmbedExtent, nodeBuffer, &bytesUsed, &mapNodes);
+
+	if (mapNodes > 0)
+		WriteMapNodes(driveInfo, (sector + bytesUsed/kBytesPerSector),
+			bytesUsed/kHFSNodeSize, mapNodes, kHFSNodeSize, nodeBuffer);
+
+	sector += (mdbp->drXTFlSize/kBytesPerSector);
+
+
+	/*--- WRITE CATALOG B*-TREE TO DISK:  */
+
+	WriteCatalogFile(driveInfo, sector, defaults, NULL, nodeBuffer, &bytesUsed, &mapNodes);
+
+	if (mapNodes > 0)
+		WriteMapNodes(driveInfo, (sector + bytesUsed/kBytesPerSector),
+			bytesUsed/kHFSNodeSize, mapNodes, kHFSNodeSize, nodeBuffer);
+
+
+	/*--- WRITE MASTER DIRECTORY BLOCK TO DISK:  */
+	
+	*plusSectors = mdbp->drEmbedExtent.blockCount *
+		(mdbp->drAlBlkSiz / driveInfo->sectorSize);
+	*plusOffset = mdbp->drAlBlSt + mdbp->drEmbedExtent.startBlock *
+		(mdbp->drAlBlkSiz / driveInfo->sectorSize);
+
+	/* write mdb last in case we fail along the way */
+
+	/* Writes both copies of the MDB */
+	WriteMDB (driveInfo, mdbp);
+	/* MDB is now big-endian */
+
+	free(nodeBuffer);		
+	free(mdbp);	
+
+	return (0);
+}
+
 /*
  * make_hfsplus
  *	
@@ -384,6 +545,150 @@ make_hfsplus(const DriveInfo *driveInfo, hfsparams_t *defaults)
 	return (0);
 }
 
+
+/*
+ * WriteMDB
+ *
+ * Writes the Master Directory Block (MDB) to disk.
+ *
+ * The MDB is byte-swapped if necessary to big endian. Since this
+ * is always the last operation, there's no point in unswapping it.
+ */
+static void
+WriteMDB (const DriveInfo *driveInfo, HFS_MDB *mdbp)
+{
+	SWAP_HFSMDB (mdbp);
+
+	WriteBuffer(driveInfo, kMDBStart, kBytesPerSector, mdbp);
+	WriteBuffer(driveInfo, driveInfo->totalSectors - 2, kBytesPerSector, mdbp);
+}
+
+
+/*
+ * InitMDB
+ *
+ * Initialize a Master Directory Block (MDB) record.
+ * 
+ * If the alignment parameter is non-zero, it indicates the aligment
+ * (in 512 byte sectors) that should be used for allocation blocks.
+ * For example, if alignment is 8, then allocation blocks will begin
+ * on a 4K boundary relative to the start of the partition.
+ *
+ */
+static void
+InitMDB(hfsparams_t *defaults, UInt32 driveBlocks, HFS_MDB *mdbp)
+{
+	UInt32	alBlkSize;
+	UInt16	numAlBlks;
+	UInt32	timeStamp;
+	UInt16	bitmapBlocks;
+	UInt32	alignment;
+	VolumeUUID	newVolumeUUID;	
+	VolumeUUID*	finderInfoUUIDPtr;
+
+	alignment = defaults->hfsAlignment;
+	bzero(mdbp, kBytesPerSector);
+	
+	alBlkSize = defaults->blockSize;
+
+	/* calculate the number of sectors needed for bitmap (rounded up) */
+	if (defaults->flags & kMakeMaxHFSBitmap)
+		bitmapBlocks = kHFSMaxAllocationBlks / kBitsPerSector;
+	else
+		bitmapBlocks = ((driveBlocks / (alBlkSize >> kLog2SectorSize)) +
+				kBitsPerSector-1) / kBitsPerSector;
+
+	mdbp->drAlBlSt = kVolBitMapStart + bitmapBlocks;  /* in sectors (disk blocks) */
+
+	/* If requested, round up block start to a multiple of "alignment" blocks */
+	if (alignment != 0)
+		mdbp->drAlBlSt = ((mdbp->drAlBlSt + alignment - 1) / alignment) * alignment;
+	
+	/* Now find out how many whole allocation blocks remain... */
+	numAlBlks = (driveBlocks - mdbp->drAlBlSt - kTailBlocks) /
+			(alBlkSize >> kLog2SectorSize);
+
+	timeStamp = UTCToLocal(defaults->createDate);
+	
+	mdbp->drSigWord = kHFSSigWord;
+	mdbp->drCrDate = timeStamp;
+	mdbp->drLsMod = timeStamp;
+	mdbp->drAtrb = kHFSVolumeUnmountedMask;
+	mdbp->drVBMSt = kVolBitMapStart;
+	mdbp->drNmAlBlks = numAlBlks;
+	mdbp->drAlBlkSiz = alBlkSize;
+	mdbp->drClpSiz = defaults->dataClumpSize;
+	mdbp->drNxtCNID = defaults->nextFreeFileID;
+	mdbp->drFreeBks = numAlBlks;
+
+	/*
+	 * Map UTF-8 input into a Mac encoding.
+	 * On conversion errors "untitled" is used as a fallback.
+	 */
+#if !LINUX
+	{
+		UniChar unibuf[kHFSMaxVolumeNameChars];
+		CFStringRef cfstr;
+		CFIndex maxchars;
+		Boolean cfOK;
+	
+		cfstr = CFStringCreateWithCString(kCFAllocatorDefault, (char *)defaults->volumeName, kCFStringEncodingUTF8);
+
+		/* Find out what Mac encoding to use: */
+		maxchars = MIN(sizeof(unibuf)/sizeof(UniChar), CFStringGetLength(cfstr));
+		CFStringGetCharacters(cfstr, CFRangeMake(0, maxchars), unibuf);
+		cfOK = CFStringGetPascalString(cfstr, mdbp->drVN, sizeof(mdbp->drVN), defaults->encodingHint);
+		CFRelease(cfstr);
+
+		if (!cfOK) {
+			mdbp->drVN[0] = strlen(kDefaultVolumeNameStr);
+			bcopy(kDefaultVolumeNameStr, &mdbp->drVN[1], mdbp->drVN[0]);
+			defaults->encodingHint = 0;
+			warnx("invalid HFS name: \"%s\", using \"%s\" instead",
+			      defaults->volumeName, kDefaultVolumeNameStr);
+		}
+		/* defaults->volumeName is used later for the root dir key */
+		bcopy(&mdbp->drVN[1], defaults->volumeName, mdbp->drVN[0]);
+		defaults->volumeName[mdbp->drVN[0]] = '\0';
+	}
+#endif
+	/* Save the encoding hint in the Finder Info (field 4). */
+	mdbp->drFndrInfo[4] = SET_HFS_TEXT_ENCODING(defaults->encodingHint);
+
+	mdbp->drWrCnt = kWriteSeqNum;
+
+	mdbp->drXTFlSize = mdbp->drXTClpSiz = defaults->extentsClumpSize;
+	mdbp->drXTExtRec[0].startBlock = 0;
+	mdbp->drXTExtRec[0].blockCount = mdbp->drXTFlSize / alBlkSize;
+	mdbp->drFreeBks -= mdbp->drXTExtRec[0].blockCount;
+
+	mdbp->drCTFlSize = mdbp->drCTClpSiz = defaults->catalogClumpSize;
+	mdbp->drCTExtRec[0].startBlock = mdbp->drXTExtRec[0].startBlock +
+					 mdbp->drXTExtRec[0].blockCount;
+	mdbp->drCTExtRec[0].blockCount = mdbp->drCTFlSize / alBlkSize;
+	mdbp->drFreeBks -= mdbp->drCTExtRec[0].blockCount;
+
+	if (defaults->flags & kMakeHFSWrapper) {
+		mdbp->drFilCnt = mdbp->drNmFls = kWapperFileCount;
+		mdbp->drNxtCNID += kWapperFileCount;
+
+		/* set blessed system folder to be root folder (2) */
+		mdbp->drFndrInfo[0] = kHFSRootFolderID;
+
+		mdbp->drEmbedSigWord = kHFSPlusSigWord;
+
+		/* software lock it and tag as having "bad" blocks */
+		mdbp->drAtrb |= kHFSVolumeSparedBlocksMask;
+		mdbp->drAtrb |= kHFSVolumeSoftwareLockMask;
+	}
+	/* Generate and write UUID for the HFS disk */
+	GenerateVolumeUUID(&newVolumeUUID);
+	finderInfoUUIDPtr = (VolumeUUID *)(&mdbp->drFndrInfo[6]);
+	finderInfoUUIDPtr->v.high = OSSwapHostLongToBig(newVolumeUUID.v.high); 
+	finderInfoUUIDPtr->v.low = OSSwapHostLongToBig(newVolumeUUID.v.low); 
+}
+
+
 /*
  * WriteVH
  *
@@ -579,6 +884,41 @@ InitVH(hfsparams_t *defaults, UInt64 sectors, HFSPlusVolumeHeader *hp)
 }
 
 
+/*
+ * InitBitmap
+ * 	
+ * This routine initializes the Allocation Bitmap. Allocation blocks
+ * that are in use have their corresponding bit set.
+ * 
+ * It assumes that initially there are no gaps between allocated blocks.
+ * 
+ * It also assumes the buffer is big enough to hold all the bits
+ * (ie its at least (alBlksUsed/8) bytes in size.
+ */
+static void
+WriteBitmap(const DriveInfo *driveInfo, UInt32 startingSector,
+        UInt32 alBlksUsed, UInt8 *buffer)
+{
+	UInt32	bytes, bits, bytesUsed;
+
+	bytes = alBlksUsed >> 3;
+	bits  = alBlksUsed & 0x0007;
+
+	(void)memset(buffer, 0xFF, bytes);
+
+	if (bits) {
+		*(UInt8 *)(buffer + bytes) = (0xFF00 >> bits) & 0xFF;
+		++bytes;
+	}
+
+	bytesUsed = ROUNDUP(bytes, driveInfo->sectorSize);
+
+	if (bytesUsed > bytes)
+		bzero(buffer + bytes, bytesUsed - bytes);
+	WriteBuffer(driveInfo, startingSector, bytesUsed, buffer);
+}
+
+
 /*
  * AllocateExtent
  *
@@ -634,6 +974,7 @@ WriteExtentsFile(const DriveInfo *driveInfo, UInt64 startingSector,
 	UInt32			nodeSize;
 	UInt32			temp;
 	SInt16			offset;
+	int			wrapper = (dp->flags & kMakeHFSWrapper);
 
 	*mapNodes = 0;
 	fileSize = dp->extentsClumpSize;
@@ -663,8 +1004,22 @@ WriteExtentsFile(const DriveInfo *driveInfo, UInt64 startingSector,
 	bthp->freeNodes		= SWAP_BE32 (SWAP_BE32 (bthp->totalNodes) - 1);  /* header */
 	bthp->clumpSize		= SWAP_BE32 (fileSize);
 
-	bthp->attributes |= SWAP_BE32 (kBTBigKeysMask);
-	bthp->maxKeyLength = SWAP_BE16 (kHFSPlusExtentKeyMaximumLength);
+	if (dp->flags & kMakeStandardHFS) {
+		bthp->maxKeyLength = SWAP_BE16 (kHFSExtentKeyMaximumLength);
+
+		/* wrapper has a bad-block extent record */
+		if (wrapper) {
+			bthp->treeDepth     = SWAP_BE16 (SWAP_BE16 (bthp->treeDepth) + 1);
+			bthp->leafRecords   = SWAP_BE32 (SWAP_BE32 (bthp->leafRecords) + 1);
+			bthp->rootNode      = SWAP_BE32 (1);
+			bthp->firstLeafNode = SWAP_BE32 (1);
+			bthp->lastLeafNode  = SWAP_BE32 (1);
+			bthp->freeNodes     = SWAP_BE32 (SWAP_BE32 (bthp->freeNodes) - 1);
+		}
+	} else {
+		bthp->attributes |= SWAP_BE32 (kBTBigKeysMask);
+		bthp->maxKeyLength = SWAP_BE16 (kHFSPlusExtentKeyMaximumLength);
+	}
 	offset += sizeof(BTHeaderRec);
 
 	SETOFFSET(buffer, nodeSize, offset, 2);
@@ -708,12 +1063,55 @@ WriteExtentsFile(const DriveInfo *driveInfo, UInt64 startingSector,
 	offset += nodeBitsInHeader/8;
 
 	SETOFFSET(buffer, nodeSize, offset, 4);
-	
+
+	if (wrapper) {
+		InitExtentsRoot(nodeSize, bbextp, (buffer + nodeSize));
+	}
+
 	*bytesUsed = (SWAP_BE32 (bthp->totalNodes) - SWAP_BE32 (bthp->freeNodes) - *mapNodes) * nodeSize;
 
 	WriteBuffer(driveInfo, startingSector, *bytesUsed, buffer);
 }
 
+static void
+InitExtentsRoot(UInt16 btNodeSize, HFSExtentDescriptor *bbextp, void *buffer)
+{
+	BTNodeDescriptor	*ndp;
+	HFSExtentKey		*ekp;
+	HFSExtentRecord		*edp;
+	SInt16				offset;
+
+	bzero(buffer, btNodeSize);
+
+	/*
+	 * All nodes have a node descriptor...
+	 */
+	ndp = (BTNodeDescriptor *)buffer;
+	ndp->kind			= kBTLeafNode;
+	ndp->numRecords		= SWAP_BE16 (1);
+	ndp->height			= 1;
+	offset = sizeof(BTNodeDescriptor);
+
+	SETOFFSET(buffer, btNodeSize, offset, 1);
+
+	/*
+	 * First and only record is bad block extents...
+	 */
+	ekp = (HFSExtentKey *)((UInt8 *) buffer + offset);
+	ekp->keyLength		= kHFSExtentKeyMaximumLength;
+	// ekp->forkType	= 0;
+	ekp->fileID			= SWAP_BE32 (kHFSBadBlockFileID);
+	// ekp->startBlock	= 0;
+	offset += sizeof(HFSExtentKey);
+
+	edp = (HFSExtentRecord *)((UInt8 *)buffer + offset);
+	edp[0]->startBlock	= SWAP_BE16 (bbextp->startBlock);
+	edp[0]->blockCount	= SWAP_BE16 (bbextp->blockCount);
+	offset += sizeof(HFSExtentRecord);
+
+	SETOFFSET(buffer, btNodeSize, offset, 2);
+}
+
 /*
  * WriteAttributesFile
  *
@@ -936,6 +1334,7 @@ WriteCatalogFile(const DriveInfo *driveInfo, UInt64 startingSector,
 	UInt32			nodeSize;
 	UInt32			temp;
 	SInt16			offset;
+	int			wrapper = (dp->flags & kMakeHFSWrapper);
 
 	*mapNodes = 0;
 	fileSize = dp->catalogClumpSize;
@@ -965,14 +1364,25 @@ WriteCatalogFile(const DriveInfo *driveInfo, UInt64 startingSector,
 	bthp->freeNodes		= SWAP_BE32 (SWAP_BE32 (bthp->totalNodes) - 2);  /* header and root */
 	bthp->clumpSize		= SWAP_BE32 (fileSize);
 
+	if (dp->flags & kMakeStandardHFS) {
+		bthp->maxKeyLength	=  SWAP_BE16 (kHFSCatalogKeyMaximumLength);
+
+		if (dp->flags & kMakeHFSWrapper) {
+			bthp->treeDepth		= SWAP_BE16 (SWAP_BE16 (bthp->treeDepth) + 1);
+			bthp->leafRecords	= SWAP_BE32 (SWAP_BE32 (bthp->leafRecords) + kWapperFileCount);
+			bthp->firstLeafNode	= SWAP_BE32 (SWAP_BE32 (bthp->rootNode) + 1);
+			bthp->lastLeafNode	= SWAP_BE32 (SWAP_BE32 (bthp->firstLeafNode) + 1);
+			bthp->freeNodes		= SWAP_BE32 (SWAP_BE32 (bthp->freeNodes) - 2);  /* tree now split with 2 leaf nodes */
+		}
+	} else /* HFS+ */ {
+		bthp->attributes	|= SWAP_BE32 (kBTVariableIndexKeysMask + kBTBigKeysMask);
+		bthp->maxKeyLength	=  SWAP_BE16 (kHFSPlusCatalogKeyMaximumLength);
+		if (dp->flags & kMakeCaseSensitive)
+			bthp->keyCompareType = kHFSBinaryCompare;
+		else
+			bthp->keyCompareType = kHFSCaseFolding;
+	}
 
-	bthp->attributes	|= SWAP_BE32 (kBTVariableIndexKeysMask + kBTBigKeysMask);
-	bthp->maxKeyLength	=  SWAP_BE16 (kHFSPlusCatalogKeyMaximumLength);
-	if (dp->flags & kMakeCaseSensitive)
-		bthp->keyCompareType = kHFSBinaryCompare;
-	else
-		bthp->keyCompareType = kHFSCaseFolding;
-	
 	offset += sizeof(BTHeaderRec);
 
 	SETOFFSET(buffer, nodeSize, offset, 2);
@@ -1015,7 +1425,17 @@ WriteCatalogFile(const DriveInfo *driveInfo, UInt64 startingSector,
 
 	SETOFFSET(buffer, nodeSize, offset, 4);
 
-	InitCatalogRoot_HFSPlus(dp, header, buffer + nodeSize);
+	if ((dp->flags & kMakeStandardHFS) == 0) {
+		InitCatalogRoot_HFSPlus(dp, header, buffer + nodeSize);
+
+	} else if (wrapper) {
+		InitCatalogRoot_HFS  (dp, buffer + (1 * nodeSize));
+		InitFirstCatalogLeaf (dp, buffer + (2 * nodeSize), TRUE);
+		InitSecondCatalogLeaf(dp, buffer + (3 * nodeSize));
+
+	} else /* plain HFS */ {
+		InitFirstCatalogLeaf(dp, buffer + nodeSize, FALSE);
+	}
 
 	*bytesUsed = (SWAP_BE32 (bthp->totalNodes) - SWAP_BE32 (bthp->freeNodes) - *mapNodes) * nodeSize;
 
@@ -1235,6 +1655,445 @@ InitCatalogRoot_HFSPlus(const hfsparams_t *dp, const HFSPlusVolumeHeader *header
 	}
 }
 
+
+static void
+InitFirstCatalogLeaf(const hfsparams_t *dp, void * buffer, int wrapper)
+{
+	BTNodeDescriptor	*ndp;
+	HFSCatalogKey		*ckp;
+	HFSCatalogKey		*tkp;
+	HFSCatalogFolder	*cdp;
+	HFSCatalogFile		*cfp;
+	HFSCatalogThread	*ctp;
+	UInt16			nodeSize;
+	SInt16			offset;
+	UInt32			timeStamp;
+
+	nodeSize = dp->catalogNodeSize;
+	timeStamp = UTCToLocal(dp->createDate);
+	bzero(buffer, nodeSize);
+
+	/*
+	 * All nodes have a node descriptor...
+	 */
+	ndp = (BTNodeDescriptor *)buffer;
+	ndp->kind			= kBTLeafNode;
+	ndp->numRecords		= SWAP_BE16 (2);
+	ndp->height			= 1;
+	offset = sizeof(BTNodeDescriptor);
+
+	SETOFFSET(buffer, nodeSize, offset, 1);
+
+	/*
+	 * First record is always the root directory...
+	 */
+	ckp = (HFSCatalogKey *)((UInt8 *)buffer + offset);
+	ckp->nodeName[0]	= strlen((char *)dp->volumeName);
+	bcopy(dp->volumeName, &ckp->nodeName[1], ckp->nodeName[0]);
+	ckp->keyLength		= 1 + 4 + ((ckp->nodeName[0] + 2) & 0xFE);  /* pad to word */
+	ckp->parentID		= SWAP_BE32 (kHFSRootParentID);
+	offset += ckp->keyLength + 1;
+
+	cdp = (HFSCatalogFolder *)((UInt8 *)buffer + offset);
+	cdp->recordType		= SWAP_BE16 (kHFSFolderRecord);
+	if (wrapper)
+		cdp->valence	= SWAP_BE16 (SWAP_BE16 (cdp->valence) + kWapperFileCount);
+	cdp->folderID		= SWAP_BE32 (kHFSRootFolderID);
+	cdp->createDate		= SWAP_BE32 (timeStamp);
+	cdp->modifyDate		= SWAP_BE32 (timeStamp);
+	offset += sizeof(HFSCatalogFolder);
+
+	SETOFFSET(buffer, nodeSize, offset, 2);
+
+	/*
+	 * Second record is always the root directory thread...
+	 */
+	tkp = (HFSCatalogKey *)((UInt8 *)buffer + offset);
+	tkp->keyLength		= kHFSCatalogKeyMinimumLength;
+	tkp->parentID		= SWAP_BE32 (kHFSRootFolderID);
+	// tkp->nodeName[0] = 0;
+	offset += tkp->keyLength + 2;
+
+	ctp = (HFSCatalogThread *)((UInt8 *)buffer + offset);
+	ctp->recordType		= SWAP_BE16 (kHFSFolderThreadRecord);
+	ctp->parentID		= SWAP_BE32 (kHFSRootParentID);
+	bcopy(ckp->nodeName, ctp->nodeName, ckp->nodeName[0]+1);
+	offset += sizeof(HFSCatalogThread);
+
+	SETOFFSET(buffer, nodeSize, offset, 3);
+	
+	/*
+	 * For Wrapper volumes there are more file records...
+	 */
+	if (wrapper) {
+		ndp->fLink				= SWAP_BE32 (3);
+		ndp->numRecords			= SWAP_BE16 (SWAP_BE16 (ndp->numRecords) + 2);
+
+		/*
+		 * Add "Desktop DB" file...
+		 */
+		ckp = (HFSCatalogKey *)((UInt8 *)buffer + offset);
+		ckp->keyLength			= 1 + 4 + ((kDTDB_Chars + 2) & 0xFE);  /* pad to word */
+		ckp->parentID			= SWAP_BE32 (kHFSRootFolderID);
+		ckp->nodeName[0]		= kDTDB_Chars;
+		bcopy(kDTDB_Name, &ckp->nodeName[1], kDTDB_Chars);
+		offset += ckp->keyLength + 1;
+
+		cfp = (HFSCatalogFile *)((UInt8 *)buffer + offset);
+		cfp->recordType			= SWAP_BE16 (kHFSFileRecord);
+		cfp->userInfo.fdType	= SWAP_BE32 (kDTDB_Type);
+		cfp->userInfo.fdCreator	= SWAP_BE32 (kDTDB_Creator);
+		cfp->userInfo.fdFlags	= SWAP_BE16 (kIsInvisible);
+		cfp->fileID				= SWAP_BE32 (kDTDB_FileID);
+		cfp->createDate			= SWAP_BE32 (timeStamp);
+		cfp->modifyDate			= SWAP_BE32 (timeStamp);
+		cfp->dataExtents[0].startBlock = SWAP_BE16 (gDTDBFork.startBlock);
+		cfp->dataExtents[0].blockCount = SWAP_BE16 (gDTDBFork.blockCount);
+		cfp->dataPhysicalSize	= SWAP_BE32 (gDTDBFork.physicalSize);
+		cfp->dataLogicalSize	= SWAP_BE32 (gDTDBFork.logicalSize);
+		offset += sizeof(HFSCatalogFile);
+
+		SETOFFSET(buffer, nodeSize, offset, 4);
+
+		/*
+		 * Add empty "Desktop DF" file...
+		 */
+		ckp = (HFSCatalogKey *)((UInt8 *)buffer + offset);
+		ckp->keyLength			= 1 + 4 + ((kDTDF_Chars + 2) & 0xFE);  /* pad to word */
+		ckp->parentID			= SWAP_BE32 (kHFSRootFolderID);
+		ckp->nodeName[0]		= kDTDF_Chars;
+		bcopy(kDTDF_Name, &ckp->nodeName[1], kDTDF_Chars);
+		offset += ckp->keyLength + 1;
+
+		cfp = (HFSCatalogFile *)((UInt8 *)buffer + offset);
+		cfp->recordType			= SWAP_BE16 (kHFSFileRecord);
+		cfp->userInfo.fdType	= SWAP_BE32 (kDTDF_Type);
+		cfp->userInfo.fdCreator	= SWAP_BE32 (kDTDF_Creator);
+		cfp->userInfo.fdFlags	= SWAP_BE16 (kIsInvisible);
+		cfp->fileID				= SWAP_BE32 (kDTDF_FileID);
+		cfp->createDate			= SWAP_BE32 (timeStamp);
+		cfp->modifyDate			= SWAP_BE32 (timeStamp);
+		offset += sizeof(HFSCatalogFile);
+
+		SETOFFSET(buffer, nodeSize, offset, 5);
+	}
+}
+
+
+static void
+InitSecondCatalogLeaf(const hfsparams_t *dp, void * buffer)
+{
+	BTNodeDescriptor	*ndp;
+	HFSCatalogKey		*ckp;
+	HFSCatalogFile		*cfp;
+	UInt16			nodeSize;
+	SInt16			offset;
+	UInt32			timeStamp;
+
+	nodeSize = dp->catalogNodeSize;
+	timeStamp = UTCToLocal(dp->createDate);
+	bzero(buffer, nodeSize);
+
+	/*
+	 * All nodes have a node descriptor...
+	 */
+	ndp = (BTNodeDescriptor *)buffer;
+	ndp->bLink		= SWAP_BE32 (2);
+	ndp->kind		= kBTLeafNode;
+	ndp->numRecords	= SWAP_BE16 (3);
+	ndp->height		= 1;
+	offset = sizeof(BTNodeDescriptor);
+
+	SETOFFSET(buffer, nodeSize, offset, 1);
+
+	/*
+	 * Add "Finder" file...
+	 */
+	ckp = (HFSCatalogKey *)((UInt8 *)buffer + offset);
+	ckp->keyLength			= 1 + 4 + ((kFinder_Chars + 2) & 0xFE);  /* pad to word */
+	ckp->parentID			= SWAP_BE32 (kHFSRootFolderID);
+	ckp->nodeName[0]		= kFinder_Chars;
+	bcopy(kFinder_Name, &ckp->nodeName[1], kFinder_Chars);
+	offset += ckp->keyLength + 1;
+
+	cfp = (HFSCatalogFile *)((UInt8 *)buffer + offset);
+	cfp->recordType			= SWAP_BE16 (kHFSFileRecord);
+	cfp->userInfo.fdType	= SWAP_BE32 (kFinder_Type);
+	cfp->userInfo.fdCreator	= SWAP_BE32 (kFinder_Creator);
+	cfp->userInfo.fdFlags	= SWAP_BE16 (kIsInvisible + kNameLocked + kHasBeenInited);
+	cfp->fileID				= SWAP_BE32 (kFinder_FileID);
+	cfp->createDate			= SWAP_BE32 (timeStamp);
+	cfp->modifyDate			= SWAP_BE32 (timeStamp);
+	offset += sizeof(HFSCatalogFile);
+
+	SETOFFSET(buffer, nodeSize, offset, 2);
+
+	/*
+	 * Add "ReadMe" file...
+	 */
+	ckp = (HFSCatalogKey *)((UInt8 *)buffer + offset);
+	ckp->keyLength			= 1 + 4 + ((kReadMe_Chars + 2) & 0xFE);  /* pad to word */
+	ckp->parentID			= SWAP_BE32 (kHFSRootFolderID);
+	ckp->nodeName[0]		= kReadMe_Chars;
+	bcopy(kReadMe_Name, &ckp->nodeName[1], kReadMe_Chars);
+	offset += ckp->keyLength + 1;
+
+	cfp = (HFSCatalogFile *)((UInt8 *)buffer + offset);
+	cfp->recordType			= SWAP_BE16 (kHFSFileRecord);
+	cfp->userInfo.fdType	= SWAP_BE32 (kReadMe_Type);
+	cfp->userInfo.fdCreator	= SWAP_BE32 (kReadMe_Creator);
+	cfp->fileID				= SWAP_BE32 (kReadMe_FileID);
+	cfp->createDate			= SWAP_BE32 (timeStamp);
+	cfp->modifyDate			= SWAP_BE32 (timeStamp);
+	cfp->dataExtents[0].startBlock = SWAP_BE16 (gReadMeFork.startBlock);
+	cfp->dataExtents[0].blockCount = SWAP_BE16 (gReadMeFork.blockCount);
+	cfp->dataPhysicalSize	= SWAP_BE32 (gReadMeFork.physicalSize);
+	cfp->dataLogicalSize	= SWAP_BE32 (gReadMeFork.logicalSize);
+	offset += sizeof(HFSCatalogFile);
+
+	SETOFFSET(buffer, nodeSize, offset, 3);
+
+	/*
+	 * Add "System" file...
+	 */
+	ckp = (HFSCatalogKey *)((UInt8 *)buffer + offset);
+	ckp->keyLength			= 1 + 4 + ((kSystem_Chars + 2) & 0xFE);  /* pad to word */
+	ckp->parentID			= SWAP_BE32 (kHFSRootFolderID);
+	ckp->nodeName[0]		= kSystem_Chars;
+	bcopy(kSystem_Name, &ckp->nodeName[1], kSystem_Chars);
+	offset += ckp->keyLength + 1;
+
+	cfp = (HFSCatalogFile *)((UInt8 *)buffer + offset);
+	cfp->recordType			= SWAP_BE16 (kHFSFileRecord);
+	cfp->userInfo.fdType	= SWAP_BE32 (kSystem_Type);
+	cfp->userInfo.fdCreator	= SWAP_BE32 (kSystem_Creator);
+	cfp->userInfo.fdFlags	= SWAP_BE16 (kIsInvisible + kNameLocked + kHasBeenInited);
+	cfp->fileID				= SWAP_BE32 (kSystem_FileID);
+	cfp->createDate			= SWAP_BE32 (timeStamp);
+	cfp->modifyDate			= SWAP_BE32 (timeStamp);
+	cfp->rsrcExtents[0].startBlock = SWAP_BE16 (gSystemFork.startBlock);
+	cfp->rsrcExtents[0].blockCount = SWAP_BE16 (gSystemFork.blockCount);
+	cfp->rsrcPhysicalSize	= SWAP_BE32 (gSystemFork.physicalSize);
+	cfp->rsrcLogicalSize	= SWAP_BE32 (gSystemFork.logicalSize);
+	offset += sizeof(HFSCatalogFile);
+
+	SETOFFSET(buffer, nodeSize, offset, 4);
+}
+
+
+static void
+InitCatalogRoot_HFS(const hfsparams_t *dp, void * buffer)
+{
+	BTNodeDescriptor	*ndp;
+	HFSCatalogKey		*ckp;
+	UInt32				*prp;	/* pointer record */
+	UInt16				nodeSize;
+	SInt16				offset;
+
+	nodeSize = dp->catalogNodeSize;
+	bzero(buffer, nodeSize);
+
+	/*
+	 * All nodes have a node descriptor...
+	 */
+	ndp = (BTNodeDescriptor *)buffer;
+	ndp->kind		= kBTIndexNode;
+	ndp->numRecords	= SWAP_BE16 (2);
+	ndp->height		= 2;
+	offset = sizeof(BTNodeDescriptor);
+
+	SETOFFSET(buffer, nodeSize, offset, 1);
+
+	/*
+	 * Add root directory index...
+	 */
+	ckp = (HFSCatalogKey *)((UInt8 *)buffer + offset);
+	ckp->keyLength		= kHFSCatalogKeyMaximumLength;
+	ckp->parentID		= SWAP_BE32 (kHFSRootParentID);
+	ckp->nodeName[0]	= strlen((char *)dp->volumeName);
+	bcopy(dp->volumeName, &ckp->nodeName[1], ckp->nodeName[0]);
+	offset += ckp->keyLength + 1;
+
+	prp = (UInt32 *)((UInt8 *)buffer + offset);
+	*prp				= SWAP_BE32 (2);	/* point to first leaf node */
+	offset += sizeof(UInt32);
+
+	SETOFFSET(buffer, nodeSize, offset, 2);
+
+	/*
+	 * Add finder file index...
+	 */
+	ckp = (HFSCatalogKey *)((UInt8 *)buffer + offset);
+	ckp->keyLength		= kHFSCatalogKeyMaximumLength;
+	ckp->parentID		= SWAP_BE32 (kHFSRootFolderID);
+	ckp->nodeName[0]	= kFinder_Chars;
+	bcopy(kFinder_Name, &ckp->nodeName[1], kFinder_Chars);
+	offset += ckp->keyLength + 1;
+
+	prp = (UInt32 *)((UInt8 *)buffer + offset);
+	*prp				= SWAP_BE32 (3);	/* point to last leaf node */
+	offset += sizeof(UInt32);
+
+	SETOFFSET(buffer, nodeSize, offset, 3);
+}
+
+
+static void
+WriteDesktopDB(const hfsparams_t *dp, const DriveInfo *driveInfo,
+               UInt32 startingSector, void *buffer, UInt32 *mapNodes)
+{
+	BTNodeDescriptor *ndp;
+	BTHeaderRec	*bthp;
+	UInt8		*bmp;
+	UInt32		nodeBitsInHeader;
+	UInt32		fileSize;
+	UInt32		nodeSize;
+	UInt32		temp;
+	SInt16		offset;
+	UInt8		*keyDiscP;
+
+	*mapNodes = 0;
+	fileSize = gDTDBFork.logicalSize;
+	nodeSize = kHFSNodeSize;
+
+	bzero(buffer, nodeSize);
+
+	/* FILL IN THE NODE DESCRIPTOR:  */
+	ndp = (BTNodeDescriptor *)buffer;
+	ndp->kind		= kBTHeaderNode;
+	ndp->numRecords	= SWAP_BE16 (3);
+	offset = sizeof(BTNodeDescriptor);
+
+	SETOFFSET(buffer, nodeSize, offset, 1);
+
+	/* FILL IN THE HEADER RECORD:  */
+	bthp = (BTHeaderRec *)((UInt8 *)buffer + offset);
+    //	bthp->treeDepth		= 0;
+    //	bthp->rootNode		= 0;
+    //	bthp->firstLeafNode	= 0;
+    //	bthp->lastLeafNode	= 0;
+    //	bthp->leafRecords	= 0;
+	bthp->nodeSize		= SWAP_BE16 (nodeSize);
+	bthp->maxKeyLength	= SWAP_BE16 (37);
+	bthp->totalNodes	= SWAP_BE32 (fileSize / nodeSize);
+	bthp->freeNodes		= SWAP_BE32 (SWAP_BE32 (bthp->totalNodes) - 1);  /* header */
+	bthp->clumpSize		= SWAP_BE32 (fileSize);
+	bthp->btreeType		= 0xFF;
+	offset += sizeof(BTHeaderRec);
+
+	SETOFFSET(buffer, nodeSize, offset, 2);
+	
+	keyDiscP = (UInt8 *)((UInt8 *)buffer + offset);
+	*keyDiscP++ = 2;			/* length of descriptor */
+	*keyDiscP++ = KD_USEPROC;	/* always uses a compare proc */
+	*keyDiscP++ = 1;			/* just one of them */
+	offset += kBTreeHeaderUserBytes;
+
+	SETOFFSET(buffer, nodeSize, offset, 3);
+
+	/* FIGURE OUT HOW MANY MAP NODES (IF ANY):  */
+	nodeBitsInHeader = 8 * (nodeSize
+					- sizeof(BTNodeDescriptor)
+					- sizeof(BTHeaderRec)
+					- kBTreeHeaderUserBytes
+					- (4 * sizeof(SInt16)) );
+
+	if (SWAP_BE32 (bthp->totalNodes) > nodeBitsInHeader) {
+		UInt32	nodeBitsInMapNode;
+		
+		ndp->fLink = SWAP_BE32 (SWAP_BE32 (bthp->lastLeafNode) + 1);
+		nodeBitsInMapNode = 8 * (nodeSize
+						- sizeof(BTNodeDescriptor)
+						- (2 * sizeof(SInt16))
+						- 2 );
+		*mapNodes = (SWAP_BE32 (bthp->totalNodes) - nodeBitsInHeader +
+		            (nodeBitsInMapNode - 1)) / nodeBitsInMapNode;
+		bthp->freeNodes = SWAP_BE32 (SWAP_BE32 (bthp->freeNodes) - *mapNodes);
+	}
+
+	/* 
+	 * FILL IN THE MAP RECORD, MARKING NODES THAT ARE IN USE.
+	 * Note - worst case (32MB alloc blk) will have only 18 nodes in use.
+	 */
+	bmp = ((UInt8 *)buffer + offset);
+	temp = SWAP_BE32 (bthp->totalNodes) - SWAP_BE32 (bthp->freeNodes);
+
+	/* Working a byte at a time is endian safe */
+	while (temp >= 8) { *bmp = 0xFF; temp -= 8; bmp++; }
+	*bmp = ~(0xFF >> temp);
+	offset += nodeBitsInHeader/8;
+
+	SETOFFSET(buffer, nodeSize, offset, 4);
+
+	WriteBuffer(driveInfo, startingSector, kHFSNodeSize, buffer);
+}
+
+
+static void
+WriteSystemFile(const DriveInfo *dip, UInt32 startingSector, UInt32 *filesize)
+{
+	int fd;
+	ssize_t	datasize, writesize;
+	UInt8 *buf;
+	struct stat stbuf;
+	
+	if (stat(HFS_BOOT_DATA, &stbuf) < 0)
+		err(1, "stat %s", HFS_BOOT_DATA);
+	
+	datasize = stbuf.st_size;
+	writesize = ROUNDUP(datasize, dip->sectorSize);
+
+	if (datasize > (64 * 1024))
+		errx(1, "hfsbootdata file too big.");
+
+	if ((buf = malloc(writesize)) == NULL)
+		err(1, NULL);
+	
+	if ((fd = open(HFS_BOOT_DATA, O_RDONLY, 0)) < 0)
+		err(1, "open %s", HFS_BOOT_DATA);
+	
+	if (read(fd, buf, datasize) != datasize) {
+		if (errno)
+			err(1, "read %s", HFS_BOOT_DATA);
+		else
+			errx(1, "problems reading %s", HFS_BOOT_DATA);
+	}
+
+	if (writesize > datasize)
+		bzero(buf + datasize, writesize - datasize);
+
+	WriteBuffer(dip, startingSector, writesize, buf);
+	
+	close(fd);
+	
+	free(buf);
+	
+	*filesize = datasize;
+}
+
+
+static void
+WriteReadMeFile(const DriveInfo *dip, UInt32 startingSector, UInt32 *filesize)
+{
+	ssize_t	datasize, writesize;
+	UInt8 *buf;
+	
+	datasize = sizeof(hfswrap_readme);
+	writesize = ROUNDUP(datasize, dip->sectorSize);
+
+	if ((buf = malloc(writesize)) == NULL)
+		err(1, NULL);
+	
+	bcopy(hfswrap_readme, buf, datasize);
+	if (writesize > datasize)
+		bzero(buf + datasize, writesize - datasize);
+	
+	WriteBuffer(dip, startingSector, writesize, buf);
+	
+	*filesize = datasize;
+}
+
+
 /*
  * WriteMapNodes
  *	
@@ -1457,6 +2316,20 @@ static UInt32 UTCToLocal(UInt32 utcTime)
         return (localTime);
 }
 
+
+static UInt32
+DivideAndRoundUp(UInt32 numerator, UInt32 denominator)
+{
+	UInt32	quotient;
+	
+	quotient = numerator / denominator;
+	if (quotient * denominator != numerator)
+		quotient++;
+	
+	return quotient;
+}
+
+
 #if !LINUX
 #define __kCFUserEncodingFileName ("/.CFUserTextEncoding")
 
diff --git a/newfs_hfs.tproj/newfs_hfs.c b/newfs_hfs.tproj/newfs_hfs.c
index 88eb1b3..34ac386 100644
--- a/newfs_hfs.tproj/newfs_hfs.c
+++ b/newfs_hfs.tproj/newfs_hfs.c
@@ -82,10 +82,12 @@ 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 int hfs_newfs __P((char *device, int forceHFS));
 static void validate_hfsplus_block_size __P((UInt64 sectorCount, UInt32 sectorSize));
 static void hfsplus_params __P((const DriveInfo* dip, hfsparams_t *defaults));
+static void hfs_params __P((const DriveInfo* dip, hfsparams_t *defaults));
 static UInt32 clumpsizecalc __P((UInt32 clumpblocks));
+static UInt32 CalcBTreeClumpSize __P((UInt32 blockSize, UInt32 nodeSize, UInt32 driveBlocks, int catalog));
 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);
@@ -103,6 +105,7 @@ UInt32	gNextCNID = kHFSFirstUserCatalogNodeID;
 time_t  createtime;
 
 int	gNoCreate = FALSE;
+int	gWrapper = FALSE;
 int	gUserCatNodeSize = FALSE;
 int	gCaseSensitive = FALSE;
 int	gUserAttrSize = FALSE;
@@ -179,6 +182,7 @@ main(argc, argv)
 	extern char *optarg;
 	extern int optind;
 	int ch;
+	int forceHFS;
 #if !LINUX
 	char *cp, *special;
 	struct statfs *mp;
@@ -190,8 +194,9 @@ main(argc, argv)
 	else
 		progname = *argv;
 
+	forceHFS = FALSE;
 
-	while ((ch = getopt(argc, argv, "G:J:D:M:N:PU:hsb:c:i:n:v:")) != EOF)
+	while ((ch = getopt(argc, argv, "G:J:D:M:N:PU:hswb:c:i:n:v:")) != EOF)
 		switch (ch) {
 		case 'G':
 			gGroupID = a_gid(optarg);
@@ -255,6 +260,10 @@ main(argc, argv)
 			getclumpopts(optarg);
 			break;
 
+		case 'h':
+			forceHFS = TRUE;
+			break;
+
 		case 'i':
 			gNextCNID = atoi(optarg);
 			/*
@@ -286,6 +295,10 @@ main(argc, argv)
 #endif
 			break;
 
+		case 'w':
+			gWrapper = TRUE;
+			break;
+
 		case '?':
 		default:
 			usage();
@@ -338,7 +351,25 @@ main(argc, argv)
 		}
 #endif
 	}
-	if (hfs_newfs(blkdevice) < 0) {
+
+	if (forceHFS && gJournaled) {
+		fprintf(stderr, "-h -J: incompatible options specified\n");
+		usage();
+	}
+	if (gCaseSensitive && (forceHFS || gWrapper)) {
+		fprintf(stderr, "-s: incompatible options specified\n");
+		usage();
+	}
+	if (gWrapper && forceHFS) {
+		fprintf(stderr, "-h -w: incompatible options specified\n");
+		usage();
+	}
+	if (!gWrapper && hfsgrowblks) {
+		fprintf(stderr, "g clump option requires -w option\n");
+		exit(1);
+	}
+
+	if (hfs_newfs(blkdevice, forceHFS) < 0) {
 #if LINUX
 		err(1, "cannot create filesystem on %s", blkdevice);
 #else
@@ -657,7 +688,7 @@ static void validate_hfsplus_block_size(UInt64 sectorCount, UInt32 sectorSize)
 
 
 static int
-hfs_newfs(char *device)
+hfs_newfs(char *device, int forceHFS)
 {
 	struct stat stbuf;
 	DriveInfo dip = { 0 };
@@ -776,35 +807,58 @@ hfs_newfs(char *device)
 	 * 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);
+	if (!forceHFS)
+		validate_hfsplus_block_size(dip.totalSectors, dip.sectorSize);
+
+	/* Make an HFS disk */
+	if (forceHFS || gWrapper) {
+		hfs_params(&dip, &defaults);
+		if (gNoCreate == 0) {
+			UInt32 totalSectors, sectorOffset;
+
+			retval = make_hfs(&dip, &defaults, &totalSectors, &sectorOffset);
+			if (retval)
+				fatal("%s: %s", device, strerror(errno));
+
+			if (gWrapper) {
+				dip.totalSectors = totalSectors;
+				dip.sectorOffset = sectorOffset;
+			} else {
+				printf("Initialized %s as a %ld MB HFS volume\n",
+					device, (long)(dip.totalSectors/2048));
+			}
+		}
+	}
 
 	/* Make an HFS Plus disk */	
+	if (gWrapper || !forceHFS) {
 
-	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 ((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");
+			}
 		}
 	}
 
@@ -1081,6 +1135,89 @@ static void hfsplus_params (const DriveInfo* dip, hfsparams_t *defaults)
 }
 
 
+static void hfs_params(const DriveInfo* dip, hfsparams_t *defaults)
+{
+	UInt64  sectorCount = dip->totalSectors;
+	UInt32  sectorSize = dip->sectorSize;
+	UInt32	alBlkSize;
+	UInt32	vSectorCount;
+	UInt32	defaultBlockSize;
+
+	defaults->flags = kMakeStandardHFS;
+	defaults->nextFreeFileID = gNextCNID;
+	defaults->createDate = createtime + MAC_GMT_FACTOR;     /* Mac OS GMT time */
+	defaults->catalogNodeSize = kHFSNodeSize;
+	defaults->extentsNodeSize = kHFSNodeSize;
+	defaults->attributesNodeSize = 0;
+	defaults->attributesClumpSize = 0;
+
+	strncpy((char *)defaults->volumeName, gVolumeName, sizeof(defaults->volumeName) - 1);
+	defaults->volumeName[sizeof(defaults->volumeName) - 1] = '\0';
+
+	/* Compute the default allocation block size */
+	if (gWrapper && hfsgrowblks) {
+		defaults->flags |= kMakeMaxHFSBitmap;
+		vSectorCount = ((UInt64)hfsgrowblks * 512) / sectorSize;
+		defaultBlockSize = sectorSize * ((vSectorCount >> 16) + 1);
+	} else
+		defaultBlockSize = sectorSize * ((sectorCount >> 16) + 1);
+
+	if (gWrapper) {
+		defaults->flags |= kMakeHFSWrapper;
+
+		/* round alBlkSize up to multiple of HFS Plus blockSize */
+		alBlkSize = ((defaultBlockSize + gBlockSize - 1) / gBlockSize) * gBlockSize;
+
+		if (gBlockSize > 4096)
+			defaults->hfsAlignment = 4096 / sectorSize;		/* Align to 4K boundary */
+		else
+			defaults->hfsAlignment = gBlockSize / sectorSize;	/* Align to blockSize boundary */
+	} else {
+		/* If allocation block size is undefined or invalid calculate itÉ*/
+		alBlkSize = gBlockSize;
+		defaults->hfsAlignment = 0;
+	}
+
+	if ( alBlkSize == 0 || (alBlkSize & 0x1FF) != 0 || alBlkSize < defaultBlockSize)
+		alBlkSize = defaultBlockSize;
+
+	defaults->blockSize = alBlkSize;
+
+	defaults->dataClumpSize = alBlkSize * 4;
+	defaults->rsrcClumpSize = alBlkSize * 4;
+	if ( gWrapper || defaults->dataClumpSize > 0x100000 )
+		defaults->dataClumpSize = alBlkSize;
+
+	if (gWrapper) {
+		if (alBlkSize == kHFSNodeSize) {
+			defaults->extentsClumpSize = (2 * kHFSNodeSize); /* header + root/leaf */
+			defaults->catalogClumpSize = (4 * kHFSNodeSize); /* header + root + 2 leaves */
+		} else {
+			defaults->extentsClumpSize = alBlkSize;
+			defaults->catalogClumpSize = alBlkSize;
+		}
+	} else {
+		defaults->catalogClumpSize = CalcBTreeClumpSize(alBlkSize, sectorSize, sectorCount, TRUE);
+		defaults->extentsClumpSize = CalcBTreeClumpSize(alBlkSize, sectorSize, sectorCount, FALSE);
+	}
+
+	if (gNoCreate) {
+		printf("%lld sectors at %ld bytes per sector\n", dip->physTotalSectors, dip->physSectorSize);
+		printf("%s format parameters:\n", gWrapper ? "HFS Wrapper" : "HFS");
+		printf("\tvolume name: \"%s\"\n", gVolumeName);
+		printf("\tblock-size: %ld\n", defaults->blockSize);
+		printf("\ttotal blocks: %lld\n", sectorCount / (alBlkSize / sectorSize) );
+		printf("\tfirst free catalog node id: %ld\n", defaults->nextFreeFileID);
+		printf("\tinitial catalog file size: %ld\n", defaults->catalogClumpSize);
+		printf("\tinitial extents file size: %ld\n", defaults->extentsClumpSize);
+		printf("\tfile clump size: %ld\n", defaults->dataClumpSize);
+		/* hfsgrowblks is in terms of 512-byte sectors */
+		if (hfsgrowblks)
+			printf("\twrapper growable from %lld to %ld sectors\n", dip->physTotalSectors, hfsgrowblks * kBytesPerSector / dip->physSectorSize);
+	}
+}
+
+
 static UInt32
 clumpsizecalc(UInt32 clumpblocks)
 {
@@ -1095,6 +1232,97 @@ clumpsizecalc(UInt32 clumpblocks)
 }
 
 
+/*
+ * CalcBTreeClumpSize
+ *
+ * This routine calculates the file clump size for both the catalog and
+ * extents overflow files. In general, this is 1/128 the size of the
+ * volume up to a maximum of 6 MB.  For really large HFS volumes it will
+ * be just 1 allocation block.
+ */
+static UInt32
+CalcBTreeClumpSize(UInt32 blockSize, UInt32 nodeSize, UInt32 driveBlocks, int catalog)
+{
+	UInt32	clumpSectors;
+	UInt32	maximumClumpSectors;
+	UInt32	sectorsPerBlock = blockSize >> kLog2SectorSize;
+	UInt32	sectorsPerNode = nodeSize >> kLog2SectorSize;
+	UInt32	nodeBitsInHeader;
+	UInt32	limitClumpSectors;
+
+	if (catalog)
+		limitClumpSectors = 6 * 1024 * 1024 / 512;	/* overall limit of 6MB */
+	else
+		limitClumpSectors = 4 * 1024 * 1024 / 512;	/* overall limit of 4MB */
+	/*
+	 * For small node sizes (eg., HFS, or default HFS Plus extents), then the clump size will
+	 * be as big as the header's map record can handle.  (That is, as big as possible, without
+	 * requiring a map node.)
+	 *
+	 * But for a 32K node size, this works out to nearly 8GB.  We need to restrict it further.
+	 * To avoid arithmetic overflow, we'll calculate things in terms of 512-byte sectors.
+	 */
+	nodeBitsInHeader = 8 * (nodeSize - sizeof(BTNodeDescriptor)
+					- sizeof(BTHeaderRec)
+					- kBTreeHeaderUserBytes
+					- (4 * sizeof(SInt16)));
+	maximumClumpSectors = nodeBitsInHeader * sectorsPerNode;
+
+	if ( maximumClumpSectors > limitClumpSectors )
+		maximumClumpSectors = limitClumpSectors;
+
+	/*
+	 * For very large HFS volumes, the allocation block size might be larger than the arbitrary limit
+	 * we set above.  Since we have to allocate at least one allocation block, then use that as the
+	 * clump size.
+	 *
+	 * Otherwise, we want to use about 1/128 of the volume, again subject to the above limit.
+	 * To avoid arithmetic overflow, we continue to work with sectors.
+	 *
+	 * But for very small volumes (less than 64K), we'll just use 4 allocation blocks.  And that
+	 * will typically be 2KB.
+	 */
+	if ( sectorsPerBlock >= maximumClumpSectors )
+	{
+		clumpSectors = sectorsPerBlock;		/* for really large volumes just use one allocation block (HFS only) */
+	}
+	else
+	{
+		/*
+		 * For large volumes, the default is 1/128 of the volume size, up to the maximumClumpSize
+		 */
+		if ( driveBlocks > 128 )
+		{
+			clumpSectors = (driveBlocks / 128);	/* the default is 1/128 of the volume size */
+
+			if (clumpSectors > maximumClumpSectors)
+				clumpSectors = maximumClumpSectors;
+		}
+		else
+		{
+			clumpSectors = sectorsPerBlock * 4;	/* for really small volumes (ie < 64K) */
+		}
+	}
+
+	/*
+	 * And we need to round up to something that is a multiple of both the node size and the allocation block size
+	 * so that it will occupy a whole number of allocation blocks, and so a whole number of nodes will fit.
+	 *
+	 * For HFS, the node size is always 512, and the allocation block size is always a multiple of 512.  For HFS
+	 * Plus, both the node size and allocation block size are powers of 2.  So, it suffices to round up to whichever
+	 * value is larger (since the larger value is always a multiple of the smaller value).
+	 */
+
+	if ( sectorsPerNode > sectorsPerBlock )
+		clumpSectors = (clumpSectors / sectorsPerNode) * sectorsPerNode;	/* truncate to nearest node*/
+	else
+		clumpSectors = (clumpSectors / sectorsPerBlock) * sectorsPerBlock;	/* truncate to nearest node and allocation block */
+
+	/* Finally, convert the clump size to bytes. */
+	return clumpSectors << kLog2SectorSize;
+}
+
+
 #define CLUMP_ENTRIES	15
 
 short clumptbl[CLUMP_ENTRIES * 3] = {
@@ -1247,8 +1475,10 @@ void usage()
 	fprintf(stderr, "usage: %s [-N [partition-size]] [hfsplus-options] special-device\n", progname);
 
 	fprintf(stderr, "  options:\n");
+	fprintf(stderr, "\t-h create an HFS format filesystem (HFS Plus is the default)\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, "\t-w add a HFS wrapper (i.e. Native Mac OS 9 bootable)\n");
 
 	fprintf(stderr, "  where hfsplus-options are:\n");
 	fprintf(stderr, "\t-J [journal-size] make this HFS+ volume journaled\n");

Reply to: