]> git.saurik.com Git - apple/hfs.git/blobdiff - core/FileExtentMapping.c
hfs-522.100.5.tar.gz
[apple/hfs.git] / core / FileExtentMapping.c
diff --git a/core/FileExtentMapping.c b/core/FileExtentMapping.c
new file mode 100644 (file)
index 0000000..1ea93f9
--- /dev/null
@@ -0,0 +1,2249 @@
+/*
+ * Copyright (c) 2000-2015 Apple Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_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. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ * 
+ * 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_OSREFERENCE_LICENSE_HEADER_END@
+ */
+
+
+#include "hfs.h"
+#include "hfs_format.h"
+#include "hfs_endian.h"
+
+#include "FileMgrInternal.h"
+#include "BTreesInternal.h"
+
+#include <sys/malloc.h>
+/*
+============================================================
+Public (Exported) Routines:
+============================================================
+
+       ExtendFileC             Allocate more space to a given file.
+
+       CompareExtentKeys
+                                       Compare two extents file keys (a search key and a trial
+                                       key).  Used by the BTree manager when searching for,
+                                       adding, or deleting keys in the extents file of an HFS
+                                       volume.
+                                       
+       CompareExtentKeysPlus
+                                       Compare two extents file keys (a search key and a trial
+                                       key).  Used by the BTree manager when searching for,
+                                       adding, or deleting keys in the extents file of an HFS+
+                                       volume.
+                                       
+       MapFileBlockC   Convert (map) an offset within a given file into a
+                                       physical disk address.
+                                       
+       TruncateFileC   Truncates the disk space allocated to a file.  The file
+                                       space is truncated to a specified new physical EOF, rounded
+                                       up to the next allocation block boundry.  There is an option
+                                       to truncate to the end of the extent containing the new EOF.
+       
+       FlushExtentFile
+                                       Flush the extents file for a given volume.
+
+       SearchExtentFile
+                                       Search the FCB and extents file for an extent record that
+                                       contains a given file position (in bytes).
+
+
+============================================================
+Internal Routines:
+============================================================
+       FindExtentRecord
+                                       Search the extents BTree for a particular extent record.
+       SearchExtentRecord
+                                       Search a given extent record to see if it contains a given
+                                       file position (in bytes).  Used by SearchExtentFile.
+       ReleaseExtents
+                                       Deallocate all allocation blocks in all extents of an extent
+                                       data record.
+       TruncateExtents
+                                       Deallocate blocks and delete extent records for all allocation
+                                       blocks beyond a certain point in a file.  The starting point
+                                       must be the first file allocation block for some extent record
+                                       for the file.
+       DeallocateFork
+                                       Deallocate all allocation blocks belonging to a given fork.
+       UpdateExtentRecord
+                                       If the extent record came from the extents file, write out
+                                       the updated record; otherwise, copy the updated record into
+                                       the FCB resident extent record.  If the record has no extents,
+                                       and was in the extents file, then delete the record instead.
+*/
+
+#if CONFIG_HFS_STD
+static const int64_t kTwoGigabytes = 0x80000000LL;
+#endif
+
+enum
+{
+       kDataForkType                   = 0,
+       kResourceForkType               = 0xFF,
+       
+       kPreviousRecord                 = -1
+};
+
+
+#if CONFIG_HFS_STD
+static OSErr HFSPlusToHFSExtents(
+       const HFSPlusExtentRecord       oldExtents,
+       HFSExtentRecord                         newExtents);
+#endif
+
+static OSErr FindExtentRecord(
+       const ExtendedVCB               *vcb,
+       u_int8_t                                forkType,
+       u_int32_t                               fileID,
+       u_int32_t                               startBlock,
+       Boolean                                 allowPrevious,
+       HFSPlusExtentKey                *foundKey,
+       HFSPlusExtentRecord             foundData,
+       u_int32_t                               *foundHint);
+
+static OSErr DeleteExtentRecord(
+       const ExtendedVCB               *vcb,
+       u_int8_t                                forkType,
+       u_int32_t                               fileID,
+       u_int32_t                               startBlock);
+
+static OSErr CreateExtentRecord(
+       ExtendedVCB             *vcb,
+       HFSPlusExtentKey                *key,
+       HFSPlusExtentRecord             extents,
+       u_int32_t                               *hint);
+
+
+static OSErr GetFCBExtentRecord(
+       const FCB                               *fcb,
+       HFSPlusExtentRecord             extents);
+
+static OSErr SearchExtentRecord(
+       ExtendedVCB             *vcb,
+       u_int32_t                               searchFABN,
+       const HFSPlusExtentRecord       extentData,
+       u_int32_t                               extentDataStartFABN,
+       u_int32_t                               *foundExtentDataOffset,
+       u_int32_t                               *endingFABNPlusOne,
+       Boolean                                 *noMoreExtents);
+
+static OSErr ReleaseExtents(
+       ExtendedVCB                             *vcb,
+       const HFSPlusExtentRecord       extentRecord,
+       u_int32_t                               *numReleasedAllocationBlocks,
+       Boolean                                 *releasedLastExtent);
+
+static OSErr DeallocateFork(
+       ExtendedVCB             *vcb,
+       HFSCatalogNodeID        fileID,
+       u_int8_t                        forkType,
+       HFSPlusExtentRecord     catalogExtents,
+       Boolean *               recordDeleted);
+
+static OSErr TruncateExtents(
+       ExtendedVCB                     *vcb,
+       u_int8_t                        forkType,
+       u_int32_t                       fileID,
+       u_int32_t                       startBlock,
+       Boolean *                       recordDeleted);
+
+static OSErr UpdateExtentRecord (
+       ExtendedVCB             *vcb,
+       FCB                             *fcb,
+       int                             deleted,
+       const HFSPlusExtentKey  *extentFileKey,
+       const HFSPlusExtentRecord       extentData,
+       u_int32_t                                       extentBTreeHint);
+
+static Boolean ExtentsAreIntegral(
+       const HFSPlusExtentRecord extentRecord,
+       u_int32_t       mask,
+       u_int32_t       *blocksChecked,
+       Boolean         *checkedLastExtent);
+
+//_________________________________________________________________________________
+//
+//     Routine:        FindExtentRecord
+//
+//     Purpose:        Search the extents BTree for an extent record matching the given
+//                             FileID, fork, and starting file allocation block number.
+//
+//     Inputs:
+//             vcb                             Volume to search
+//             forkType                0 = data fork, -1 = resource fork
+//             fileID                  File's FileID (CatalogNodeID)
+//             startBlock              Starting file allocation block number
+//             allowPrevious   If the desired record isn't found and this flag is set,
+//                                             then see if the previous record belongs to the same fork.
+//                                             If so, then return it.
+//
+//     Outputs:
+//             foundKey        The key data for the record actually found
+//             foundData       The extent record actually found (NOTE: on an HFS volume, the
+//                                     fourth entry will be zeroes.
+//             foundHint       The BTree hint to find the node again
+//_________________________________________________________________________________
+static OSErr FindExtentRecord(
+       const ExtendedVCB       *vcb,
+       u_int8_t                        forkType,
+       u_int32_t                       fileID,
+       u_int32_t                       startBlock,
+       Boolean                         allowPrevious,
+       HFSPlusExtentKey        *foundKey,
+       HFSPlusExtentRecord     foundData,
+       u_int32_t                       *foundHint)
+{
+       FCB *                           fcb;
+       struct BTreeIterator *btIterator = NULL;
+       FSBufferDescriptor      btRecord;
+       OSErr                           err;
+       u_int16_t                       btRecordSize;
+       
+       err = noErr;
+       if (foundHint)
+               *foundHint = 0;
+       fcb = GetFileControlBlock(vcb->extentsRefNum);
+
+       btIterator = hfs_mallocz(sizeof(struct BTreeIterator));
+
+       /* HFS Plus / HFSX */
+       if (vcb->vcbSigWord != kHFSSigWord) {
+               HFSPlusExtentKey *      extentKeyPtr;
+               HFSPlusExtentRecord     extentData;
+
+               extentKeyPtr = (HFSPlusExtentKey*) &btIterator->key;
+               extentKeyPtr->keyLength  = kHFSPlusExtentKeyMaximumLength;
+               extentKeyPtr->forkType   = forkType;
+               extentKeyPtr->pad                = 0;
+               extentKeyPtr->fileID     = fileID;
+               extentKeyPtr->startBlock = startBlock;
+               
+               btRecord.bufferAddress = &extentData;
+               btRecord.itemSize = sizeof(HFSPlusExtentRecord);
+               btRecord.itemCount = 1;
+
+               err = BTSearchRecord(fcb, btIterator, &btRecord, &btRecordSize, btIterator);
+
+               if (err == btNotFound && allowPrevious) {
+                       err = BTIterateRecord(fcb, kBTreePrevRecord, btIterator, &btRecord, &btRecordSize);
+
+                       //      A previous record may not exist, so just return btNotFound (like we would if
+                       //      it was for the wrong file/fork).
+                       if (err == (OSErr) fsBTStartOfIterationErr)             //¥¥ fsBTStartOfIterationErr is type unsigned long
+                               err = btNotFound;
+
+                       if (err == noErr) {
+                               //      Found a previous record.  Does it belong to the same fork of the same file?
+                               if (extentKeyPtr->fileID != fileID || extentKeyPtr->forkType != forkType)
+                                       err = btNotFound;
+                       }
+               }
+
+               if (err == noErr) {
+                       // Copy the found key back for the caller
+                       if (foundKey)
+                               BlockMoveData(extentKeyPtr, foundKey, sizeof(HFSPlusExtentKey));
+                       // Copy the found data back for the caller
+                       BlockMoveData(&extentData, foundData, sizeof(HFSPlusExtentRecord));
+               }
+       }
+#if CONFIG_HFS_STD
+       else { 
+               HFSExtentKey *          extentKeyPtr;
+               HFSExtentRecord         extentData;
+
+               extentKeyPtr = (HFSExtentKey*) &btIterator->key;
+               extentKeyPtr->keyLength = kHFSExtentKeyMaximumLength;
+               extentKeyPtr->forkType = forkType;
+               extentKeyPtr->fileID = fileID;
+               extentKeyPtr->startBlock = startBlock;
+               
+               btRecord.bufferAddress = &extentData;
+               btRecord.itemSize = sizeof(HFSExtentRecord);
+               btRecord.itemCount = 1;
+
+               err = BTSearchRecord(fcb, btIterator, &btRecord, &btRecordSize, btIterator);
+
+               if (err == btNotFound && allowPrevious) {
+                       err = BTIterateRecord(fcb, kBTreePrevRecord, btIterator, &btRecord, &btRecordSize);
+
+                       //      A previous record may not exist, so just return btNotFound (like we would if
+                       //      it was for the wrong file/fork).
+                       if (err == (OSErr) fsBTStartOfIterationErr)             //¥¥ fsBTStartOfIterationErr is type unsigned long
+                               err = btNotFound;
+
+                       if (err == noErr) {
+                               //      Found a previous record.  Does it belong to the same fork of the same file?
+                               if (extentKeyPtr->fileID != fileID || extentKeyPtr->forkType != forkType)
+                                       err = btNotFound;
+                       }
+               }
+
+               if (err == noErr) {
+                       u_int16_t       i;
+                       
+                       // Copy the found key back for the caller
+                       if (foundKey) {
+                               foundKey->keyLength  = kHFSPlusExtentKeyMaximumLength;
+                               foundKey->forkType   = extentKeyPtr->forkType;
+                               foundKey->pad        = 0;
+                               foundKey->fileID     = extentKeyPtr->fileID;
+                               foundKey->startBlock = extentKeyPtr->startBlock;
+                       }
+                       // Copy the found data back for the caller
+                       foundData[0].startBlock = extentData[0].startBlock;
+                       foundData[0].blockCount = extentData[0].blockCount;
+                       foundData[1].startBlock = extentData[1].startBlock;
+                       foundData[1].blockCount = extentData[1].blockCount;
+                       foundData[2].startBlock = extentData[2].startBlock;
+                       foundData[2].blockCount = extentData[2].blockCount;
+                       
+                       for (i = 3; i < kHFSPlusExtentDensity; ++i)
+                       {
+                               foundData[i].startBlock = 0;
+                               foundData[i].blockCount = 0;
+                       }
+               }
+       }
+#endif
+
+       if (foundHint)
+               *foundHint = btIterator->hint.nodeNum;
+
+       hfs_free(btIterator, sizeof(*btIterator));
+       return err;
+}
+
+
+
+static OSErr CreateExtentRecord(
+       ExtendedVCB     *vcb,
+       HFSPlusExtentKey        *key,
+       HFSPlusExtentRecord     extents,
+       u_int32_t                       *hint)
+{
+       struct BTreeIterator *btIterator = NULL;
+       FSBufferDescriptor      btRecord;
+       u_int16_t  btRecordSize;
+       int  lockflags;
+       OSErr  err;
+       
+       err = noErr;
+       *hint = 0;
+
+       btIterator = hfs_mallocz(sizeof(struct BTreeIterator));
+
+       /*
+        * The lock taken by callers of ExtendFileC is speculative and
+        * only occurs when the file already has overflow extents. So
+        * We need to make sure we have the lock here.  The extents
+        * btree lock can be nested (its recursive) so we always take
+        * it here.
+        */
+       lockflags = hfs_systemfile_lock(vcb, SFL_EXTENTS, HFS_EXCLUSIVE_LOCK);
+
+       /* HFS+/HFSX */
+       if (vcb->vcbSigWord != kHFSSigWord) {
+               btRecordSize = sizeof(HFSPlusExtentRecord);
+               btRecord.bufferAddress = extents;
+               btRecord.itemSize = btRecordSize;
+               btRecord.itemCount = 1;
+
+               BlockMoveData(key, &btIterator->key, sizeof(HFSPlusExtentKey));
+       }
+#if CONFIG_HFS_STD
+       else {
+               /* HFS Standard */
+               HFSExtentKey *          keyPtr;
+               HFSExtentRecord         data;
+               
+               btRecordSize = sizeof(HFSExtentRecord);
+               btRecord.bufferAddress = &data;
+               btRecord.itemSize = btRecordSize;
+               btRecord.itemCount = 1;
+
+               keyPtr = (HFSExtentKey*) &btIterator->key;
+               keyPtr->keyLength       = kHFSExtentKeyMaximumLength;
+               keyPtr->forkType        = key->forkType;
+               keyPtr->fileID          = key->fileID;
+               keyPtr->startBlock      = key->startBlock;
+               
+               err = HFSPlusToHFSExtents(extents, data);
+       }
+#endif
+
+       if (err == noErr)
+               err = BTInsertRecord(GetFileControlBlock(vcb->extentsRefNum), btIterator, &btRecord, btRecordSize);
+
+       if (err == noErr)
+               *hint = btIterator->hint.nodeNum;
+
+       (void) BTFlushPath(GetFileControlBlock(vcb->extentsRefNum));
+       
+       hfs_systemfile_unlock(vcb, lockflags);
+
+       hfs_free(btIterator, sizeof(*btIterator));
+       return err;
+}
+
+
+static OSErr DeleteExtentRecord(
+       const ExtendedVCB       *vcb,
+       u_int8_t                        forkType,
+       u_int32_t                       fileID,
+       u_int32_t                       startBlock)
+{
+       struct BTreeIterator *btIterator = NULL;
+       OSErr                           err;
+       
+       err = noErr;
+
+       btIterator = hfs_mallocz(sizeof(struct BTreeIterator));
+
+       /* HFS+ / HFSX */
+       if (vcb->vcbSigWord != kHFSSigWord) {           //      HFS Plus volume
+               HFSPlusExtentKey *      keyPtr;
+
+               keyPtr = (HFSPlusExtentKey*) &btIterator->key;
+               keyPtr->keyLength       = kHFSPlusExtentKeyMaximumLength;
+               keyPtr->forkType        = forkType;
+               keyPtr->pad                     = 0;
+               keyPtr->fileID          = fileID;
+               keyPtr->startBlock      = startBlock;
+       }
+#if CONFIG_HFS_STD
+       else {
+               /* HFS standard */
+               HFSExtentKey *  keyPtr;
+
+               keyPtr = (HFSExtentKey*) &btIterator->key;
+               keyPtr->keyLength       = kHFSExtentKeyMaximumLength;
+               keyPtr->forkType        = forkType;
+               keyPtr->fileID          = fileID;
+               keyPtr->startBlock      = startBlock;
+       }
+#endif
+
+       err = BTDeleteRecord(GetFileControlBlock(vcb->extentsRefNum), btIterator);
+       (void) BTFlushPath(GetFileControlBlock(vcb->extentsRefNum));
+       
+
+       hfs_free(btIterator, sizeof(*btIterator));
+       return err;
+}
+
+
+
+//_________________________________________________________________________________
+//
+// Routine:            MapFileBlock
+//
+// Function:   Maps a file position into a physical disk address.
+//
+//_________________________________________________________________________________
+
+OSErr MapFileBlockC (
+       ExtendedVCB             *vcb,                           // volume that file resides on
+       FCB                             *fcb,                           // FCB of file
+       size_t                  numberOfBytes,          // number of contiguous bytes desired
+       off_t                   offset,                         // starting offset within file (in bytes)
+       daddr64_t               *startSector,           // first sector (NOT an allocation block)
+       size_t                  *availableBytes)        // number of contiguous bytes (up to numberOfBytes)
+{
+       OSErr                           err;
+       u_int32_t                       allocBlockSize;                 //      Size of the volume's allocation block
+       u_int32_t                       sectorSize;
+       HFSPlusExtentKey        foundKey;
+       HFSPlusExtentRecord     foundData;
+       u_int32_t                       foundIndex;
+       u_int32_t                       hint;
+       u_int32_t                       firstFABN = 0;                  // file allocation block of first block in found extent
+       u_int32_t                       nextFABN;                               // file allocation block of block after end of found extent
+       off_t                           dataEnd;                                // (offset) end of range that is contiguous
+       u_int32_t                       sectorsPerBlock;                // Number of sectors per allocation block
+       u_int32_t                       startBlock = 0;                 // volume allocation block corresponding to firstFABN
+       daddr64_t                       temp;
+       off_t                           tmpOff;
+
+       allocBlockSize = vcb->blockSize;
+       sectorSize = VCBTOHFS(vcb)->hfs_logical_block_size;
+
+       err = SearchExtentFile(vcb, fcb, offset, &foundKey, foundData, &foundIndex, &hint, &nextFABN);
+       if (err == noErr) {
+               startBlock = foundData[foundIndex].startBlock;
+               firstFABN = nextFABN - foundData[foundIndex].blockCount;
+       }
+       
+       if (err != noErr)
+       {
+               return err;
+       }
+
+       //
+       //      Determine the end of the available space.  It will either be the end of the extent,
+       //      or the file's PEOF, whichever is smaller.
+       //
+       dataEnd = (off_t)((off_t)(nextFABN) * (off_t)(allocBlockSize));   // Assume valid data through end of this extent
+       if (((off_t)fcb->ff_blocks * (off_t)allocBlockSize) < dataEnd)    // Is PEOF shorter?
+               dataEnd = (off_t)fcb->ff_blocks * (off_t)allocBlockSize;  // Yes, so only map up to PEOF
+       
+       //      Compute the number of sectors in an allocation block
+       sectorsPerBlock = allocBlockSize / sectorSize;  // sectors per allocation block
+       
+       //
+       //      Compute the absolute sector number that contains the offset of the given file
+       //      offset in sectors from start of the extent +
+       //      offset in sectors from start of allocation block space
+       //
+       temp = (daddr64_t)((offset - (off_t)((off_t)(firstFABN) * (off_t)(allocBlockSize)))/sectorSize);
+       temp += (daddr64_t)startBlock * (daddr64_t)sectorsPerBlock;
+
+       /* Add in any volume offsets */
+       if (vcb->vcbSigWord == kHFSPlusSigWord)
+               temp += vcb->hfsPlusIOPosOffset / sectorSize;
+       else
+               temp += vcb->vcbAlBlSt;
+       
+       //      Return the desired sector for file position "offset"
+       *startSector = temp;
+       
+       //
+       //      Determine the number of contiguous bytes until the end of the extent
+       //      (or the amount they asked for, whichever comes first).
+       //
+       if (availableBytes)
+       {
+               tmpOff = dataEnd - offset;
+               /*
+                * Disallow negative runs.
+                */
+               if (tmpOff <= 0) {
+                       /* This shouldn't happen unless something is corrupt */
+                       hfs_corruption_debug("MapFileBlockC: tmpOff <= 0 (%lld)\n", tmpOff);
+                       return EINVAL;
+               }
+
+               if (tmpOff > (off_t)(numberOfBytes)) {
+                       *availableBytes = numberOfBytes;  // more there than they asked for, so pin the output
+               }
+               else {
+                       *availableBytes = tmpOff;
+               }
+       }
+
+       return noErr;
+}
+
+
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+//     Routine:        ReleaseExtents
+//
+//     Function:       Release the extents of a single extent data record.
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+
+static OSErr ReleaseExtents(
+       ExtendedVCB                     *vcb,
+       const HFSPlusExtentRecord       extentRecord,
+       u_int32_t                               *numReleasedAllocationBlocks,
+       Boolean                                 *releasedLastExtent)
+{
+       u_int32_t       extentIndex;
+       u_int32_t       numberOfExtents;
+       OSErr   err = noErr;
+       
+       *numReleasedAllocationBlocks = 0;
+       *releasedLastExtent = false;
+       
+       if (vcb->vcbSigWord == kHFSPlusSigWord)
+               numberOfExtents = kHFSPlusExtentDensity;
+       else
+               numberOfExtents = kHFSExtentDensity;
+
+       for( extentIndex = 0; extentIndex < numberOfExtents; extentIndex++)
+       {
+               u_int32_t       numAllocationBlocks;
+               
+               // Loop over the extent record and release the blocks associated with each extent.
+               
+               numAllocationBlocks = extentRecord[extentIndex].blockCount;
+               if ( numAllocationBlocks == 0 )
+               {
+                       *releasedLastExtent = true;
+                       break;
+               }
+
+               err = BlockDeallocate( vcb, extentRecord[extentIndex].startBlock, numAllocationBlocks , 0);
+               if ( err != noErr )
+                       break;
+                                       
+               *numReleasedAllocationBlocks += numAllocationBlocks;            //      bump FABN to beg of next extent
+       }
+
+       return( err );
+}
+
+
+
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+//     Routine:        TruncateExtents
+//
+//     Purpose:        Delete extent records whose starting file allocation block number
+//                             is greater than or equal to a given starting block number.  The
+//                             allocation blocks represented by the extents are deallocated.
+//
+//     Inputs:
+//             vcb                     Volume to operate on
+//             fileID          Which file to operate on
+//             startBlock      Starting file allocation block number for first extent
+//                                     record to delete.
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+
+static OSErr TruncateExtents(
+       ExtendedVCB             *vcb,
+       u_int8_t                forkType,
+       u_int32_t               fileID,
+       u_int32_t               startBlock,
+       Boolean *               recordDeleted)
+{
+       OSErr                           err;
+       u_int32_t                       numberExtentsReleased;
+       Boolean                         releasedLastExtent;
+       u_int32_t                       hint;
+       HFSPlusExtentKey        key;
+       HFSPlusExtentRecord     extents;
+       int  lockflags;
+
+       /*
+        * The lock taken by callers of TruncateFileC is speculative and
+        * only occurs when the file already has overflow extents. So
+        * We need to make sure we have the lock here.  The extents
+        * btree lock can be nested (its recursive) so we always take
+        * it here.
+        */
+       lockflags = hfs_systemfile_lock(vcb, SFL_EXTENTS, HFS_EXCLUSIVE_LOCK);
+
+       while (true) {
+               err = FindExtentRecord(vcb, forkType, fileID, startBlock, false, &key, extents, &hint);
+               if (err != noErr) {
+                       if (err == btNotFound)
+                               err = noErr;
+                       break;
+               }
+               
+               err = ReleaseExtents( vcb, extents, &numberExtentsReleased, &releasedLastExtent );
+               if (err != noErr) break;
+               
+               err = DeleteExtentRecord(vcb, forkType, fileID, startBlock);
+               if (err != noErr) break;
+
+               *recordDeleted = true;
+               startBlock += numberExtentsReleased;
+       }
+       hfs_systemfile_unlock(vcb, lockflags);
+       
+       return err;
+}
+
+
+
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+//     Routine:        DeallocateFork
+//
+//     Function:       De-allocates all disk space allocated to a specified fork.
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+
+static OSErr DeallocateFork(
+       ExtendedVCB             *vcb,
+       HFSCatalogNodeID        fileID,
+       u_int8_t                        forkType,
+       HFSPlusExtentRecord     catalogExtents,
+       Boolean *               recordDeleted) /* true if a record was deleted */
+{
+       OSErr                           err;
+       u_int32_t                       numReleasedAllocationBlocks;
+       Boolean                         releasedLastExtent;
+       
+       //      Release the catalog extents
+       err = ReleaseExtents( vcb, catalogExtents, &numReleasedAllocationBlocks, &releasedLastExtent );
+       // Release the extra extents, if present
+       if (err == noErr && !releasedLastExtent)
+               err = TruncateExtents(vcb, forkType, fileID, numReleasedAllocationBlocks, recordDeleted);
+
+       return( err );
+}
+
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+//     Routine:        FlushExtentFile
+//
+//     Function:       Flushes the extent file for a specified volume
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+
+OSErr FlushExtentFile( ExtendedVCB *vcb )
+{
+       FCB *   fcb;
+       OSErr   err;
+       int  lockflags;
+       
+       fcb = GetFileControlBlock(vcb->extentsRefNum);
+
+       lockflags = hfs_systemfile_lock(vcb, SFL_EXTENTS, HFS_EXCLUSIVE_LOCK);
+       err = BTFlushPath(fcb);
+       hfs_systemfile_unlock(vcb, lockflags);
+
+       if ( err == noErr )
+       {
+               // If the FCB for the extent "file" is dirty, mark the VCB as dirty.
+               
+        if (FTOC(fcb)->c_flag & C_MODIFIED)
+               {
+                       MarkVCBDirty( vcb );
+               //      err = FlushVolumeControlBlock( vcb );
+               }
+       }
+       
+       return( err );
+}
+
+
+#if CONFIG_HFS_STD
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+//     Routine:        CompareExtentKeys
+//
+//     Function:       Compares two extent file keys (a search key and a trial key) for
+//                             an HFS volume.
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+
+int32_t CompareExtentKeys( const HFSExtentKey *searchKey, const HFSExtentKey *trialKey )
+{
+       int32_t result;         //      ± 1
+       
+       #if DEBUG
+               if (searchKey->keyLength != kHFSExtentKeyMaximumLength)
+                       DebugStr("HFS: search Key is wrong length");
+               if (trialKey->keyLength != kHFSExtentKeyMaximumLength)
+                       DebugStr("HFS: trial Key is wrong length");
+       #endif
+       
+       result = -1;            //      assume searchKey < trialKey
+       
+       if (searchKey->fileID == trialKey->fileID) {
+               //
+               //      FileNum's are equal; compare fork types
+               //
+               if (searchKey->forkType == trialKey->forkType) {
+                       //
+                       //      Fork types are equal; compare allocation block number
+                       //
+                       if (searchKey->startBlock == trialKey->startBlock) {
+                               //
+                               //      Everything is equal
+                               //
+                               result = 0;
+                       }
+                       else {
+                               //
+                               //      Allocation block numbers differ; determine sign
+                               //
+                               if (searchKey->startBlock > trialKey->startBlock)
+                                       result = 1;
+                       }
+               }
+               else {
+                       //
+                       //      Fork types differ; determine sign
+                       //
+                       if (searchKey->forkType > trialKey->forkType)
+                               result = 1;
+               }
+       }
+       else {
+               //
+               //      FileNums differ; determine sign
+               //
+               if (searchKey->fileID > trialKey->fileID)
+                       result = 1;
+       }
+       
+       return( result );
+}
+#endif
+
+
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+//     Routine:        CompareExtentKeysPlus
+//
+//     Function:       Compares two extent file keys (a search key and a trial key) for
+//                             an HFS volume.
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+
+int32_t CompareExtentKeysPlus( const HFSPlusExtentKey *searchKey, const HFSPlusExtentKey *trialKey )
+{
+       int32_t result;         //      ± 1
+       
+       #if DEBUG
+               if (searchKey->keyLength != kHFSPlusExtentKeyMaximumLength)
+                       DebugStr("HFS: search Key is wrong length");
+               if (trialKey->keyLength != kHFSPlusExtentKeyMaximumLength)
+                       DebugStr("HFS: trial Key is wrong length");
+       #endif
+       
+       result = -1;            //      assume searchKey < trialKey
+       
+       if (searchKey->fileID == trialKey->fileID) {
+               //
+               //      FileNum's are equal; compare fork types
+               //
+               if (searchKey->forkType == trialKey->forkType) {
+                       //
+                       //      Fork types are equal; compare allocation block number
+                       //
+                       if (searchKey->startBlock == trialKey->startBlock) {
+                               //
+                               //      Everything is equal
+                               //
+                               result = 0;
+                       }
+                       else {
+                               //
+                               //      Allocation block numbers differ; determine sign
+                               //
+                               if (searchKey->startBlock > trialKey->startBlock)
+                                       result = 1;
+                       }
+               }
+               else {
+                       //
+                       //      Fork types differ; determine sign
+                       //
+                       if (searchKey->forkType > trialKey->forkType)
+                               result = 1;
+               }
+       }
+       else {
+               //
+               //      FileNums differ; determine sign
+               //
+               if (searchKey->fileID > trialKey->fileID)
+                       result = 1;
+       }
+       
+       return( result );
+}
+
+static int
+should_pin_blocks(hfsmount_t *hfsmp, FCB *fcb)
+{
+       if (!ISSET(hfsmp->hfs_flags, HFS_CS_HOTFILE_PIN)
+               || fcb->ff_cp == NULL || fcb->ff_cp->c_vp == NULL) {
+               return 0;
+       }
+
+       int pin_blocks;
+
+       //
+       // File system metadata should get pinned
+       //
+       if (vnode_issystem(fcb->ff_cp->c_vp)) {
+               return 1;
+       }
+
+       //
+       // If a file is AutoCandidate, we should not pin its blocks because
+       // it was an automatically added file and this function is intended
+       // to pin new blocks being added to user-generated content.
+       //
+       if (fcb->ff_cp->c_attr.ca_recflags & kHFSAutoCandidateMask) {
+               return 0;
+       }
+
+       //
+       // If a file is marked FastDevPinned it is an existing pinned file 
+       // or a new file that should be pinned.
+       //
+       // If a file is marked FastDevCandidate it is a new file that is
+       // being written to for the first time so we don't want to pin it
+       // just yet as it may not meet the criteria (i.e. too large).
+       //
+       if ((fcb->ff_cp->c_attr.ca_recflags & (kHFSFastDevPinnedMask)) != 0) {
+               pin_blocks = 1;
+       } else {
+               pin_blocks = 0;
+       }
+
+       return pin_blocks;
+}
+       
+
+
+static void
+pin_blocks_if_needed(ExtendedVCB *vcb, FCB *fcb, u_int32_t startBlock, u_int32_t blockCount)   
+{
+       if (!should_pin_blocks(vcb, fcb)) {
+               return;
+       }
+       
+       // ask CoreStorage to pin the new blocks being added to this file
+       if (hfs_pin_block_range((struct hfsmount *)vcb, HFS_PIN_IT, startBlock, blockCount) == 0) {
+               struct vnode *vp = fcb->ff_cp->c_vp;
+               
+               // and make sure to keep our accounting in order
+               hfs_hotfile_adjust_blocks(vp, -blockCount);
+       }
+}
+
+
+
+/*
+ * Add a file extent to a file.
+ *
+ * Used by hfs_extendfs to extend the volume allocation bitmap file.
+ *
+ */
+int
+AddFileExtent(ExtendedVCB *vcb, FCB *fcb, u_int32_t startBlock, u_int32_t blockCount)
+{
+       HFSPlusExtentKey foundKey;
+       HFSPlusExtentRecord foundData;
+       u_int32_t foundIndex;
+       u_int32_t hint;
+       u_int32_t nextBlock;
+       int64_t peof;
+       int i;
+       int error;
+
+       peof = (int64_t)(fcb->ff_blocks + blockCount) * (int64_t)vcb->blockSize;
+
+       error = SearchExtentFile(vcb, fcb, peof-1, &foundKey, foundData, &foundIndex, &hint, &nextBlock);
+       if (error != fxRangeErr)
+               return (EBUSY);
+
+       /*
+        * Add new extent.  See if there is room in the current record.
+        */
+       if (foundData[foundIndex].blockCount != 0)
+               ++foundIndex;
+       if (foundIndex == kHFSPlusExtentDensity) {
+               /*
+                * Existing record is full so create a new one.
+                */
+               foundKey.keyLength = kHFSPlusExtentKeyMaximumLength;
+               foundKey.forkType = kDataForkType;
+               foundKey.pad = 0;
+               foundKey.fileID = FTOC(fcb)->c_fileid;
+               foundKey.startBlock = nextBlock;
+               
+               foundData[0].startBlock = startBlock;
+               foundData[0].blockCount = blockCount;
+               
+               /* zero out remaining extents. */
+               for (i = 1; i < kHFSPlusExtentDensity; ++i) {
+                       foundData[i].startBlock = 0;
+                       foundData[i].blockCount = 0;
+               }
+
+               foundIndex = 0;
+
+               error = CreateExtentRecord(vcb, &foundKey, foundData, &hint);
+               if (error == fxOvFlErr) {
+                       error = dskFulErr;
+               } else if (error == 0) {
+                       pin_blocks_if_needed(vcb, fcb, startBlock, blockCount);
+               }
+               
+       } else {
+               /* 
+                * Add a new extent into existing record.
+                */
+               foundData[foundIndex].startBlock = startBlock;
+               foundData[foundIndex].blockCount = blockCount;
+               error = UpdateExtentRecord(vcb, fcb, 0, &foundKey, foundData, hint);
+               if (error == 0) {
+                       pin_blocks_if_needed(vcb, fcb, startBlock, blockCount);
+               }
+       }
+       (void) FlushExtentFile(vcb);
+
+       return (error);
+}
+
+
+//_________________________________________________________________________________
+//
+// Routine:            Extendfile
+//
+// Function:   Extends the disk space allocated to a file.
+//
+//_________________________________________________________________________________
+
+OSErr ExtendFileC (
+       ExtendedVCB             *vcb,                           // volume that file resides on
+       FCB                             *fcb,                           // FCB of file to truncate
+       int64_t                 bytesToAdd,                     // number of bytes to allocate
+       u_int32_t               blockHint,                      // desired starting allocation block
+       u_int32_t               flags,                          // EFContig and/or EFAll
+       int64_t                 *actualBytesAdded)      // number of bytes actually allocated
+{
+       OSErr                           err;
+       u_int32_t                       volumeBlockSize;
+       int64_t                         blocksToAdd;
+       int64_t                         bytesThisExtent;
+       HFSPlusExtentKey        foundKey;
+       HFSPlusExtentRecord     foundData;
+       u_int32_t                       foundIndex;
+       u_int32_t                       hint;
+       u_int32_t                       nextBlock;
+       u_int32_t                       startBlock;
+       Boolean                         allOrNothing;
+       Boolean                         forceContig;
+       Boolean                         wantContig;
+       Boolean                         useMetaZone;
+       Boolean                         needsFlush;
+       int                                     allowFlushTxns;
+       u_int32_t                       actualStartBlock;
+       u_int32_t                       actualNumBlocks;
+       u_int32_t                       numExtentsPerRecord;
+       int64_t                         maximumBytes;
+       int64_t                         availbytes;
+       int64_t                         peof;
+       u_int32_t                       prevblocks;
+       uint32_t                        fastdev = 0;
+
+       struct hfsmount *hfsmp = (struct hfsmount*)vcb; 
+       allowFlushTxns = 0;
+       needsFlush = false;
+       *actualBytesAdded = 0;
+       volumeBlockSize = vcb->blockSize;
+       allOrNothing = ((flags & kEFAllMask) != 0);
+       forceContig = ((flags & kEFContigMask) != 0);
+       prevblocks = fcb->ff_blocks;
+
+       if (vcb->vcbSigWord != kHFSSigWord) {
+               numExtentsPerRecord = kHFSPlusExtentDensity;
+       }
+#if CONFIG_HFS_STD
+       else {
+               /* HFS Standard */
+               numExtentsPerRecord = kHFSExtentDensity;
+
+               /* Make sure the request and new PEOF are less than 2GB if HFS std*/
+               if (bytesToAdd >=  kTwoGigabytes)
+                       goto HFS_Std_Overflow;
+               if ((((int64_t)fcb->ff_blocks * (int64_t)volumeBlockSize) + bytesToAdd) >= kTwoGigabytes)
+                       goto HFS_Std_Overflow;
+       }
+#endif
+
+       //
+       //      Determine how many blocks need to be allocated.
+       //      Round up the number of desired bytes to add.
+       //
+       blocksToAdd = howmany(bytesToAdd, volumeBlockSize);
+       bytesToAdd = (int64_t)((int64_t)blocksToAdd * (int64_t)volumeBlockSize);
+
+       /*
+        * For deferred allocations just reserve the blocks.
+        */
+       if ((flags & kEFDeferMask)
+       &&  (vcb->vcbSigWord == kHFSPlusSigWord)
+       &&  (bytesToAdd < (int64_t)HFS_MAX_DEFERED_ALLOC)
+       &&  (blocksToAdd < hfs_freeblks(VCBTOHFS(vcb), 1))) {
+               hfs_lock_mount (hfsmp);
+               vcb->loanedBlocks += blocksToAdd;
+               hfs_unlock_mount(hfsmp);
+
+               fcb->ff_unallocblocks += blocksToAdd;
+               FTOC(fcb)->c_blocks   += blocksToAdd;
+               fcb->ff_blocks        += blocksToAdd;
+
+               /*
+                * We haven't touched the disk here; no blocks have been
+                * allocated and the volume will not be inconsistent if we
+                * don't update the catalog record immediately.
+                */
+               FTOC(fcb)->c_flag |= C_MINOR_MOD;
+               *actualBytesAdded = bytesToAdd;
+               return (0);
+       }
+       /* 
+        * Give back any unallocated blocks before doing real allocations.
+        */
+       if (fcb->ff_unallocblocks > 0) {
+               u_int32_t loanedBlocks;
+
+               loanedBlocks = fcb->ff_unallocblocks;
+               blocksToAdd += loanedBlocks;
+               bytesToAdd = (int64_t)blocksToAdd * (int64_t)volumeBlockSize;
+               FTOC(fcb)->c_blocks -= loanedBlocks;
+               fcb->ff_blocks -= loanedBlocks;
+               fcb->ff_unallocblocks  = 0;
+
+               hfs_lock_mount(hfsmp);
+               vcb->loanedBlocks -= loanedBlocks;
+               hfs_unlock_mount(hfsmp);
+       }
+
+       //
+       //      If the file's clump size is larger than the allocation block size,
+       //      then set the maximum number of bytes to the requested number of bytes
+       //      rounded up to a multiple of the clump size.
+       //
+       if ((vcb->vcbClpSiz > (int32_t)volumeBlockSize)
+       &&  (bytesToAdd < (int64_t)HFS_MAX_DEFERED_ALLOC)
+       &&  (flags & kEFNoClumpMask) == 0) {
+               maximumBytes = (int64_t)howmany(bytesToAdd, vcb->vcbClpSiz);
+               maximumBytes *= vcb->vcbClpSiz;
+       } else {
+               maximumBytes = bytesToAdd;
+       }
+       
+#if CONFIG_HFS_STD
+       //
+       //      Compute new physical EOF, rounded up to a multiple of a block.
+       //
+       if ( (vcb->vcbSigWord == kHFSSigWord) &&                //      Too big?
+                ((((int64_t)fcb->ff_blocks * (int64_t)volumeBlockSize) + bytesToAdd) >= kTwoGigabytes) ) {
+               if (allOrNothing)                                       // Yes, must they have it all?
+                       goto HFS_Std_Overflow;                                          // Yes, can't have it
+               else {
+                       --blocksToAdd;                                          // No, give give 'em one block less
+                       bytesToAdd -= volumeBlockSize;
+               }
+       }
+#endif
+
+       //
+       //      If allocation is all-or-nothing, make sure there are
+       //      enough free blocks on the volume (quick test).
+       //
+       if (allOrNothing &&
+           (blocksToAdd > hfs_freeblks(VCBTOHFS(vcb), flags & kEFReserveMask))) {
+               err = dskFulErr;
+               goto ErrorExit;
+       }
+       
+       //
+       //      See if there are already enough blocks allocated to the file.
+       //
+       peof = ((int64_t)fcb->ff_blocks * (int64_t)volumeBlockSize) + bytesToAdd;  // potential new PEOF
+       err = SearchExtentFile(vcb, fcb, peof-1, &foundKey, foundData, &foundIndex, &hint, &nextBlock);
+       if (err == noErr) {
+               //      Enough blocks are already allocated.  Just update the FCB to reflect the new length.
+               fcb->ff_blocks = peof / volumeBlockSize;
+               FTOC(fcb)->c_blocks += (bytesToAdd / volumeBlockSize);
+               FTOC(fcb)->c_flag |= C_MODIFIED;
+               goto Exit;
+       }
+       if (err != fxRangeErr)          // Any real error?
+               goto ErrorExit;                         // Yes, so exit immediately
+
+       //
+       //      Adjust the PEOF to the end of the last extent.
+       //
+       peof = (int64_t)((int64_t)nextBlock * (int64_t)volumeBlockSize);                        // currently allocated PEOF
+       bytesThisExtent = (int64_t)(nextBlock - fcb->ff_blocks) * (int64_t)volumeBlockSize;
+       if (bytesThisExtent != 0) {
+               fcb->ff_blocks = nextBlock;
+               FTOC(fcb)->c_blocks += (bytesThisExtent / volumeBlockSize);
+               FTOC(fcb)->c_flag |= C_MODIFIED;
+               bytesToAdd -= bytesThisExtent;
+       }
+       
+       //
+       //      Allocate some more space.
+       //
+       //      First try a contiguous allocation (of the whole amount).
+       //      If that fails, get whatever we can.
+       //              If forceContig, then take whatever we got
+       //              else, keep getting bits and pieces (non-contig)
+       
+       /*
+        * Note that for sparse devices (like sparse bundle dmgs), we
+        * should only be aggressive with re-using once-allocated pieces
+        * if we're not dealing with system files.  If we're trying to operate
+        * on behalf of a system file, we need the maximum contiguous amount
+        * possible.  For non-system files we favor locality and fragmentation over
+        * contiguity as it can result in fewer blocks being needed from the underlying
+        * filesystem that the sparse image resides upon. 
+        */
+       err = noErr;
+       if (   (vcb->hfs_flags & HFS_HAS_SPARSE_DEVICE)
+                       && (fcb->ff_cp->c_fileid >= kHFSFirstUserCatalogNodeID)
+                       && (flags & kEFMetadataMask) == 0) {
+               /*
+                * We want locality over contiguity so by default we set wantContig to 
+                * false unless we hit one of the circumstances below.
+                */ 
+               wantContig = false;
+               if (hfs_isrbtree_active(VCBTOHFS(vcb))) {
+                       /* 
+                        * If the red-black tree is acive, we can always find a suitable contiguous
+                        * chunk.  So if the user specifically requests contiguous files,  we should 
+                        * honor that no matter what kind of device it is.
+                        */
+                       if (forceContig) {
+                               wantContig = true;
+                       }
+               }
+               else {
+                       /* 
+                        * If the red-black tree is not active, then only set wantContig to true
+                        * if we have never done a contig scan on the device, which would populate
+                        * the free extent cache.  Note that the caller may explicitly unset the 
+                        * DID_CONTIG_SCAN bit in order to force us to vend a contiguous extent here
+                        * if the caller wants to get a contiguous chunk.
+                        */
+                       if ((vcb->hfs_flags & HFS_DID_CONTIG_SCAN) == 0) { 
+                               vcb->hfs_flags |= HFS_DID_CONTIG_SCAN;  
+                               wantContig = true;
+                       }
+               }
+       } 
+       else {
+               wantContig = true;
+       }
+
+       if (should_pin_blocks(hfsmp, fcb))
+               fastdev = HFS_ALLOC_FAST_DEV;
+
+       useMetaZone = flags & kEFMetadataMask;
+       do {
+               if (blockHint != 0)
+                       startBlock = blockHint;
+               else
+                       startBlock = foundData[foundIndex].startBlock + foundData[foundIndex].blockCount;
+
+               actualNumBlocks = 0;
+               actualStartBlock = 0;
+                       
+               /* Find number of free blocks based on reserved block flag option */
+               availbytes = (int64_t)hfs_freeblks(VCBTOHFS(vcb), flags & kEFReserveMask) *
+                            (int64_t)volumeBlockSize;
+               if (availbytes <= 0) {
+                       err = dskFulErr;
+               } else {
+                       if (wantContig && (availbytes < bytesToAdd)) {
+                               err = dskFulErr;
+                       }
+                       else {
+                               uint32_t ba_flags = fastdev;
+
+                               if (wantContig) {
+                                       ba_flags |= HFS_ALLOC_FORCECONTIG;      
+                               }
+                               if (useMetaZone) {
+                                       ba_flags |= HFS_ALLOC_METAZONE;
+                               }
+                               if (allowFlushTxns) {
+                                       ba_flags |= HFS_ALLOC_FLUSHTXN;
+                               }
+
+                               err = BlockAllocate(
+                                                 vcb,
+                                                 startBlock,
+                                                 howmany(MIN(bytesToAdd, availbytes), volumeBlockSize),
+                                                 howmany(MIN(maximumBytes, availbytes), volumeBlockSize),
+                                                 ba_flags,
+                                                 &actualStartBlock,
+                                                 &actualNumBlocks);
+                       }
+               }
+               if (err == dskFulErr) {
+                       if (forceContig) {
+                               if (allowFlushTxns == 0) {
+                                       /* If we're forcing contiguity, re-try but allow plucking from recently freed regions */
+                                       allowFlushTxns = 1;
+                                       wantContig = 1;
+                                       err = noErr;
+                                       continue;
+                               }
+                               else {
+                                       break;                  // AllocContig failed because not enough contiguous space
+                               }
+                       }
+                       if (wantContig) {
+                               //      Couldn't get one big chunk, so get whatever we can.
+                               err = noErr;
+                               wantContig = false;
+                               continue;
+                       }
+                       if (actualNumBlocks != 0)
+                               err = noErr;
+
+                       if (useMetaZone == 0) {
+                               /* Couldn't get anything so dip into metadat zone */
+                               err = noErr;
+                               useMetaZone = 1;
+                               continue;
+                       }
+
+                       /* If we couldn't find what we needed without flushing the journal, then go ahead and do it now */
+                       if (allowFlushTxns == 0) {
+                               allowFlushTxns = 1;
+                               err = noErr;
+                               continue;
+                       }
+
+               }
+               if (err == noErr) {
+                       //      Add the new extent to the existing extent record, or create a new one.
+                       if ((actualStartBlock == startBlock) && (blockHint == 0)) {
+                               //      We grew the file's last extent, so just adjust the number of blocks.
+                               foundData[foundIndex].blockCount += actualNumBlocks;
+                               err = UpdateExtentRecord(vcb, fcb, 0, &foundKey, foundData, hint);
+                               if (err != noErr) break;
+                       }
+                       else {
+                               u_int16_t       i;
+
+                               //      Need to add a new extent.  See if there is room in the current record.
+                               if (foundData[foundIndex].blockCount != 0)      //      Is current extent free to use?
+                                       ++foundIndex;                                                   //      No, so use the next one.
+                               if (foundIndex == numExtentsPerRecord) {
+                                       //      This record is full.  Need to create a new one.
+                                       if (FTOC(fcb)->c_fileid == kHFSExtentsFileID) {
+                                               (void) BlockDeallocate(vcb, actualStartBlock, actualNumBlocks, 0);
+                                               err = dskFulErr;                // Oops.  Can't extend extents file past first record.
+                                               break;
+                                       }
+                                       
+                                       foundKey.keyLength = kHFSPlusExtentKeyMaximumLength;
+                                       if (FORK_IS_RSRC(fcb))
+                                               foundKey.forkType = kResourceForkType;
+                                       else
+                                               foundKey.forkType = kDataForkType;
+                                       foundKey.pad = 0;
+                                       foundKey.fileID = FTOC(fcb)->c_fileid;
+                                       foundKey.startBlock = nextBlock;
+                                       
+                                       foundData[0].startBlock = actualStartBlock;
+                                       foundData[0].blockCount = actualNumBlocks;
+                                       
+                                       // zero out remaining extents...
+                                       for (i = 1; i < kHFSPlusExtentDensity; ++i)
+                                       {
+                                               foundData[i].startBlock = 0;
+                                               foundData[i].blockCount = 0;
+                                       }
+
+                                       foundIndex = 0;
+                                       
+                                       err = CreateExtentRecord(vcb, &foundKey, foundData, &hint);
+                                       if (err == fxOvFlErr) {
+                                               //      We couldn't create an extent record because extents B-tree
+                                               //      couldn't grow.  Dellocate the extent just allocated and
+                                               //      return a disk full error.
+                                               (void) BlockDeallocate(vcb, actualStartBlock, actualNumBlocks, 0);
+                                               err = dskFulErr;
+                                       }
+                                       if (err != noErr) break;
+
+                                       needsFlush = true;              //      We need to update the B-tree header
+                               }
+                               else {
+                                       //      Add a new extent into this record and update.
+                                       foundData[foundIndex].startBlock = actualStartBlock;
+                                       foundData[foundIndex].blockCount = actualNumBlocks;
+                                       err = UpdateExtentRecord(vcb, fcb, 0, &foundKey, foundData, hint);
+                                       if (err != noErr) break;
+                               }
+                       }
+
+                       // Figure out how many bytes were actually allocated.
+                       // NOTE: BlockAllocate could have allocated more than we asked for.
+                       // Don't set the PEOF beyond what our client asked for.
+                       nextBlock += actualNumBlocks;
+                       bytesThisExtent = (int64_t)((int64_t)actualNumBlocks * (int64_t)volumeBlockSize);
+                       if (bytesThisExtent > bytesToAdd) {
+                               bytesToAdd = 0;
+                       }
+                       else {
+                               bytesToAdd -= bytesThisExtent;
+                               maximumBytes -= bytesThisExtent;
+                       }
+                       fcb->ff_blocks += (bytesThisExtent / volumeBlockSize);
+                       FTOC(fcb)->c_blocks += (bytesThisExtent / volumeBlockSize);
+                       FTOC(fcb)->c_flag |= C_MODIFIED;
+
+                       //      If contiguous allocation was requested, then we've already got one contiguous
+                       //      chunk.  If we didn't get all we wanted, then adjust the error to disk full.
+                       if (forceContig) {
+                               if (bytesToAdd != 0)
+                                       err = dskFulErr;
+                               break;                  //      We've already got everything that's contiguous
+                       }
+               }
+       } while (err == noErr && bytesToAdd);
+
+ErrorExit:
+Exit:
+       if (VCBTOHFS(vcb)->hfs_flags & HFS_METADATA_ZONE) {
+               /* Keep the roving allocator out of the metadata zone. */
+               if (vcb->nextAllocation >= VCBTOHFS(vcb)->hfs_metazone_start &&
+                   vcb->nextAllocation <= VCBTOHFS(vcb)->hfs_metazone_end) {
+                       hfs_lock_mount (hfsmp);
+                       HFS_UPDATE_NEXT_ALLOCATION(vcb, VCBTOHFS(vcb)->hfs_metazone_end + 1);   
+                       MarkVCBDirty(vcb);
+                       hfs_unlock_mount(hfsmp);
+               }
+       }
+       if (prevblocks < fcb->ff_blocks) {
+               *actualBytesAdded = (int64_t)(fcb->ff_blocks - prevblocks) * (int64_t)volumeBlockSize;
+       } else {
+               *actualBytesAdded = 0;
+       }
+
+       if (fastdev) {
+               hfs_hotfile_adjust_blocks(fcb->ff_cp->c_vp, 
+                                         (int64_t)prevblocks - fcb->ff_blocks);
+       }
+
+       if (needsFlush)
+               (void) FlushExtentFile(vcb);
+
+       return err;
+
+#if CONFIG_HFS_STD
+HFS_Std_Overflow:
+       err = fileBoundsErr;
+       goto ErrorExit;
+#endif
+}
+
+
+
+//_________________________________________________________________________________
+//
+// Routine:            TruncateFileC
+//
+// Function:   Truncates the disk space allocated to a file.  The file space is
+//                             truncated to a specified new PEOF rounded up to the next allocation
+//                             block boundry.  If the 'TFTrunExt' option is specified, the file is
+//                             truncated to the end of the extent containing the new PEOF.
+//
+//_________________________________________________________________________________
+
+OSErr TruncateFileC (
+       ExtendedVCB             *vcb,                           // volume that file resides on
+       FCB                             *fcb,                           // FCB of file to truncate
+       int64_t                 peof,                           // new physical size for file
+       int                             deleted,                        // if nonzero, the file's catalog record has already been deleted.
+       int                             rsrc,                           // does this represent a resource fork or not?
+       uint32_t                fileid,                         // the fileid of the file we're manipulating.
+       Boolean                 truncateToExtent)       // if true, truncate to end of extent containing newPEOF
+
+{
+       OSErr                           err;
+       u_int32_t                       nextBlock;              //      next file allocation block to consider
+       u_int32_t                       startBlock;             //      Physical (volume) allocation block number of start of a range
+       u_int32_t                       physNumBlocks;  //      Number of allocation blocks in file (according to PEOF)
+       u_int32_t                       numBlocks;
+       HFSPlusExtentKey        key;                    //      key for current extent record; key->keyLength == 0 if FCB's extent record
+       u_int32_t                       hint;                   //      BTree hint corresponding to key
+       HFSPlusExtentRecord     extentRecord;
+       u_int32_t                       extentIndex;
+       u_int32_t                       extentNextBlock;
+       u_int32_t                       numExtentsPerRecord;
+       int64_t             temp64;
+       u_int8_t                        forkType;
+       Boolean                         extentChanged;  // true if we actually changed an extent
+       Boolean                         recordDeleted;  // true if an extent record got deleted
+
+       recordDeleted = false;
+       
+       if (vcb->vcbSigWord == kHFSPlusSigWord) {
+               numExtentsPerRecord = kHFSPlusExtentDensity;
+       }
+       else {
+               numExtentsPerRecord = kHFSExtentDensity;
+       }
+       
+       if (rsrc) {
+               forkType = kResourceForkType;
+       }
+       else {
+               forkType = kDataForkType;
+       }
+       
+       temp64 = fcb->ff_blocks;
+       physNumBlocks = (u_int32_t)temp64;
+
+       //
+       //      Round newPEOF up to a multiple of the allocation block size.  If new size is
+       //      two gigabytes or more, then round down by one allocation block (??? really?
+       //      shouldn't that be an error?).
+       //
+       nextBlock = howmany(peof, vcb->blockSize);      // number of allocation blocks to remain in file
+       peof = (int64_t)((int64_t)nextBlock * (int64_t)vcb->blockSize);                                 // number of bytes in those blocks
+
+#if CONFIG_HFS_STD
+       if ((vcb->vcbSigWord == kHFSSigWord) && (peof >= kTwoGigabytes)) {
+               #if DEBUG
+                       DebugStr("HFS: Trying to truncate a file to 2GB or more");
+               #endif
+               err = fileBoundsErr;
+               goto ErrorExit;
+       }
+#endif
+
+       //
+       //      Update FCB's length
+       //
+       /*
+        * XXX Any errors could cause ff_blocks and c_blocks to get out of sync...
+        */
+       numBlocks = peof / vcb->blockSize;
+       if (!deleted) {
+               FTOC(fcb)->c_blocks -= (fcb->ff_blocks - numBlocks);
+       }
+       fcb->ff_blocks = numBlocks;
+       
+       // this catalog entry is modified and *must* get forced 
+       // to disk when hfs_update() is called
+       if (!deleted) {
+               /* 
+                * If the file is already C_NOEXISTS, then the catalog record
+                * has been removed from disk already.  We wouldn't need to force 
+                * another update
+                */
+               FTOC(fcb)->c_flag |= C_MODIFIED;
+       }
+       //
+       //      If the new PEOF is 0, then truncateToExtent has no meaning (we should always deallocate
+       //      all storage).
+       //
+       if (peof == 0) {
+               int i;
+               
+               //      Deallocate all the extents for this fork
+               err = DeallocateFork(vcb, fileid, forkType, fcb->fcbExtents, &recordDeleted);
+               if (err != noErr) goto ErrorExit;       //      got some error, so return it
+               
+               //      Update the catalog extent record (making sure it's zeroed out)
+               if (err == noErr) {
+                       for (i=0; i < kHFSPlusExtentDensity; i++) {
+                               fcb->fcbExtents[i].startBlock = 0;
+                               fcb->fcbExtents[i].blockCount = 0;
+                       }
+               }
+               goto Done;
+       }
+       
+       //
+       //      Find the extent containing byte (peof-1).  This is the last extent we'll keep.
+       //      (If truncateToExtent is true, we'll keep the whole extent; otherwise, we'll only
+       //      keep up through peof).  The search will tell us how many allocation blocks exist
+       //      in the found extent plus all previous extents.
+       //
+       err = SearchExtentFile(vcb, fcb, peof-1, &key, extentRecord, &extentIndex, &hint, &extentNextBlock);
+       if (err != noErr) goto ErrorExit;
+
+       extentChanged = false;          //      haven't changed the extent yet
+       
+       if (!truncateToExtent) {
+               //
+               //      Shorten this extent.  It may be the case that the entire extent gets
+               //      freed here.
+               //
+               numBlocks = extentNextBlock - nextBlock;        //      How many blocks in this extent to free up
+               if (numBlocks != 0) {
+                       //      Compute first volume allocation block to free
+                       startBlock = extentRecord[extentIndex].startBlock + extentRecord[extentIndex].blockCount - numBlocks;
+                       //      Free the blocks in bitmap
+                       err = BlockDeallocate(vcb, startBlock, numBlocks, 0);
+                       if (err != noErr) goto ErrorExit;
+                       //      Adjust length of this extent
+                       extentRecord[extentIndex].blockCount -= numBlocks;
+                       //      If extent is empty, set start block to 0
+                       if (extentRecord[extentIndex].blockCount == 0)
+                               extentRecord[extentIndex].startBlock = 0;
+                       //      Remember that we changed the extent record
+                       extentChanged = true;
+               }
+       }
+       
+       //
+       //      Now move to the next extent in the record, and set up the file allocation block number
+       //
+       nextBlock = extentNextBlock;            //      Next file allocation block to free
+       ++extentIndex;                                          //      Its index within the extent record
+       
+       //
+       //      Release all following extents in this extent record.  Update the record.
+       //
+       while (extentIndex < numExtentsPerRecord && extentRecord[extentIndex].blockCount != 0) {
+               numBlocks = extentRecord[extentIndex].blockCount;
+               //      Deallocate this extent
+               err = BlockDeallocate(vcb, extentRecord[extentIndex].startBlock, numBlocks, 0);
+               if (err != noErr) goto ErrorExit;
+               //      Update next file allocation block number
+               nextBlock += numBlocks;
+               //      Zero out start and length of this extent to delete it from record
+               extentRecord[extentIndex].startBlock = 0;
+               extentRecord[extentIndex].blockCount = 0;
+               //      Remember that we changed an extent
+               extentChanged = true;
+               //      Move to next extent in record
+               ++extentIndex;
+       }
+       
+       //
+       //      If any of the extents in the current record were changed, then update that
+       //      record (in the FCB, or extents file).
+       //
+       if (extentChanged) {
+               err = UpdateExtentRecord(vcb, fcb, deleted, &key, extentRecord, hint);
+               if (err != noErr) goto ErrorExit;
+       }
+       
+       //
+       //      If there are any following allocation blocks, then we need
+       //      to seach for their extent records and delete those allocation
+       //      blocks.
+       //
+       if (nextBlock < physNumBlocks)
+               err = TruncateExtents(vcb, forkType, fileid, nextBlock, &recordDeleted);
+
+Done:
+ErrorExit:
+       if (recordDeleted)
+               (void) FlushExtentFile(vcb);
+
+       return err;
+}
+
+
+/*
+ * HFS Plus only
+ *
+ */
+OSErr HeadTruncateFile (
+       ExtendedVCB  *vcb,
+       FCB  *fcb,
+       u_int32_t  headblks)
+{
+       HFSPlusExtentRecord  extents;
+       HFSPlusExtentRecord  tailExtents;
+       HFSCatalogNodeID  fileID;
+       u_int8_t  forkType;
+       u_int32_t  blkcnt = 0;
+       u_int32_t  startblk;
+       u_int32_t  blksfreed;
+       int  i, j;
+       int  error = 0;
+       int  lockflags;
+
+
+       if (vcb->vcbSigWord != kHFSPlusSigWord)
+               return (-1);
+
+       forkType = FORK_IS_RSRC(fcb) ? kResourceForkType : kDataForkType;
+       fileID = FTOC(fcb)->c_fileid;
+       bzero(tailExtents, sizeof(tailExtents));
+
+       blksfreed = 0;
+       startblk = 0;
+
+       /*
+        * Process catalog resident extents
+        */
+       for (i = 0, j = 0; i < kHFSPlusExtentDensity; ++i) {
+               blkcnt = fcb->fcbExtents[i].blockCount;
+               if (blkcnt == 0)
+                       break;  /* end of extents */
+
+               if (blksfreed < headblks) {
+                       error = BlockDeallocate(vcb, fcb->fcbExtents[i].startBlock, blkcnt, 0);
+                       /*
+                        * Any errors after the first BlockDeallocate
+                        * must be ignored so we can put the file in
+                        * a known state.
+                        */
+                       if (error ) {
+                               if (i == 0)
+                                       goto ErrorExit;  /* uh oh */
+                               else {
+                                       error = 0;
+                                       printf("hfs: HeadTruncateFile: problems deallocating %s (%d)\n",
+                                              FTOC(fcb)->c_desc.cd_nameptr ? (const char *)FTOC(fcb)->c_desc.cd_nameptr : "", error);
+                               }
+                       }
+
+                       blksfreed += blkcnt;
+                       fcb->fcbExtents[i].startBlock = 0;
+                       fcb->fcbExtents[i].blockCount = 0;
+               } else {
+                       tailExtents[j].startBlock = fcb->fcbExtents[i].startBlock;
+                       tailExtents[j].blockCount = blkcnt;
+                       ++j;
+               }
+               startblk += blkcnt;     
+       }
+       
+       if (blkcnt == 0)
+               goto CopyExtents;
+
+       lockflags = hfs_systemfile_lock(vcb, SFL_EXTENTS, HFS_EXCLUSIVE_LOCK);
+
+       /* 
+        * Process overflow extents
+        */
+       for (;;) {
+               u_int32_t  extblks;
+
+               error = FindExtentRecord(vcb, forkType, fileID, startblk, false, NULL, extents, NULL);
+               if (error) {
+                       /*
+                        * Any errors after the first BlockDeallocate
+                        * must be ignored so we can put the file in
+                        * a known state.
+                        */
+                       if (error != btNotFound)
+                               printf("hfs: HeadTruncateFile: problems finding extents %s (%d)\n",
+                                      FTOC(fcb)->c_desc.cd_nameptr ? (const char *)FTOC(fcb)->c_desc.cd_nameptr : "", error);
+                       error = 0;
+                       break;
+               }
+
+               for(i = 0, extblks = 0; i < kHFSPlusExtentDensity; ++i) {
+                       blkcnt = extents[i].blockCount;
+                       if (blkcnt == 0)
+                               break;  /* end of extents */
+
+                       if (blksfreed < headblks) {
+                               error = BlockDeallocate(vcb, extents[i].startBlock, blkcnt, 0);
+                               if (error) {
+                                       printf("hfs: HeadTruncateFile: problems deallocating %s (%d)\n",
+                                              FTOC(fcb)->c_desc.cd_nameptr ? (const char *)FTOC(fcb)->c_desc.cd_nameptr : "", error);
+                                       error = 0;
+                               }
+                               blksfreed += blkcnt;
+                       } else {
+                               tailExtents[j].startBlock = extents[i].startBlock;
+                               tailExtents[j].blockCount = blkcnt;
+                               ++j;
+                       }
+                       extblks += blkcnt;              
+               }
+               
+               error = DeleteExtentRecord(vcb, forkType, fileID, startblk);
+               if (error) {
+                       printf("hfs: HeadTruncateFile: problems deallocating %s (%d)\n",
+                               FTOC(fcb)->c_desc.cd_nameptr ? (const char *)FTOC(fcb)->c_desc.cd_nameptr : "", error);
+                       error = 0;
+               }
+               
+               if (blkcnt == 0)
+                       break;  /* all done */
+
+               startblk += extblks;
+       }
+       hfs_systemfile_unlock(vcb, lockflags);
+
+CopyExtents:
+       if (blksfreed) {
+               bcopy(tailExtents, fcb->fcbExtents, sizeof(tailExtents));
+               blkcnt = fcb->ff_blocks - headblks;
+               FTOC(fcb)->c_blocks -= headblks;
+               fcb->ff_blocks = blkcnt;
+
+               FTOC(fcb)->c_flag |= C_MODIFIED;
+               FTOC(fcb)->c_touch_chgtime = TRUE;
+
+               (void) FlushExtentFile(vcb);
+       }
+
+ErrorExit:     
+       return MacToVFSError(error);
+}
+
+
+
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+//     Routine:        SearchExtentRecord (was XRSearch)
+//
+//     Function:       Searches extent record for the extent mapping a given file
+//                             allocation block number (FABN).
+//
+//     Input:          searchFABN                      -  desired FABN
+//                             extentData                      -  pointer to extent data record (xdr)
+//                             extentDataStartFABN     -  beginning FABN for extent record
+//
+//     Output:         foundExtentDataOffset  -  offset to extent entry within xdr
+//                                                     result = noErr, offset to extent mapping desired FABN
+//                                                     result = FXRangeErr, offset to last extent in record
+//                             endingFABNPlusOne       -  ending FABN +1
+//                             noMoreExtents           - True if the extent was not found, and the
+//                                                                       extent record was not full (so don't bother
+//                                                                       looking in subsequent records); false otherwise.
+//
+//     Result:         noErr = ok
+//                             FXRangeErr = desired FABN > last mapped FABN in record
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+
+static OSErr SearchExtentRecord(
+       ExtendedVCB             *vcb,
+       u_int32_t                               searchFABN,
+       const HFSPlusExtentRecord       extentData,
+       u_int32_t                               extentDataStartFABN,
+       u_int32_t                               *foundExtentIndex,
+       u_int32_t                               *endingFABNPlusOne,
+       Boolean                                 *noMoreExtents)
+{
+       OSErr   err = noErr;
+       u_int32_t       extentIndex;
+       /* Set it to the HFS std value */
+       u_int32_t       numberOfExtents = kHFSExtentDensity;
+       u_int32_t       numAllocationBlocks;
+       Boolean foundExtent;
+       
+       *endingFABNPlusOne      = extentDataStartFABN;
+       *noMoreExtents          = false;
+       foundExtent                     = false;
+
+       /* Override numberOfExtents for HFS+/HFSX */
+       if (vcb->vcbSigWord != kHFSSigWord) {
+               numberOfExtents = kHFSPlusExtentDensity;
+       }
+       
+       for( extentIndex = 0; extentIndex < numberOfExtents; ++extentIndex )
+       {
+               
+               // Loop over the extent record and find the search FABN.
+               
+               numAllocationBlocks = extentData[extentIndex].blockCount;
+               if ( numAllocationBlocks == 0 )
+               {
+                       break;
+               }
+
+               *endingFABNPlusOne += numAllocationBlocks;
+               
+               if( searchFABN < *endingFABNPlusOne )
+               {
+                       // Found the extent.
+                       foundExtent = true;
+                       break;
+               }
+       }
+       
+       if( foundExtent )
+       {
+               // Found the extent. Note the extent offset
+               *foundExtentIndex = extentIndex;
+       }
+       else
+       {
+               // Did not find the extent. Set foundExtentDataOffset accordingly
+               if( extentIndex > 0 )
+               {
+                       *foundExtentIndex = extentIndex - 1;
+               }
+               else
+               {
+                       *foundExtentIndex = 0;
+               }
+               
+               // If we found an empty extent, then set noMoreExtents.
+               if (extentIndex < numberOfExtents)
+                       *noMoreExtents = true;
+
+               // Finally, return an error to the caller
+               err = fxRangeErr;
+       }
+
+       return( err );
+}
+
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+//     Routine:        SearchExtentFile (was XFSearch)
+//
+//     Function:       Searches extent file (including the FCB resident extent record)
+//                             for the extent mapping a given file position.
+//
+//     Input:          vcb                     -  VCB pointer
+//                             fcb                     -  FCB pointer
+//                             filePosition    -  file position (byte address)
+//
+// Output:             foundExtentKey                  -  extent key record (xkr)
+//                                                     If extent was found in the FCB's resident extent record,
+//                                                     then foundExtentKey->keyLength will be set to 0.
+//                             foundExtentData                 -  extent data record(xdr)
+//                             foundExtentIndex        -  index to extent entry in xdr
+//                                                     result =  0, offset to extent mapping desired FABN
+//                                                     result = FXRangeErr, offset to last extent in record
+//                                                                      (i.e., kNumExtentsPerRecord-1)
+//                             extentBTreeHint                 -  BTree hint for extent record
+//                                                     kNoHint = Resident extent record
+//                             endingFABNPlusOne               -  ending FABN +1
+//
+//     Result:
+//             noErr                   Found an extent that contains the given file position
+//             FXRangeErr              Given position is beyond the last allocated extent
+//             (other)                 (some other internal I/O error)
+//\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b\8b
+
+OSErr SearchExtentFile(
+       ExtendedVCB     *vcb,
+       const FCB                       *fcb,
+       int64_t                         filePosition,
+       HFSPlusExtentKey        *foundExtentKey,
+       HFSPlusExtentRecord     foundExtentData,
+       u_int32_t                       *foundExtentIndex,
+       u_int32_t                       *extentBTreeHint,
+       u_int32_t                       *endingFABNPlusOne )
+{
+       OSErr                           err;
+       u_int32_t                       filePositionBlock;
+       int64_t                         temp64;
+       Boolean                         noMoreExtents;
+       int  lockflags;
+       
+       temp64 = filePosition / (int64_t)vcb->blockSize;
+       filePositionBlock = (u_int32_t)temp64;
+
+    bcopy ( fcb->fcbExtents, foundExtentData, sizeof(HFSPlusExtentRecord));
+       
+       //      Search the resident FCB first.
+    err = SearchExtentRecord( vcb, filePositionBlock, foundExtentData, 0,
+                                                                       foundExtentIndex, endingFABNPlusOne, &noMoreExtents );
+
+       if( err == noErr ) {
+               // Found the extent. Set results accordingly
+               *extentBTreeHint = kNoHint;                     // no hint, because not in the BTree
+               foundExtentKey->keyLength = 0;          // 0 = the FCB itself
+               
+               goto Exit;
+       }
+       
+       //      Didn't find extent in FCB.  If FCB's extent record wasn't full, there's no point
+       //      in searching the extents file.  Note that SearchExtentRecord left us pointing at
+       //      the last valid extent (or the first one, if none were valid).  This means we need
+       //      to fill in the hint and key outputs, just like the "if" statement above.
+       if ( noMoreExtents ) {
+               *extentBTreeHint = kNoHint;                     // no hint, because not in the BTree
+               foundExtentKey->keyLength = 0;          // 0 = the FCB itself
+               err = fxRangeErr;               // There are no more extents, so must be beyond PEOF
+               goto Exit;
+       }
+       
+       //
+       //      Find the desired record, or the previous record if it is the same fork
+       //
+       lockflags = hfs_systemfile_lock(vcb, SFL_EXTENTS, HFS_EXCLUSIVE_LOCK);
+
+       err = FindExtentRecord(vcb, FORK_IS_RSRC(fcb) ? kResourceForkType : kDataForkType,
+                                                  FTOC(fcb)->c_fileid, filePositionBlock, true, foundExtentKey, foundExtentData, extentBTreeHint);
+       hfs_systemfile_unlock(vcb, lockflags);
+
+       if (err == btNotFound) {
+               //
+               //      If we get here, the desired position is beyond the extents in the FCB, and there are no extents
+               //      in the extents file.  Return the FCB's extents and a range error.
+               //
+               *extentBTreeHint = kNoHint;
+               foundExtentKey->keyLength = 0;
+               err = GetFCBExtentRecord(fcb, foundExtentData);
+               //      Note: foundExtentIndex and endingFABNPlusOne have already been set as a result of the very
+               //      first SearchExtentRecord call in this function (when searching in the FCB's extents, and
+               //      we got a range error).
+               
+               return fxRangeErr;
+       }
+       
+       //
+       //      If we get here, there was either a BTree error, or we found an appropriate record.
+       //      If we found a record, then search it for the correct index into the extents.
+       //
+       if (err == noErr) {
+               //      Find appropriate index into extent record
+               err = SearchExtentRecord(vcb, filePositionBlock, foundExtentData, foundExtentKey->startBlock,
+                                                                foundExtentIndex, endingFABNPlusOne, &noMoreExtents);
+       }
+
+Exit:
+       return err;
+}
+
+
+
+//============================================================================
+//     Routine:        UpdateExtentRecord
+//
+//     Function:       Write new extent data to an existing extent record with a given key.
+//                             If all of the extents are empty, and the extent record is in the
+//                             extents file, then the record is deleted.
+//
+//     Input:          vcb                                             -       the volume containing the extents
+//                             fcb                                             -       the file that owns the extents
+//                             deleted                                 -       whether or not the file is already deleted
+//                             extentFileKey                   -       pointer to extent key record (xkr)
+//                                             If the key length is 0, then the extents are actually part
+//                                             of the catalog record, stored in the FCB.
+//                             extentData                      -       pointer to extent data record (xdr)
+//                             extentBTreeHint                 -       hint for given key, or kNoHint
+//
+//     Result:         noErr = ok
+//                             (other) = error from BTree
+//============================================================================
+
+static OSErr UpdateExtentRecord (ExtendedVCB *vcb, FCB  *fcb, int deleted,
+                                                                const HFSPlusExtentKey  *extentFileKey,
+                                                                const HFSPlusExtentRecord  extentData,
+                                                                u_int32_t  extentBTreeHint) 
+{
+    OSErr err = noErr;
+       
+       if (extentFileKey->keyLength == 0) {    // keyLength == 0 means the FCB's extent record
+               BlockMoveData(extentData, fcb->fcbExtents, sizeof(HFSPlusExtentRecord));
+               if (!deleted) {
+                       FTOC(fcb)->c_flag |= C_MODIFIED;
+               }
+       }
+       else {
+               struct BTreeIterator *btIterator = NULL;
+               FSBufferDescriptor btRecord;
+               u_int16_t btRecordSize;
+               FCB * btFCB;
+               int lockflags;
+
+               //
+               //      Need to find and change a record in Extents BTree
+               //
+               btFCB = GetFileControlBlock(vcb->extentsRefNum);
+               
+               btIterator = hfs_mallocz(sizeof(struct BTreeIterator));
+
+               /*
+                * The lock taken by callers of ExtendFileC/TruncateFileC is
+                * speculative and only occurs when the file already has
+                * overflow extents. So we need to make sure we have the lock
+                * here.  The extents btree lock can be nested (its recursive)
+                * so we always take it here.
+                */
+               lockflags = hfs_systemfile_lock(vcb, SFL_EXTENTS, HFS_EXCLUSIVE_LOCK);
+
+               /* HFS+/HFSX */
+               if (vcb->vcbSigWord != kHFSSigWord) {           //      HFS Plus volume
+                       HFSPlusExtentRecord     foundData;              // The extent data actually found
+
+                       BlockMoveData(extentFileKey, &btIterator->key, sizeof(HFSPlusExtentKey));
+
+                       btIterator->hint.index = 0;
+                       btIterator->hint.nodeNum = extentBTreeHint;
+
+                       btRecord.bufferAddress = &foundData;
+                       btRecord.itemSize = sizeof(HFSPlusExtentRecord);
+                       btRecord.itemCount = 1;
+
+                       err = BTSearchRecord(btFCB, btIterator, &btRecord, &btRecordSize, btIterator);
+       
+                       if (err == noErr) {
+                               BlockMoveData(extentData, &foundData, sizeof(HFSPlusExtentRecord));
+                               err = BTReplaceRecord(btFCB, btIterator, &btRecord, btRecordSize);
+                       }
+                       (void) BTFlushPath(btFCB);
+               }
+#if CONFIG_HFS_STD
+               else {
+                       /* HFS Standard */
+                       HFSExtentKey *  key;                            // Actual extent key used on disk in HFS
+                       HFSExtentRecord foundData;                      // The extent data actually found
+
+                       key = (HFSExtentKey*) &btIterator->key;
+                       key->keyLength  = kHFSExtentKeyMaximumLength;
+                       key->forkType   = extentFileKey->forkType;
+                       key->fileID             = extentFileKey->fileID;
+                       key->startBlock = extentFileKey->startBlock;
+
+                       btIterator->hint.index = 0;
+                       btIterator->hint.nodeNum = extentBTreeHint;
+
+                       btRecord.bufferAddress = &foundData;
+                       btRecord.itemSize = sizeof(HFSExtentRecord);
+                       btRecord.itemCount = 1;
+
+                       err = BTSearchRecord(btFCB, btIterator, &btRecord, &btRecordSize, btIterator);
+
+                       if (err == noErr)
+                               err = HFSPlusToHFSExtents(extentData, (HFSExtentDescriptor *)&foundData);
+
+                       if (err == noErr)
+                               err = BTReplaceRecord(btFCB, btIterator, &btRecord, btRecordSize);
+                       (void) BTFlushPath(btFCB);
+
+               }
+#endif
+
+               hfs_systemfile_unlock(vcb, lockflags);
+
+               hfs_free(btIterator, sizeof(*btIterator));
+       }
+       
+       return err;
+}
+
+
+
+#if CONFIG_HFS_STD
+static OSErr HFSPlusToHFSExtents(
+       const HFSPlusExtentRecord       oldExtents,
+       HFSExtentRecord         newExtents)
+{
+       OSErr   err;
+       
+       err = noErr;
+
+       // copy the first 3 extents
+       newExtents[0].startBlock = oldExtents[0].startBlock;
+       newExtents[0].blockCount = oldExtents[0].blockCount;
+       newExtents[1].startBlock = oldExtents[1].startBlock;
+       newExtents[1].blockCount = oldExtents[1].blockCount;
+       newExtents[2].startBlock = oldExtents[2].startBlock;
+       newExtents[2].blockCount = oldExtents[2].blockCount;
+
+       #if DEBUG
+               if (oldExtents[3].startBlock || oldExtents[3].blockCount) {
+                       DebugStr("ExtentRecord with > 3 extents is invalid for HFS");
+                       err = fsDSIntErr;
+               }
+       #endif
+       
+       return err;
+}
+#endif
+
+
+
+static OSErr GetFCBExtentRecord(
+       const FCB                       *fcb,
+       HFSPlusExtentRecord     extents)
+{
+       
+       BlockMoveData(fcb->fcbExtents, extents, sizeof(HFSPlusExtentRecord));
+       
+       return noErr;
+}
+
+
+//_________________________________________________________________________________
+//
+// Routine:            ExtentsAreIntegral
+//
+// Purpose:            Ensure that each extent can hold an integral number of nodes
+//                             Called by the NodesAreContiguous function
+//_________________________________________________________________________________
+
+static Boolean ExtentsAreIntegral(
+       const HFSPlusExtentRecord extentRecord,
+       u_int32_t       mask,
+       u_int32_t       *blocksChecked,
+       Boolean         *checkedLastExtent)
+{
+       u_int32_t       blocks;
+       u_int32_t       extentIndex;
+
+       *blocksChecked = 0;
+       *checkedLastExtent = false;
+       
+       for(extentIndex = 0; extentIndex < kHFSPlusExtentDensity; extentIndex++)
+       {               
+               blocks = extentRecord[extentIndex].blockCount;
+               
+               if ( blocks == 0 )
+               {
+                       *checkedLastExtent = true;
+                       break;
+               }
+
+               *blocksChecked += blocks;
+
+               if (blocks & mask)
+                       return false;
+       }
+       
+       return true;
+}
+
+
+//_________________________________________________________________________________
+//
+// Routine:            NodesAreContiguous
+//
+// Purpose:            Ensure that all b-tree nodes are contiguous on disk
+//                             Called by BTOpenPath during volume mount
+//_________________________________________________________________________________
+
+Boolean NodesAreContiguous(
+       ExtendedVCB     *vcb,
+       FCB                     *fcb,
+       u_int32_t       nodeSize)
+{
+       u_int32_t                       mask;
+       u_int32_t                       startBlock;
+       u_int32_t                       blocksChecked;
+       u_int32_t                       hint;
+       HFSPlusExtentKey        key;
+       HFSPlusExtentRecord     extents;
+       OSErr                           result;
+       Boolean                         lastExtentReached;
+       int  lockflags;
+       
+
+       if (vcb->blockSize >= nodeSize)
+               return TRUE;
+
+       mask = (nodeSize / vcb->blockSize) - 1;
+
+       // check the local extents
+       (void) GetFCBExtentRecord(fcb, extents);
+       if ( !ExtentsAreIntegral(extents, mask, &blocksChecked, &lastExtentReached) )
+               return FALSE;
+
+       if ( lastExtentReached || 
+                (int64_t)((int64_t)blocksChecked * (int64_t)vcb->blockSize) >= (int64_t)fcb->ff_size)
+               return TRUE;
+
+       startBlock = blocksChecked;
+
+       lockflags = hfs_systemfile_lock(vcb, SFL_EXTENTS, HFS_EXCLUSIVE_LOCK);
+
+       // check the overflow extents (if any)
+       while ( !lastExtentReached )
+       {
+               result = FindExtentRecord(vcb, kDataForkType, fcb->ff_cp->c_fileid, startBlock, FALSE, &key, extents, &hint);
+               if (result) break;
+
+               if ( !ExtentsAreIntegral(extents, mask, &blocksChecked, &lastExtentReached) ) {
+                       hfs_systemfile_unlock(vcb, lockflags);
+                       return FALSE;
+               }
+               startBlock += blocksChecked;
+       }
+       hfs_systemfile_unlock(vcb, lockflags);
+       return TRUE;
+}
+