/* * Copyright (c) 2000-2008 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@ */ /* File: BTree.c Contains: Implementation of public interface routines for B-tree manager. Version: HFS Plus 1.0 Written by: Gordon Sheridan and Bill Bruffey Copyright: © 1992-1999 by Apple Computer, Inc., all rights reserved. File Ownership: DRI: Don Brady Other Contact: Mark Day Technology: File Systems Writers: (msd) Mark Day (DSH) Deric Horn (djb) Don Brady Change History (most recent first): 9/22/99 ser Added routines BTGetLastSync and BTSetLastSync 6/1/99 djb Sync up with Mac OS 8.6. 6/30/98 djb In BTOpenPath make sure nodes are contiguous on disk (radar #2249539). 4/15/98 djb In BTOpenPath need to clear nodeRec.buffer if GetBlockProc fails. 4/11/98 djb Add RequireFileLock checking to all external entry points. 03/23/98 djb In BTOpenPath use kTrashBlock option when releasing the header so that we get a full node when we call GetNode. 12/12/97 djb Radar #2202682, BTIterateRecord with kBTreeCurrentRecord was not checking if we had a record and could call BlockMove with an uninitialize source pointer (causing a bus error). 10/24/97 msd In BTIterateRecord, when moving to the previous or next record and we have to move to another node, see if we need to release the node about to be "shifted out" (opposite sibling of the direction we need to move). 7/25/97 DSH BTSearchRecord now takes a heuristicHint, nodeNum, and tries it before calling SearchBTree 7/24/97 djb GetBlockProc now take a file refnum instead of an FCB ptr. 7/22/97 djb Move trace points from BTreeWrapper.c to here. 7/21/97 djb LogEndTime now takes an error code. 7/16/97 DSH FilesInternal.i renamed FileMgrInternal.i to avoid name collision 5/19/97 djb Add summary traces to BTIterateRecord. 4/23/97 djb first checked in 2/19/97 djb Enable variable sized index keys for HFS+ volumes. Added node cache to support nodes larger than 512 bytes. 1/27/97 djb Calls to InsertTree and DeleteTree are now recursive (to support variable sized index keys). 1/13/97 djb Added support for getting current record to BTIterateRecord. 1/6/97 djb Initialize "BigKeys" attribute in BTOpen. 1/3/97 djb Added support for large keys. 12/23/96 djb On exit map fsBTEmptyErr and fsBTEndOfIterationErr to fsBTRecordNotFoundErr. 12/19/96 djb first checked in History applicable to original Scarecrow Design: <13> 10/25/96 ser Changing for new VFPI <12> 10/18/96 ser Converting over VFPI changes <11> 9/17/96 dkh More BTree statistics. Modified hint checks to not bail out when an error is returned from GetNode. <10> 9/16/96 dkh Revised BTree statistics. <9> 8/23/96 dkh Remove checks for multiple paths to BTree file. Need to add equivalent mechanism later. <8> 6/20/96 dkh Radar #1358740. Switch from using Pools to debug MemAllocators. <7> 3/14/96 jev Fix BTreeSetRecord, recordFound was not set for the case of a simple replace causing the leafRecords count to get bumped even though we didn't have to add a record. <6> 3/1/96 prp Fix lint problems. Bug in BTSetRecord that does not initialize recordFound. <5> 1/22/96 dkh Add #include Memory.h <4> 1/10/96 msd Use the real function names from Math64.i. <3> 1/4/96 jev Fix BTItererateRecord for the condition when the iterator position routine does not find the record and we are looking for the next record. In such a case, if the node's forrward link is non-zero, we have to keep iterating next and not return fsBTEndOfIterationErr error. <2> 12/7/95 dkh D10E2 build. Changed usage of Ref data type to LogicalAddress. <1> 10/18/95 rst Moved from Scarecrow project. <24> 7/18/95 mbb Change MoveData & ClearBytes to BlockMoveData & BlockZero. <23> 1/31/95 prp GetBlockProc interface uses a 64 bit node number. <22> 1/12/95 wjk Adopt Model FileSystem changes in D5. <21> 11/16/94 prp Add IsItAHint routine and use it whenever hint's node number was used for testing. <20> 11/10/94 prp BTGetInfo name collides with the same name in FileManagerPriv.i. Change it to BTGetInformation. <19> 9/30/94 prp Get in sync with D2 interface changes. <18> 7/22/94 wjk Convert to the new set of header files. <17> 12/9/93 wjk Cleanup usage of char, Byte, int8, UInt8, etc. <16> 12/2/93 wjk Move from Makefiles to BuildFiles. Fit into the ModernOS and NRCmds environments. <15> 11/30/93 wjk Move from Makefiles to BuildFiles. Fit into the ModernOS and NRCmds environments. <14> 9/30/93 gs Rename E_NoGetNodeProc and E_NoReleaseNodeProc to E_NoXxxxBlockProc. <13> 8/31/93 prp Use Set64U instead of Set64. <12> 8/16/93 prp In BTSearchRecord, if the input hint found the node and record, set the local nodeNum variable correctly so that the resultant iterator gets set correctly. <11> 7/1/93 gs Fix bug in BTIterateRecord related to kBTreePrevRecord operation. <10> 6/2/93 gs Update for changes to FSErrors.h and add some comments. <9> 5/24/93 gs Fix bug in BTInsert/Set/ReplaceRecord which didn't set node hint properly in some cases. <8> 5/24/93 gs Do NOT map fsBTEmptyErr to fsBTRecordNotFoundErr in BTSearchRecord. <7> 5/24/93 gs Rename BTFlush to BTFlushPath. <6> 5/21/93 gs Add hint optimization to Set/Replace routines. <5> 5/10/93 gs Remove Panic from BTInitialize for small logicalEOF. Implement Insert, Set, Replace, and Delete. <4> 3/23/93 gs Finish BTInitialize. <3> 2/8/93 gs Implement BTSearchRecord and BTIterateRecord. <2> 12/8/92 gs Implement Open and Close routines. <1> 11/15/92 gs first checked in */ #include "../headers/BTreesPrivate.h" #include "../../hfs_btreeio.h" /* * The amount that the BTree header leaf count can be wrong before we assume * it is in an infinite loop. */ #define kNumLeafRecSlack 10 //////////////////////////////////// Globals //////////////////////////////////// /////////////////////////// BTree Module Entry Points /////////////////////////// /*------------------------------------------------------------------------------- Routine: BTOpenPath - Open a file for access as a B*Tree. Function: Create BTree control block for a file, if necessary. Validates the file to be sure it looks like a BTree file. Input: filePtr - pointer to file to open as a B-tree keyCompareProc - pointer to client's KeyCompare function Result: noErr - success paramErr - required ptr was nil fsBTInvalidFileErr - memFullErr - != noErr - failure -------------------------------------------------------------------------------*/ OSStatus BTOpenPath(FCB *filePtr, KeyCompareProcPtr keyCompareProc) { OSStatus err; BTreeControlBlockPtr btreePtr; BTHeaderRec *header; NodeRec nodeRec; ////////////////////// Preliminary Error Checking /////////////////////////// if ( filePtr == nil ) { return paramErr; } /* * Subsequent opens allow key compare proc to be changed. */ if ( filePtr->fcbBTCBPtr != nil && keyCompareProc != nil) { btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; btreePtr->keyCompareProc = keyCompareProc; return noErr; } if ( filePtr->fcbEOF < kMinNodeSize ) return fsBTInvalidFileErr; //////////////////////// Allocate Control Block ///////////////////////////// btreePtr = (BTreeControlBlock*) NewPtrSysClear( sizeof( BTreeControlBlock ) ); if (btreePtr == nil) { Panic ("\pBTOpen: no memory for btreePtr."); return memFullErr; } btreePtr->getBlockProc = GetBTreeBlock; btreePtr->releaseBlockProc = ReleaseBTreeBlock; btreePtr->setEndOfForkProc = ExtendBTreeFile; btreePtr->keyCompareProc = keyCompareProc; /////////////////////////// Read Header Node //////////////////////////////// nodeRec.buffer = nil; // so we can call ReleaseNode btreePtr->fileRefNum = GetFileRefNumFromFCB(filePtr); filePtr->fcbBTCBPtr = (Ptr) btreePtr; // attach btree cb to file /* Prefer doing I/O a physical block at a time */ nodeRec.blockSize = VTOHFS(btreePtr->fileRefNum)->hfs_physical_block_size; /* Start with the allocation block size for regular files. */ if (FTOC(filePtr)->c_fileid >= kHFSFirstUserCatalogNodeID) { nodeRec.blockSize = FCBTOVCB(filePtr)->blockSize; } REQUIRE_FILE_LOCK(btreePtr->fileRefNum, false); // it is now safe to call M_ExitOnError (err) err = SetBTreeBlockSize (btreePtr->fileRefNum, nodeRec.blockSize, 1); M_ExitOnError (err); err = GetBTreeBlock(btreePtr->fileRefNum, kHeaderNodeNum, kGetBlock, &nodeRec ); if (err != noErr) { nodeRec.buffer = nil; nodeRec.blockHeader = nil; Panic("\pBTOpen: getNodeProc returned error getting header node."); goto ErrorExit; } ++btreePtr->numGetNodes; header = (BTHeaderRec*) ((u_long)nodeRec.buffer + sizeof(BTNodeDescriptor)); ///////////////////////////// verify header ///////////////////////////////// err = VerifyHeader (filePtr, header); M_ExitOnError (err); ///////////////////// Initalize fields from header ////////////////////////// PanicIf ( (FCBTOVCB(filePtr)->vcbSigWord != 0x4244) && (header->nodeSize == 512), "\p BTOpenPath: wrong node size for HFS+ volume!"); // 0x4244 = 'BD' btreePtr->treeDepth = header->treeDepth; btreePtr->rootNode = header->rootNode; btreePtr->leafRecords = header->leafRecords; btreePtr->firstLeafNode = header->firstLeafNode; btreePtr->lastLeafNode = header->lastLeafNode; btreePtr->nodeSize = header->nodeSize; btreePtr->maxKeyLength = header->maxKeyLength; btreePtr->totalNodes = header->totalNodes; btreePtr->freeNodes = header->freeNodes; if (FTOC(filePtr)->c_fileid >= kHFSFirstUserCatalogNodeID) filePtr->ff_clumpsize = header->clumpSize; btreePtr->btreeType = header->btreeType; btreePtr->keyCompareType = header->keyCompareType; btreePtr->attributes = header->attributes; if ( btreePtr->maxKeyLength > 40 ) btreePtr->attributes |= (kBTBigKeysMask + kBTVariableIndexKeysMask); //€€ we need a way to save these attributes /////////////////////// Initialize dynamic fields /////////////////////////// btreePtr->version = kBTreeVersion; btreePtr->flags = 0; btreePtr->writeCount = 1; /////////////////////////// Check Header Node /////////////////////////////// // set kBadClose attribute bit, and UpdateNode /* b-tree node size must be at least as big as the logical block size */ if (btreePtr->nodeSize < VTOHFS(btreePtr->fileRefNum)->hfs_logical_block_size) { /* * If this tree has any records or the media is writeable then * we cannot mount using the current physical block size. */ if (btreePtr->leafRecords > 0 || VTOHFS(btreePtr->fileRefNum)->hfs_flags & HFS_WRITEABLE_MEDIA) { err = fsBTBadNodeSize; goto ErrorExit; } } /* * If the actual node size is different than the amount we read, * then release and trash this block, and re-read with the correct * node size. */ if ( btreePtr->nodeSize != nodeRec.blockSize ) { err = SetBTreeBlockSize (btreePtr->fileRefNum, btreePtr->nodeSize, 32); M_ExitOnError (err); /* * Need to use kTrashBlock option to force the * buffer cache to read the entire node */ err = ReleaseBTreeBlock(btreePtr->fileRefNum, &nodeRec, kTrashBlock); ++btreePtr->numReleaseNodes; M_ExitOnError (err); err = GetNode (btreePtr, kHeaderNodeNum, 0, &nodeRec ); M_ExitOnError (err); } //€€ total nodes * node size <= LEOF? err = ReleaseNode (btreePtr, &nodeRec); M_ExitOnError (err); /* * Under Mac OS, b-tree nodes can be non-contiguous on disk when the * allocation block size is smaller than the b-tree node size. * * If journaling is turned on for this volume we can't deal with this * situation and so we bail out. If journaling isn't on it's ok as * hfs_strategy_fragmented() deals with it. Journaling can't support * this because it assumes that if you give it a block that it's * contiguous on disk. */ if ( FCBTOHFS(filePtr)->jnl && !NodesAreContiguous(FCBTOVCB(filePtr), filePtr, btreePtr->nodeSize) ) { return fsBTInvalidNodeErr; } //////////////////////////////// Success //////////////////////////////////// //€€ align LEOF to multiple of node size? - just on close return noErr; /////////////////////// Error - Clean up and Exit /////////////////////////// ErrorExit: filePtr->fcbBTCBPtr = nil; (void) ReleaseNode (btreePtr, &nodeRec); DisposePtr( (Ptr) btreePtr ); return err; } /*------------------------------------------------------------------------------- Routine: BTClosePath - Flush BTree Header and Deallocate Memory for BTree. Function: Flush the BTreeControlBlock fields to header node, and delete BTree control block and key descriptor associated with the file if filePtr is last path of type kBTreeType ('btre'). Input: filePtr - pointer to file to delete BTree control block for. Result: noErr - success fsBTInvalidFileErr - != noErr - failure -------------------------------------------------------------------------------*/ OSStatus BTClosePath (FCB *filePtr) { OSStatus err; BTreeControlBlockPtr btreePtr; btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; if (btreePtr == nil) return fsBTInvalidFileErr; REQUIRE_FILE_LOCK(btreePtr->fileRefNum, false); ////////////////////// Check for other BTree Paths ////////////////////////// btreePtr->attributes &= ~kBTBadCloseMask; // clear "bad close" attribute bit err = UpdateHeader (btreePtr, true); M_ExitOnError (err); DisposePtr( (Ptr) btreePtr ); filePtr->fcbBTCBPtr = nil; return noErr; /////////////////////// Error - Clean Up and Exit /////////////////////////// ErrorExit: return err; } /*------------------------------------------------------------------------------- Routine: BTSearchRecord - Search BTree for a record with a matching key. Function: Search for position in B*Tree indicated by searchKey. If a valid node hint is provided, it will be searched first, then SearchTree will be called. If a BTreeIterator is provided, it will be set to the position found as a result of the search. If a record exists at that position, and a BufferDescriptor is supplied, the record will be copied to the buffer (as much as will fit), and recordLen will be set to the length of the record. If an error other than fsBTRecordNotFoundErr occurs, the BTreeIterator, if any, is invalidated, and recordLen is set to 0. Input: pathPtr - pointer to path for BTree file. searchKey - pointer to search key to match. hintPtr - pointer to hint (may be nil) Output: record - pointer to BufferDescriptor containing record recordLen - length of data at recordPtr iterator - pointer to BTreeIterator indicating position result of search Result: noErr - success, record contains copy of record found fsBTRecordNotFoundErr - record was not found, no data copied fsBTInvalidFileErr - no BTreeControlBlock is allocated for the fork fsBTInvalidKeyLengthErr - != noErr - failure -------------------------------------------------------------------------------*/ OSStatus BTSearchRecord (FCB *filePtr, BTreeIterator *searchIterator, FSBufferDescriptor *record, u_int16_t *recordLen, BTreeIterator *resultIterator ) { OSStatus err; BTreeControlBlockPtr btreePtr; TreePathTable treePathTable; u_int32_t nodeNum; BlockDescriptor node; u_int16_t index; BTreeKeyPtr keyPtr; RecordPtr recordPtr; u_int16_t len; Boolean foundRecord; Boolean validHint; if (filePtr == nil) { return paramErr; } if (searchIterator == nil) { return paramErr; } node.buffer = nil; node.blockHeader = nil; btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; if (btreePtr == nil) { return fsBTInvalidFileErr; } REQUIRE_FILE_LOCK(btreePtr->fileRefNum, true); foundRecord = false; ////////////////////////////// Take A Hint ////////////////////////////////// err = IsItAHint (btreePtr, searchIterator, &validHint); M_ExitOnError (err); if (validHint) { nodeNum = searchIterator->hint.nodeNum; err = GetNode (btreePtr, nodeNum, kGetNodeHint, &node); if( err == noErr ) { if ( ((BTNodeDescriptor*) node.buffer)->kind == kBTLeafNode && ((BTNodeDescriptor*) node.buffer)->numRecords > 0 ) { foundRecord = SearchNode (btreePtr, node.buffer, &searchIterator->key, &index); //€€ if !foundRecord, we could still skip tree search if ( 0 < index < numRecords ) } if (foundRecord == false) { err = ReleaseNode (btreePtr, &node); M_ExitOnError (err); } else { ++btreePtr->numValidHints; } } if( foundRecord == false ) (void) BTInvalidateHint( searchIterator ); } //////////////////////////// Search The Tree //////////////////////////////// if (foundRecord == false) { err = SearchTree ( btreePtr, &searchIterator->key, treePathTable, &nodeNum, &node, &index); switch (err) { case noErr: foundRecord = true; break; case fsBTRecordNotFoundErr: break; default: goto ErrorExit; } } //////////////////////////// Get the Record ///////////////////////////////// if (foundRecord == true) { //XXX Should check for errors! Or BlockMove could choke on recordPtr!!! GetRecordByIndex (btreePtr, node.buffer, index, &keyPtr, &recordPtr, &len); if (recordLen != nil) *recordLen = len; if (record != nil) { ByteCount recordSize; recordSize = record->itemCount * record->itemSize; if (len > recordSize) len = recordSize; BlockMoveData (recordPtr, record->bufferAddress, len); } } /////////////////////// Success - Update Iterator /////////////////////////// if (resultIterator != nil) { if (foundRecord) { resultIterator->hint.writeCount = btreePtr->writeCount; resultIterator->hint.nodeNum = nodeNum; resultIterator->hint.index = index; } #if DEBUG_BUILD resultIterator->hint.reserved1 = 0; resultIterator->hint.reserved2 = 0; resultIterator->version = 0; resultIterator->reserved = 0; #endif // copy the key in the BTree when found rather than searchIterator->key to get proper case/diacriticals if (foundRecord == true) BlockMoveData ((Ptr)keyPtr, (Ptr)&resultIterator->key, CalcKeySize(btreePtr, keyPtr)); else BlockMoveData ((Ptr)&searchIterator->key, (Ptr)&resultIterator->key, CalcKeySize(btreePtr, &searchIterator->key)); } err = ReleaseNode (btreePtr, &node); M_ExitOnError (err); if (foundRecord == false) return fsBTRecordNotFoundErr; else return noErr; /////////////////////// Error - Clean Up and Exit /////////////////////////// ErrorExit: if (recordLen != nil) *recordLen = 0; if (resultIterator != nil) { resultIterator->hint.writeCount = 0; resultIterator->hint.nodeNum = 0; resultIterator->hint.index = 0; resultIterator->hint.reserved1 = 0; resultIterator->hint.reserved2 = 0; resultIterator->version = 0; resultIterator->reserved = 0; resultIterator->key.length16 = 0; // zero out two bytes to cover both types of keys } if ( err == fsBTEmptyErr ) err = fsBTRecordNotFoundErr; return err; } /*------------------------------------------------------------------------------- Routine: BTIterateRecord - Find the first, next, previous, or last record. Function: Find the first, next, previous, or last record in the BTree Input: pathPtr - pointer to path iterate records for. operation - iteration operation (first,next,prev,last) iterator - pointer to iterator indicating start position Output: iterator - iterator is updated to indicate new position newKeyPtr - pointer to buffer to copy key found by iteration record - pointer to buffer to copy record found by iteration recordLen - length of record Result: noErr - success != noErr - failure -------------------------------------------------------------------------------*/ OSStatus BTIterateRecord (FCB *filePtr, BTreeIterationOperation operation, BTreeIterator *iterator, FSBufferDescriptor *record, u_int16_t *recordLen ) { OSStatus err; BTreeControlBlockPtr btreePtr; BTreeKeyPtr keyPtr; RecordPtr recordPtr; u_int16_t len; Boolean foundRecord; u_int32_t nodeNum; BlockDescriptor left, node, right; u_int16_t index; ////////////////////////// Priliminary Checks /////////////////////////////// left.buffer = nil; left.blockHeader = nil; right.buffer = nil; right.blockHeader = nil; node.buffer = nil; node.blockHeader = nil; if (filePtr == nil) { return paramErr; } btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; if (btreePtr == nil) { return fsBTInvalidFileErr; //€€ handle properly } REQUIRE_FILE_LOCK(btreePtr->fileRefNum, true); if ((operation != kBTreeFirstRecord) && (operation != kBTreeNextRecord) && (operation != kBTreeCurrentRecord) && (operation != kBTreePrevRecord) && (operation != kBTreeLastRecord)) { err = fsInvalidIterationMovmentErr; goto ErrorExit; } /////////////////////// Find First or Last Record /////////////////////////// if ((operation == kBTreeFirstRecord) || (operation == kBTreeLastRecord)) { if (operation == kBTreeFirstRecord) nodeNum = btreePtr->firstLeafNode; else nodeNum = btreePtr->lastLeafNode; if (nodeNum == 0) { err = fsBTEmptyErr; goto ErrorExit; } err = GetNode (btreePtr, nodeNum, 0, &node); M_ExitOnError (err); if ( ((NodeDescPtr) node.buffer)->kind != kBTLeafNode || ((NodeDescPtr) node.buffer)->numRecords <= 0 ) { err = ReleaseNode (btreePtr, &node); M_ExitOnError (err); err = fsBTInvalidNodeErr; printf ("hfs: BTIterateRecord() found invalid btree node on volume %s\n", FCBTOVCB(filePtr)->vcbVN); hfs_mark_volume_inconsistent(FCBTOVCB(filePtr)); goto ErrorExit; } if (operation == kBTreeFirstRecord) index = 0; else index = ((BTNodeDescriptor*) node.buffer)->numRecords - 1; goto CopyData; //€€ is there a cleaner way? } //////////////////////// Find Iterator Position ///////////////////////////// // Not called for (operation == kBTreeFirstRecord || operation == kBTreeLastRecord) err = FindIteratorPosition (btreePtr, iterator, &left, &node, &right, &nodeNum, &index, &foundRecord); M_ExitOnError (err); ///////////////////// Find Next Or Previous Record ////////////////////////// if (operation == kBTreePrevRecord) { if (index > 0) { --index; } else { if (left.buffer == nil) { nodeNum = ((NodeDescPtr) node.buffer)->bLink; if ( nodeNum > 0) { // BTree nodes are always grabbed in left to right order. // Therefore release the current node before looking up the // left node. err = ReleaseNode(btreePtr, &node); M_ExitOnError(err); // Look up the left node err = GetNode (btreePtr, nodeNum, 0, &left); M_ExitOnError (err); // Look up the current node again err = GetRightSiblingNode (btreePtr, left.buffer, &node); M_ExitOnError (err); } else { err = fsBTStartOfIterationErr; goto ErrorExit; } } // Before we stomp on "right", we'd better release it if needed if (right.buffer != nil) { err = ReleaseNode(btreePtr, &right); M_ExitOnError(err); } right = node; node = left; left.buffer = nil; index = ((NodeDescPtr) node.buffer)->numRecords -1; } } else if (operation == kBTreeNextRecord) { if ((foundRecord != true) && (((NodeDescPtr) node.buffer)->fLink == 0) && (index == ((NodeDescPtr) node.buffer)->numRecords)) { err = fsBTEndOfIterationErr; goto ErrorExit; } // we did not find the record but the index is already positioned correctly if ((foundRecord == false) && (index != ((NodeDescPtr) node.buffer)->numRecords)) goto CopyData; // we found the record OR we have to look in the next node if (index < ((NodeDescPtr) node.buffer)->numRecords -1) { ++index; } else { if (right.buffer == nil) { nodeNum = ((NodeDescPtr) node.buffer)->fLink; if ( nodeNum > 0) { err = GetNode (btreePtr, nodeNum, 0, &right); M_ExitOnError (err); } else { err = fsBTEndOfIterationErr; goto ErrorExit; } } // Before we stomp on "left", we'd better release it if needed if (left.buffer != nil) { err = ReleaseNode(btreePtr, &left); M_ExitOnError(err); } left = node; node = right; right.buffer = nil; index = 0; } } else // operation == kBTreeCurrentRecord { // make sure we have something... if ((foundRecord != true) && (index >= ((NodeDescPtr) node.buffer)->numRecords)) { err = fsBTEndOfIterationErr; goto ErrorExit; } } //////////////////// Copy Record And Update Iterator //////////////////////// CopyData: // added check for errors err = GetRecordByIndex (btreePtr, node.buffer, index, &keyPtr, &recordPtr, &len); M_ExitOnError (err); if (recordLen != nil) *recordLen = len; if (record != nil) { ByteCount recordSize; recordSize = record->itemCount * record->itemSize; if (len > recordSize) len = recordSize; BlockMoveData (recordPtr, record->bufferAddress, len); } if (iterator != nil) // first & last do not require iterator { iterator->hint.writeCount = btreePtr->writeCount; iterator->hint.nodeNum = nodeNum; iterator->hint.index = index; iterator->hint.reserved1 = 0; iterator->hint.reserved2 = 0; iterator->version = 0; iterator->reserved = 0; /* SER * Check for infinite loops by making sure we do not * process more leaf records, than can possibly be (or the BTree header * is seriously damaged)....a brute force method. */ if ((operation == kBTreeFirstRecord) || (operation == kBTreeLastRecord)) iterator->hitCount = 1; else if (operation != kBTreeCurrentRecord) iterator->hitCount += 1; /* Always use the highest max, in case the grows while iterating */ iterator->maxLeafRecs = max(btreePtr->leafRecords, iterator->maxLeafRecs); #if 0 if (iterator->hitCount > iterator->maxLeafRecs + kNumLeafRecSlack) { err = fsBTInvalidNodeErr; printf ("hfs: BTIterateRecord() found invalid btree node on volume %s\n", FCBTOVCB(filePtr)->vcbVN); hfs_mark_volume_inconsistent(FCBTOVCB(filePtr)); goto ErrorExit; } #endif BlockMoveData ((Ptr)keyPtr, (Ptr)&iterator->key, CalcKeySize(btreePtr, keyPtr)); } ///////////////////////////// Release Nodes ///////////////////////////////// err = ReleaseNode (btreePtr, &node); M_ExitOnError (err); if (left.buffer != nil) { err = ReleaseNode (btreePtr, &left); M_ExitOnError (err); } if (right.buffer != nil) { err = ReleaseNode (btreePtr, &right); M_ExitOnError (err); } return noErr; /////////////////////// Error - Clean Up and Exit /////////////////////////// ErrorExit: (void) ReleaseNode (btreePtr, &left); (void) ReleaseNode (btreePtr, &node); (void) ReleaseNode (btreePtr, &right); if (recordLen != nil) *recordLen = 0; if (iterator != nil) { iterator->hint.writeCount = 0; iterator->hint.nodeNum = 0; iterator->hint.index = 0; iterator->hint.reserved1 = 0; iterator->hint.reserved2 = 0; iterator->version = 0; iterator->reserved = 0; iterator->key.length16 = 0; } if ( err == fsBTEmptyErr || err == fsBTEndOfIterationErr ) err = fsBTRecordNotFoundErr; return err; } /*------------------------------------------------------------------------------- Routine: BTIterateRecords Function: Find a series of records Input: filePtr - b-tree file operation - iteration operation (first,next,prev,last) iterator - pointer to iterator indicating start position callBackProc - pointer to routince to process a record callBackState - pointer to state data (used by callBackProc) Output: iterator - iterator is updated to indicate new position Result: noErr - success != noErr - failure -------------------------------------------------------------------------------*/ OSStatus BTIterateRecords(FCB *filePtr, BTreeIterationOperation operation, BTreeIterator *iterator, IterateCallBackProcPtr callBackProc, void * callBackState) { OSStatus err; BTreeControlBlockPtr btreePtr; BTreeKeyPtr keyPtr; RecordPtr recordPtr; u_int16_t len; Boolean foundRecord; u_int32_t nodeNum; BlockDescriptor left, node, right; u_int16_t index; ////////////////////////// Priliminary Checks /////////////////////////////// left.buffer = nil; left.blockHeader = nil; right.buffer = nil; right.blockHeader = nil; node.buffer = nil; node.blockHeader = nil; btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; REQUIRE_FILE_LOCK(btreePtr->fileRefNum, true); if ((operation != kBTreeFirstRecord) && (operation != kBTreeNextRecord) && (operation != kBTreeCurrentRecord) && (operation != kBTreePrevRecord) && (operation != kBTreeLastRecord)) { err = fsInvalidIterationMovmentErr; goto ErrorExit; } /////////////////////// Find First or Last Record /////////////////////////// if ((operation == kBTreeFirstRecord) || (operation == kBTreeLastRecord)) { if (operation == kBTreeFirstRecord) nodeNum = btreePtr->firstLeafNode; else nodeNum = btreePtr->lastLeafNode; if (nodeNum == 0) { err = fsBTEmptyErr; goto ErrorExit; } err = GetNode(btreePtr, nodeNum, 0, &node); M_ExitOnError(err); if ( ((NodeDescPtr)node.buffer)->kind != kBTLeafNode || ((NodeDescPtr)node.buffer)->numRecords <= 0 ) { err = ReleaseNode(btreePtr, &node); M_ExitOnError(err); err = fsBTInvalidNodeErr; printf ("hfs: BTIterateRecords() found invalid btree node on volume %s\n", FCBTOVCB(filePtr)->vcbVN); hfs_mark_volume_inconsistent(FCBTOVCB(filePtr)); goto ErrorExit; } if (operation == kBTreeFirstRecord) index = 0; else index = ((BTNodeDescriptor*) node.buffer)->numRecords - 1; goto ProcessData; } //////////////////////// Find Iterator Position ///////////////////////////// // Not called for (operation == kBTreeFirstRecord || operation == kBTreeLastRecord) err = FindIteratorPosition(btreePtr, iterator, &left, &node, &right, &nodeNum, &index, &foundRecord); if (err == fsBTRecordNotFoundErr) err = 0; M_ExitOnError(err); ///////////////////// Find Next Or Previous Record ////////////////////////// if (operation == kBTreePrevRecord) { if (index > 0) { --index; } else { if (left.buffer == nil) { nodeNum = ((NodeDescPtr) node.buffer)->bLink; if ( nodeNum > 0) { // BTree nodes are always grabbed in left to right order. // Therefore release the current node before looking up the // left node. err = ReleaseNode(btreePtr, &node); M_ExitOnError(err); // Look up the left node err = GetNode (btreePtr, nodeNum, 0, &left); M_ExitOnError (err); // Look up the current node again err = GetRightSiblingNode (btreePtr, left.buffer, &node); M_ExitOnError (err); } else { err = fsBTStartOfIterationErr; goto ErrorExit; } } // Before we stomp on "right", we'd better release it if needed if (right.buffer != nil) { err = ReleaseNode(btreePtr, &right); M_ExitOnError(err); } right = node; node = left; left.buffer = nil; index = ((NodeDescPtr) node.buffer)->numRecords -1; } } else if (operation == kBTreeNextRecord) { if ((foundRecord != true) && (((NodeDescPtr)node.buffer)->fLink == 0) && (index == ((NodeDescPtr)node.buffer)->numRecords)) { err = fsBTEndOfIterationErr; goto ErrorExit; } // we did not find the record but the index is already positioned correctly if ((foundRecord == false) && (index != ((NodeDescPtr)node.buffer)->numRecords)) goto ProcessData; // we found the record OR we have to look in the next node if (index < ((NodeDescPtr)node.buffer)->numRecords -1) { ++index; } else { if (right.buffer == nil) { nodeNum = ((NodeDescPtr)node.buffer)->fLink; if ( nodeNum > 0) { err = GetNode(btreePtr, nodeNum, 0, &right); M_ExitOnError(err); } else { err = fsBTEndOfIterationErr; goto ErrorExit; } } // Before we stomp on "left", we'd better release it if needed if (left.buffer != nil) { err = ReleaseNode(btreePtr, &left); M_ExitOnError(err); } left = node; node = right; right.buffer = nil; index = 0; } } else // operation == kBTreeCurrentRecord { // make sure we have something... if ((foundRecord != true) && (index >= ((NodeDescPtr)node.buffer)->numRecords)) { err = fsBTEndOfIterationErr; goto ErrorExit; } } //////////////////// Process Records Using Callback //////////////////////// ProcessData: err = GetRecordByIndex(btreePtr, node.buffer, index, &keyPtr, &recordPtr, &len); if (err) { err = btBadNode; goto ErrorExit; } while (err == 0) { if (callBackProc(keyPtr, recordPtr, callBackState) == 0) break; if ((index+1) < ((NodeDescPtr)node.buffer)->numRecords) { ++index; } else { if (right.buffer == nil) { nodeNum = ((NodeDescPtr)node.buffer)->fLink; if ( nodeNum > 0) { err = GetNode(btreePtr, nodeNum, 0, &right); M_ExitOnError(err); } else { err = fsBTEndOfIterationErr; break; } } // Before we stomp on "left", we'd better release it if needed if (left.buffer != nil) { err = ReleaseNode(btreePtr, &left); M_ExitOnError(err); } left = node; node = right; right.buffer = nil; index = 0; } err = GetRecordByIndex(btreePtr, node.buffer, index, &keyPtr, &recordPtr, &len); if (err) { err = btBadNode; goto ErrorExit; } } ///////////////// Update Iterator to Last Item Processed ///////////////////// if (iterator != nil) // first & last have optional iterator { iterator->hint.writeCount = btreePtr->writeCount; iterator->hint.nodeNum = nodeNum; iterator->hint.index = index; iterator->version = 0; BlockMoveData((Ptr)keyPtr, (Ptr)&iterator->key, CalcKeySize(btreePtr, keyPtr)); } M_ExitOnError(err); ///////////////////////////// Release Nodes ///////////////////////////////// err = ReleaseNode(btreePtr, &node); M_ExitOnError(err); if (left.buffer != nil) { err = ReleaseNode(btreePtr, &left); M_ExitOnError(err); } if (right.buffer != nil) { err = ReleaseNode(btreePtr, &right); M_ExitOnError(err); } return noErr; /////////////////////// Error - Clean Up and Exit /////////////////////////// ErrorExit: (void) ReleaseNode(btreePtr, &left); (void) ReleaseNode(btreePtr, &node); (void) ReleaseNode(btreePtr, &right); if (iterator != nil) { iterator->hint.writeCount = 0; iterator->hint.nodeNum = 0; iterator->hint.index = 0; iterator->version = 0; iterator->key.length16 = 0; } if ( err == fsBTEmptyErr || err == fsBTEndOfIterationErr ) err = fsBTRecordNotFoundErr; return err; } //////////////////////////////// BTInsertRecord ///////////////////////////////// OSStatus BTInsertRecord (FCB *filePtr, BTreeIterator *iterator, FSBufferDescriptor *record, u_int16_t recordLen ) { OSStatus err; BTreeControlBlockPtr btreePtr; TreePathTable treePathTable; u_int32_t nodesNeeded; BlockDescriptor nodeRec; u_int32_t insertNodeNum; u_int16_t index; Boolean recordFit; ////////////////////////// Priliminary Checks /////////////////////////////// nodeRec.buffer = nil; // so we can call ReleaseNode nodeRec.blockHeader = nil; err = CheckInsertParams (filePtr, iterator, record, recordLen); if (err != noErr) return err; btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; REQUIRE_FILE_LOCK(btreePtr->fileRefNum, false); ///////////////////////// Find Insert Position ////////////////////////////// // always call SearchTree for Insert err = SearchTree (btreePtr, &iterator->key, treePathTable, &insertNodeNum, &nodeRec, &index); switch (err) // set/replace/insert decision point { case noErr: err = fsBTDuplicateRecordErr; goto ErrorExit; case fsBTRecordNotFoundErr: break; case fsBTEmptyErr: // if tree empty add 1st leaf node if (btreePtr->freeNodes == 0) { err = ExtendBTree (btreePtr, btreePtr->totalNodes + 1); M_ExitOnError (err); } err = AllocateNode (btreePtr, &insertNodeNum); M_ExitOnError (err); err = GetNewNode (btreePtr, insertNodeNum, &nodeRec); M_ExitOnError (err); // XXXdbg ModifyBlockStart(btreePtr->fileRefNum, &nodeRec); ((NodeDescPtr)nodeRec.buffer)->kind = kBTLeafNode; ((NodeDescPtr)nodeRec.buffer)->height = 1; recordFit = InsertKeyRecord (btreePtr, nodeRec.buffer, 0, &iterator->key, KeyLength(btreePtr, &iterator->key), record->bufferAddress, recordLen ); if (recordFit != true) { err = fsBTRecordTooLargeErr; goto ErrorExit; } /* * Update the B-tree control block. Do this before * calling UpdateNode since it will compare the node's * height with treeDepth. */ btreePtr->treeDepth = 1; btreePtr->rootNode = insertNodeNum; btreePtr->firstLeafNode = insertNodeNum; btreePtr->lastLeafNode = insertNodeNum; err = UpdateNode (btreePtr, &nodeRec, 0, kLockTransaction); M_ExitOnError (err); M_BTreeHeaderDirty (btreePtr); goto Success; default: goto ErrorExit; } if (index > 0) { // XXXdbg ModifyBlockStart(btreePtr->fileRefNum, &nodeRec); recordFit = InsertKeyRecord (btreePtr, nodeRec.buffer, index, &iterator->key, KeyLength(btreePtr, &iterator->key), record->bufferAddress, recordLen); if (recordFit == true) { err = UpdateNode (btreePtr, &nodeRec, 0, kLockTransaction); M_ExitOnError (err); goto Success; } } /////////////////////// Extend File If Necessary //////////////////////////// if ((btreePtr->treeDepth + 1UL) > btreePtr->freeNodes) { nodesNeeded = btreePtr->treeDepth + 1 + btreePtr->totalNodes - btreePtr->freeNodes; if (nodesNeeded > CalcMapBits (btreePtr)) // we'll need to add a map node too! ++nodesNeeded; err = ExtendBTree (btreePtr, nodesNeeded); M_ExitOnError (err); } // no need to delete existing record err = InsertTree (btreePtr, treePathTable, &iterator->key, record->bufferAddress, recordLen, &nodeRec, index, 1, kInsertRecord, &insertNodeNum); M_ExitOnError (err); //////////////////////////////// Success //////////////////////////////////// Success: ++btreePtr->writeCount; ++btreePtr->leafRecords; M_BTreeHeaderDirty (btreePtr); // create hint iterator->hint.writeCount = btreePtr->writeCount; iterator->hint.nodeNum = insertNodeNum; iterator->hint.index = 0; // unused iterator->hint.reserved1 = 0; iterator->hint.reserved2 = 0; return noErr; ////////////////////////////// Error Exit /////////////////////////////////// ErrorExit: (void) ReleaseNode (btreePtr, &nodeRec); iterator->hint.writeCount = 0; iterator->hint.nodeNum = 0; iterator->hint.index = 0; iterator->hint.reserved1 = 0; iterator->hint.reserved2 = 0; if (err == fsBTEmptyErr) err = fsBTRecordNotFoundErr; return err; } //////////////////////////////// BTReplaceRecord //////////////////////////////// OSStatus BTReplaceRecord (FCB *filePtr, BTreeIterator *iterator, FSBufferDescriptor *record, u_int16_t recordLen ) { OSStatus err; BTreeControlBlockPtr btreePtr; TreePathTable treePathTable; u_int32_t nodesNeeded; BlockDescriptor nodeRec; u_int32_t insertNodeNum; u_int16_t index; Boolean recordFit; Boolean validHint; ////////////////////////// Priliminary Checks /////////////////////////////// nodeRec.buffer = nil; // so we can call ReleaseNode nodeRec.blockHeader = nil; err = CheckInsertParams (filePtr, iterator, record, recordLen); if (err != noErr) return err; btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; REQUIRE_FILE_LOCK(btreePtr->fileRefNum, false); ////////////////////////////// Take A Hint ////////////////////////////////// err = IsItAHint (btreePtr, iterator, &validHint); M_ExitOnError (err); if (validHint) { insertNodeNum = iterator->hint.nodeNum; err = GetNode (btreePtr, insertNodeNum, kGetNodeHint, &nodeRec); if( err == noErr ) { // XXXdbg ModifyBlockStart(btreePtr->fileRefNum, &nodeRec); err = TrySimpleReplace (btreePtr, nodeRec.buffer, iterator, record, recordLen, &recordFit); M_ExitOnError (err); if (recordFit) { err = UpdateNode (btreePtr, &nodeRec, 0, 0); M_ExitOnError (err); ++btreePtr->numValidHints; goto Success; } else { (void) BTInvalidateHint( iterator ); } err = ReleaseNode (btreePtr, &nodeRec); M_ExitOnError (err); } else { (void) BTInvalidateHint( iterator ); } } ////////////////////////////// Get A Clue /////////////////////////////////// err = SearchTree (btreePtr, &iterator->key, treePathTable, &insertNodeNum, &nodeRec, &index); M_ExitOnError (err); // record must exit for Replace // optimization - if simple replace will work then don't extend btree // €€ if we tried this before, and failed because it wouldn't fit then we shouldn't try this again... // XXXdbg ModifyBlockStart(btreePtr->fileRefNum, &nodeRec); err = TrySimpleReplace (btreePtr, nodeRec.buffer, iterator, record, recordLen, &recordFit); M_ExitOnError (err); if (recordFit) { err = UpdateNode (btreePtr, &nodeRec, 0, 0); M_ExitOnError (err); goto Success; } //////////////////////////// Make Some Room ///////////////////////////////// if ((btreePtr->treeDepth + 1UL) > btreePtr->freeNodes) { nodesNeeded = btreePtr->treeDepth + 1 + btreePtr->totalNodes - btreePtr->freeNodes; if (nodesNeeded > CalcMapBits (btreePtr)) // we'll need to add a map node too! ++nodesNeeded; err = ExtendBTree (btreePtr, nodesNeeded); M_ExitOnError (err); } // XXXdbg ModifyBlockStart(btreePtr->fileRefNum, &nodeRec); DeleteRecord (btreePtr, nodeRec.buffer, index); // delete existing key/record err = InsertTree (btreePtr, treePathTable, &iterator->key, record->bufferAddress, recordLen, &nodeRec, index, 1, kReplaceRecord, &insertNodeNum); M_ExitOnError (err); ++btreePtr->writeCount; /* writeCount changes only if the tree structure changed */ Success: // create hint iterator->hint.writeCount = btreePtr->writeCount; iterator->hint.nodeNum = insertNodeNum; iterator->hint.index = 0; // unused iterator->hint.reserved1 = 0; iterator->hint.reserved2 = 0; return noErr; ////////////////////////////// Error Exit /////////////////////////////////// ErrorExit: (void) ReleaseNode (btreePtr, &nodeRec); iterator->hint.writeCount = 0; iterator->hint.nodeNum = 0; iterator->hint.index = 0; iterator->hint.reserved1 = 0; iterator->hint.reserved2 = 0; return err; } //////////////////////////////// BTUpdateRecord //////////////////////////////// OSStatus BTUpdateRecord(FCB *filePtr, BTreeIterator *iterator, IterateCallBackProcPtr callBackProc, void * callBackState) { OSStatus err; BTreeControlBlockPtr btreePtr; TreePathTable treePathTable; BlockDescriptor nodeRec; RecordPtr recordPtr; BTreeKeyPtr keyPtr; u_int32_t insertNodeNum; u_int16_t recordLen; u_int16_t index; Boolean validHint; ////////////////////////// Priliminary Checks /////////////////////////////// nodeRec.buffer = nil; // so we can call ReleaseNode nodeRec.blockHeader = nil; btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; REQUIRE_FILE_LOCK(btreePtr->fileRefNum, true); ////////////////////////////// Take A Hint ////////////////////////////////// err = IsItAHint (btreePtr, iterator, &validHint); M_ExitOnError (err); if (validHint) { insertNodeNum = iterator->hint.nodeNum; err = GetNode (btreePtr, insertNodeNum, kGetNodeHint, &nodeRec); if (err == noErr) { if (((NodeDescPtr)nodeRec.buffer)->kind == kBTLeafNode && SearchNode (btreePtr, nodeRec.buffer, &iterator->key, &index)) { err = GetRecordByIndex(btreePtr, nodeRec.buffer, index, &keyPtr, &recordPtr, &recordLen); M_ExitOnError (err); // XXXdbg ModifyBlockStart(btreePtr->fileRefNum, &nodeRec); err = callBackProc(keyPtr, recordPtr, callBackState); M_ExitOnError (err); err = UpdateNode (btreePtr, &nodeRec, 0, 0); M_ExitOnError (err); ++btreePtr->numValidHints; goto Success; } else { (void) BTInvalidateHint( iterator ); } err = ReleaseNode (btreePtr, &nodeRec); M_ExitOnError (err); } else { (void) BTInvalidateHint( iterator ); } } ////////////////////////////// Get A Clue /////////////////////////////////// err = SearchTree (btreePtr, &iterator->key, treePathTable, &insertNodeNum, &nodeRec, &index); M_ExitOnError (err); err = GetRecordByIndex(btreePtr, nodeRec.buffer, index, &keyPtr, &recordPtr, &recordLen); M_ExitOnError (err); // XXXdbg ModifyBlockStart(btreePtr->fileRefNum, &nodeRec); err = callBackProc(keyPtr, recordPtr, callBackState); M_ExitOnError (err); err = UpdateNode (btreePtr, &nodeRec, 0, 0); M_ExitOnError (err); Success: // create hint iterator->hint.writeCount = btreePtr->writeCount; iterator->hint.nodeNum = insertNodeNum; iterator->hint.index = 0; iterator->hint.reserved1 = 0; iterator->hint.reserved2 = 0; return noErr; ////////////////////////////// Error Exit /////////////////////////////////// ErrorExit: (void) ReleaseNode (btreePtr, &nodeRec); iterator->hint.writeCount = 0; iterator->hint.nodeNum = 0; iterator->hint.index = 0; iterator->hint.reserved1 = 0; iterator->hint.reserved2 = 0; return err; } //////////////////////////////// BTDeleteRecord ///////////////////////////////// OSStatus BTDeleteRecord (FCB *filePtr, BTreeIterator *iterator ) { OSStatus err; BTreeControlBlockPtr btreePtr; TreePathTable treePathTable; BlockDescriptor nodeRec; u_int32_t nodesNeeded; u_int32_t nodeNum; u_int16_t index; ////////////////////////// Priliminary Checks /////////////////////////////// nodeRec.buffer = nil; // so we can call ReleaseNode nodeRec.blockHeader = nil; M_ReturnErrorIf (filePtr == nil, paramErr); M_ReturnErrorIf (iterator == nil, paramErr); btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; if (btreePtr == nil) { err = fsBTInvalidFileErr; goto ErrorExit; } REQUIRE_FILE_LOCK(btreePtr->fileRefNum, false); /////////////////////////////// Find Key //////////////////////////////////// // check hint for simple delete case (index > 0, numRecords > 2) err = SearchTree (btreePtr, &iterator->key, treePathTable, &nodeNum, &nodeRec, &index); M_ExitOnError (err); // record must exit for Delete /////////////////////// Extend File If Necessary //////////////////////////// if ((btreePtr->treeDepth + 1UL) > btreePtr->totalNodes) { nodesNeeded = btreePtr->treeDepth + 1 + btreePtr->totalNodes; if (nodesNeeded > CalcMapBits (btreePtr)) ++nodesNeeded; err = ExtendBTree (btreePtr, nodesNeeded); M_ExitOnError (err); } ///////////////////////////// Delete Record ///////////////////////////////// err = DeleteTree (btreePtr, treePathTable, &nodeRec, index, 1); M_ExitOnError (err); ++btreePtr->writeCount; --btreePtr->leafRecords; M_BTreeHeaderDirty (btreePtr); iterator->hint.nodeNum = 0; return noErr; ////////////////////////////// Error Exit /////////////////////////////////// ErrorExit: (void) ReleaseNode (btreePtr, &nodeRec); return err; } OSStatus BTGetInformation (FCB *filePtr, u_int16_t file_version, BTreeInfoRec *info ) { #pragma unused (file_version) BTreeControlBlockPtr btreePtr; M_ReturnErrorIf (filePtr == nil, paramErr); btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; /* * XXX SER * This should not require the whole tree to be locked, just maybe the BTreeControlBlockPtr * * REQUIRE_FILE_LOCK(btreePtr->fileRefNum, true); */ M_ReturnErrorIf (btreePtr == nil, fsBTInvalidFileErr); M_ReturnErrorIf (info == nil, paramErr); //€€ check version? info->nodeSize = btreePtr->nodeSize; info->maxKeyLength = btreePtr->maxKeyLength; info->treeDepth = btreePtr->treeDepth; info->numRecords = btreePtr->leafRecords; info->numNodes = btreePtr->totalNodes; info->numFreeNodes = btreePtr->freeNodes; info->lastfsync = btreePtr->lastfsync; info->keyCompareType = btreePtr->keyCompareType; return noErr; } // XXXdbg __private_extern__ OSStatus BTIsDirty(FCB *filePtr) { BTreeControlBlockPtr btreePtr; btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; return TreeIsDirty(btreePtr); } /*------------------------------------------------------------------------------- Routine: BTFlushPath - Flush BTreeControlBlock to Header Node. Function: Brief_description_of_the_function_and_any_side_effects Input: pathPtr - pointer to path control block for B*Tree file to flush Output: none Result: noErr - success != noErr - failure -------------------------------------------------------------------------------*/ OSStatus BTFlushPath (FCB *filePtr) { OSStatus err; BTreeControlBlockPtr btreePtr; M_ReturnErrorIf (filePtr == nil, paramErr); btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; M_ReturnErrorIf (btreePtr == nil, fsBTInvalidFileErr); REQUIRE_FILE_LOCK(btreePtr->fileRefNum, true); err = UpdateHeader (btreePtr, false); return err; } /*------------------------------------------------------------------------------- Routine: BTReload - Reload B-tree Header Data. Function: Reload B-tree header data from disk. This is called after fsck has made repairs to the root filesystem. The filesystem is mounted read-only when BTReload is caled. Input: filePtr - the B*Tree file that needs its header updated Output: none Result: noErr - success != noErr - failure -------------------------------------------------------------------------------*/ OSStatus BTReloadData(FCB *filePtr) { OSStatus err; BTreeControlBlockPtr btreePtr; BlockDescriptor node; BTHeaderRec *header; node.buffer = nil; node.blockHeader = nil; btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; if (btreePtr == nil) return (fsBTInvalidFileErr); REQUIRE_FILE_LOCK(btreePtr->fileRefNum, false); err = GetNode(btreePtr, kHeaderNodeNum, 0, &node); if (err != noErr) return (err); header = (BTHeaderRec*)((char *)node.buffer + sizeof(BTNodeDescriptor)); if ((err = VerifyHeader (filePtr, header)) == 0) { btreePtr->treeDepth = header->treeDepth; btreePtr->rootNode = header->rootNode; btreePtr->leafRecords = header->leafRecords; btreePtr->firstLeafNode = header->firstLeafNode; btreePtr->lastLeafNode = header->lastLeafNode; btreePtr->maxKeyLength = header->maxKeyLength; btreePtr->totalNodes = header->totalNodes; btreePtr->freeNodes = header->freeNodes; btreePtr->btreeType = header->btreeType; btreePtr->flags &= (~kBTHeaderDirty); } (void) ReleaseNode(btreePtr, &node); return err; } /*------------------------------------------------------------------------------- Routine: BTInvalidateHint - Invalidates the hint within a BTreeInterator. Function: Invalidates the hint within a BTreeInterator. Input: iterator - pointer to BTreeIterator Output: iterator - iterator with the hint.nodeNum cleared Result: noErr - success paramErr - iterator == nil -------------------------------------------------------------------------------*/ OSStatus BTInvalidateHint (BTreeIterator *iterator ) { if (iterator == nil) return paramErr; iterator->hint.nodeNum = 0; return noErr; } /*------------------------------------------------------------------------------- Routine: BTGetLastSync Function: Returns the last time that this btree was flushed, does not include header. Input: filePtr - pointer file control block Output: lastfsync - time in seconds of last update Result: noErr - success paramErr - iterator == nil -------------------------------------------------------------------------------*/ OSStatus BTGetLastSync (FCB *filePtr, u_int32_t *lastsync) { BTreeControlBlockPtr btreePtr; M_ReturnErrorIf (filePtr == nil, paramErr); btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; /* Maybe instead of requiring a lock..an atomic set might be more appropriate */ REQUIRE_FILE_LOCK(btreePtr->fileRefNum, true); M_ReturnErrorIf (btreePtr == nil, fsBTInvalidFileErr); M_ReturnErrorIf (lastsync == nil, paramErr); *lastsync = btreePtr->lastfsync; return noErr; } /*------------------------------------------------------------------------------- Routine: BTSetLastSync Function: Sets the last time that this btree was flushed, does not include header. Input: fcb - pointer file control block Output: lastfsync - time in seconds of last update Result: noErr - success paramErr - iterator == nil -------------------------------------------------------------------------------*/ OSStatus BTSetLastSync (FCB *filePtr, u_int32_t lastsync) { BTreeControlBlockPtr btreePtr; M_ReturnErrorIf (filePtr == nil, paramErr); btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; /* Maybe instead of requiring a lock..an atomic set might be more appropriate */ REQUIRE_FILE_LOCK(btreePtr->fileRefNum, true); M_ReturnErrorIf (btreePtr == nil, fsBTInvalidFileErr); M_ReturnErrorIf (lastsync == 0, paramErr); btreePtr->lastfsync = lastsync; return noErr; } __private_extern__ OSStatus BTHasContiguousNodes (FCB *filePtr) { BTreeControlBlockPtr btreePtr; M_ReturnErrorIf (filePtr == nil, paramErr); btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; REQUIRE_FILE_LOCK(btreePtr->fileRefNum, true); M_ReturnErrorIf (btreePtr == nil, fsBTInvalidFileErr); return NodesAreContiguous(FCBTOVCB(filePtr), filePtr, btreePtr->nodeSize); } /*------------------------------------------------------------------------------- Routine: BTGetUserData Function: Read the user data area of the b-tree header node. -------------------------------------------------------------------------------*/ __private_extern__ OSStatus BTGetUserData(FCB *filePtr, void * dataPtr, int dataSize) { BTreeControlBlockPtr btreePtr; BlockDescriptor node; char * offset; OSStatus err; if (dataSize > kBTreeHeaderUserBytes) return (EINVAL); node.buffer = nil; node.blockHeader = nil; btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; if (btreePtr == nil) return (fsBTInvalidFileErr); REQUIRE_FILE_LOCK(btreePtr->fileRefNum, false); err = GetNode(btreePtr, kHeaderNodeNum, 0, &node); if (err) return (err); offset = (char *)node.buffer + sizeof(BTNodeDescriptor) + sizeof(BTHeaderRec); bcopy(offset, dataPtr, dataSize); (void) ReleaseNode(btreePtr, &node); return (0); } /*------------------------------------------------------------------------------- Routine: BTSetUserData Function: Write the user data area of the b-tree header node. -------------------------------------------------------------------------------*/ __private_extern__ OSStatus BTSetUserData(FCB *filePtr, void * dataPtr, int dataSize) { BTreeControlBlockPtr btreePtr; BlockDescriptor node; char * offset; OSStatus err; if (dataSize > kBTreeHeaderUserBytes) return (EINVAL); node.buffer = nil; node.blockHeader = nil; btreePtr = (BTreeControlBlockPtr) filePtr->fcbBTCBPtr; if (btreePtr == nil) return (fsBTInvalidFileErr); REQUIRE_FILE_LOCK(btreePtr->fileRefNum, false); err = GetNode(btreePtr, kHeaderNodeNum, 0, &node); if (err) return (err); ModifyBlockStart(btreePtr->fileRefNum, &node); offset = (char *)node.buffer + sizeof(BTNodeDescriptor) + sizeof(BTHeaderRec); bcopy(dataPtr, offset, dataSize); err = UpdateNode (btreePtr, &node, 0, 0); return (err); }