/* * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This 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 OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* File: Catalog.c Contains: Catalog Manager Implementation Version: HFS Plus 1.0 Copyright: © 1996-2000 by Apple Computer, Inc., all rights reserved. File Ownership: DRI: Don Brady Other Contact: Mark Day Technology: xxx put technology here xxx Writers: (msd) Mark Day (DSH) Deric Horn (djb) Don Brady Change History (most recent first): 2/2/99 djb Fix CreateFileIDRef to copy entire name when creating thread record. 1/7/99 djb Use a max bytes of 256 in calls to ConvertUnicodeToUTF8. 12/9/98 djb UpdateCatalogNode only updates vcbLsMod if contentModDate changes. 11/5/98 djb Add support for UTF-8 names. 8/31/98 djb GetTimeLocal now takes an input. 7/8/98 ser Added accessDate and AttributeModDate init. to create routine. 6/5/98 djb Added CreateFileIDRef routine. 6/3/98 djb Merge MoveCatalogRecord and RenameCatalogRecord into one routine. 4/17/98 djb Add VCB locking. 4/6/98 djb Catalog iterators now need to be released. 4/6/98 djb Removed CreateVolumeCatalogCache and DisposeVolumeCatalogCache (obsolete). 3/31/98 djb Make UpdateCatalogNode interface thread-safe. 3/31/98 djb Sync up with final HFSVolumes.h header file. 3/17/98 djb Fixed CreateCatalogNode interface to take kCatalogFolderNode and kCatalogFileNode as type input. 12/10/97 DSH 2201501, UpdateCatalogNode to only update CatalogRecords which are under 2 Gig by checking the overloaded valence field. 11/20/97 djb Radar #2002357. Fixing retry mechanism. 11/17/97 djb PrepareInputName routine now returns an error. 11/13/97 djb Radar #1683572. Add new GetCatalogOffspringFile routine for PBGetFileInfo calls (support used to be in HFSPathnameCalls.a). 11/7/97 msd Change calls to the wrapper routine CompareUnicodeNames() to use the underlying routine FastUnicodeCompare() instead. 10/19/97 msd Bug 1684586. GetCatInfo and SetCatInfo use only contentModDate. 10/17/97 djb Change Catalog Create/Rename to use ConvertInputNameToUnicode. 10/13/97 djb Update volumeNameEncodingHint when changing volume name. Change name of GetSystemTextEncoding to GetDefaultTextEncoding. 10/1/97 djb Add new catalog iterators and node cache to improve performance. 9/12/97 msd In CreateCatalogNode, make sure parent is a folder, not a file. 9/10/97 msd In RenameCatalogNodeUnicode, remove HFS-only code and make sure the conversion context is set up and marked in the volume's bitmap. 9/9/97 DSH Added RelString_Glue to avoid having to link DFAEngine with Interface.o 9/8/97 msd Make sure a folder's modifyDate is set whenever its contentModDate is set. In UpdateCatalogNode, make sure the modifyDate is greater or equal to contentModDate; do a DebugStr only for debug builds. 9/7/97 djb Make some DebuStrs HFS_DIAGNOSTIC only. 9/4/97 djb Add more Catalog Iterators, Instrument RelString. 9/4/97 msd Remove call to PropertyDeleteObject. 8/18/97 DSH Use RelString instead of FastRelString in DFA to avoid loading branch island instead of table. 8/14/97 djb Remove hard link support. Switch over to FastRelString. 8/8/97 djb Fixed bugs in LinkCatalogNode. 8/5/97 djb Don't restore vcbNxtCNID if thread exists (radar #1670614). 7/25/97 DSH Pass heuristicHint to BTSearchRecord from GetCatalogOffspring. 7/18/97 msd Include LowMemPriv.h. In LinkCatalogNode, now sets the kInsertedFileThread2 flag correctly; should only affect error recovery code. 7/16/97 DSH FilesInternal.i renamed FileMgrInternal.i to avoid name collision 7/8/97 DSH Loading PrecompiledHeaders from define passed in on C line 6/27/97 msd Add PBLongRename SPI. Added RenameCatalogNodeUnicode call, which takes Unicode names for HFS Plus volumes. Removed calls to Attributes module when creating, renaming or moving nodes. 6/24/97 djb Validate the mangled name matches in LocateCatalogNodeByMangledName. 6/24/97 djb Add hard link support. 6/20/97 msd Use contentModDate and attributeModDate fields instead of modifyDate. Made CopyCatalogNodeData public. 6/18/97 djb Add routines LocateCatalogNodeWithRetry & UpdateVolumeEncodings. Add mangled name retry to DeleteCatalogNode, MoveCatalogNode and RenameCatalogNode. 6/13/97 djb Major changes for longname support and multiple scripts. 6/9/97 msd Instead of calling GetDateTime, call GetTimeUTC or GetTimeLocal. Dates on an HFS Plus volume need to be converted to/from UTC. 6/4/97 djb Set textEncoding hint in Rename and Create. TrashCatalogIterator was not always called with the correct folder ID. 5/21/97 djb Turn off recursive iterators. 5/19/97 djb Add support for B-tree iterators to GetCatalogOffspring. 5/9/97 djb Get in sync with FilesInternal.i. 4/24/97 djb First checked into Common System Project. 4/11/97 DSH Use extended VCB fields catalogRefNum, and extentsRefNum. 4/4/97 djb Get in sync with volume format changes. 3/31/97 djb Additional HFS Plus optimization added to GetCatalogNode. 3/28/97 djb Add Optimization to GetCatalogNode. 3/27/97 djb Unicode conversion routines now use byte counts. 3/17/97 DSH Casting to compile with SC, GetRecordSize -> GetCatalogRecordSize, moved some prototypes to extern. 3/5/97 msd Add calls to Property Manager when catalog entries are created, deleted, moved, renamed. 2/19/97 djb HFS Plus catalog keys no longer have a pad word. 1/24/97 DSH (djb) GetCatalogOffSpring() fix volume->vcbDirIDM = 0 1/23/97 DSH Truncate name to CMMaxCName characters in PrepareInputName(). 1/14/97 djb Fixed RenameCatalogNode for case when just a cnid is passed. 1/13/97 djb Added support for varaible sized thread records in HFS+. 1/11/97 DSH Moving PrepareInputName() declaration fo FilesInternal.h 1/10/97 djb CopyCatalogNodeData was trashing the resource extents on HFS+. 1/10/97 djb CopyCatalogNodeData was trashing dataLogicalSize on HFS+ disks. 1/9/97 djb Get in sync with new HFSVolumesPriv.i. 1/6/97 djb Added name length checking to CompareExtendedCatalogKeys. Fixed GetCatalogOffspring - it was not correctly passing the HFS+ flag to PrepareOutputName. Fixed BuildKey for HFS+ keys. 1/3/97 djb Fixed termination bug in GetCatalogOffspring. Added support for large keys. Integrated latest HFSVolumesPriv.h changes. 12/19/96 DSH Changed call from C_FlushMDB to HFS+ savy FlushVolumeControlBlock() 12/19/96 djb Add new B-tree manager... 12/13/96 djb Fixing bugs for HFS+. Switch to HFSUnicodeWrappers routines. 12/12/96 djb Changed the SPI for GetCatalogNode, GetCatalogOffspring, and UpdateCatalogNode. 12/12/96 DSH Removed static function declarations for functions used by FileIDServices.c. 11/11/96 djb Added support for HFS+ Unicode names. Major changes throughout. 11/4/96 djb Added FSSpec output to GetCatalogNode and GetCatalogOffspring routines. 10/29/96 djb first checked in */ #pragma segment Catalog #include #include #include "../../hfs_endian.h" #include "../headers/FileMgrInternal.h" #include "../headers/BTreesInternal.h" #include "../headers/CatalogPrivate.h" #include "../headers/HFSUnicodeWrappers.h" #include "../headers/HFSInstrumentation.h" // External routines extern SInt32 FastRelString( ConstStr255Param str1, ConstStr255Param str2 ); extern SInt16 RelString_Glue(StringPtr pStr1, StringPtr pStr2); // Internal routines static OSErr IterateCatalogNode(ExtendedVCB *volume, CatalogIterator *catalogIterator, UInt16 index, CatalogNodeData *nodeData, HFSCatalogNodeID *nodeID, SInt16 *nodeType); void InitCatalogThreadRecord(ExtendedVCB *volume, UInt32 nodeType, CatalogKey *nodeKey, CatalogRecord *record, UInt32 *threadSize); void InitCatalogRecord(ExtendedVCB *volume, UInt32 nodeType, UInt32 textEncoding, CatalogRecord *record, UInt32 *recordSize, HFSCatalogNodeID catalogNodeID); #if HFS_DIAGNOSTIC #include #define PRINTIT(A) kprintf A; #else #define PRINTIT(A) #endif /* HFS_DIAGNOSTIC */ //_________________________________________________________________________________ // Exported Routines // // CreateCatalogNode - Creates a new folder or file CNode. // DeleteCatalogNode - Deletes an existing folder or file CNode. // GetCatalogNode - Locates an existing folder or file CNode. // GetCatalogOffspringFile - Gets an offspring file record from a folder. // GetCatalogOffspring - Gets an offspring record from a folder. // MoveRenameCatalogNode - Moves/Renames an existing folder or file CNode. // UpdateCatalogNode - Marks a Catalog BTree node as 'dirty'. // CreateFileIDRef - Creates a file thread record for hfs file node // CompareCatalogKeys - Compares two catalog keys. // //_________________________________________________________________________________ //_________________________________________________________________________________ // // About date/time values: // // Date/time values stored in control blocks and generic structures (such as // CatalogNodeData) are always stored in local time. Values stored in HFS volume // format structures (such as B-tree records) are also stored in local time. // Values stored in HFS Plus format structures are stored in UTC. //_________________________________________________________________________________ // Implementation //_________________________________________________________________________________ // Routine: CreateCatalogNode // // Function: Creates a new folder or file CNode. A new folder or file // record is added to the catalog BTree. If a folder CNode is // being created, a new thread record is also added. // //_________________________________________________________________________________ OSErr CreateCatalogNode ( ExtendedVCB *volume, HFSCatalogNodeID parentID, ConstUTF8Param name, UInt32 nodeType, HFSCatalogNodeID *catalogNodeID, UInt32 *catalogHint) { CatalogKey *nodeKey; CatalogRecord nodeData; // 520 bytes UInt32 nodeDataSize; CatalogRecord parentThreadData; // 520 bytes HFSCatalogNodeID parentsParentID; CatalogName *parentNamePtr; UInt32 tempHint; UInt32 textEncoding; UInt16 tempSize; OSErr result; Boolean isHFSPlus = (volume->vcbSigWord == kHFSPlusSigWord); FCB *fcb; FSBufferDescriptor btRecord; BTreeIterator iterator; BTreeIterator threadIter; HFSCatalogNodeID nextCNID; if (nodeType != kCatalogFolderNode && nodeType != kCatalogFileNode) return paramErr; fcb = GetFileControlBlock(volume->catalogRefNum); nodeKey = (CatalogKey *) &iterator.key; //--- make sure parent exists (by locating the parent's thread record) result = LocateCatalogThread(volume, parentID, &parentThreadData, &tempSize, &tempHint); ReturnIfError(result); TrashCatalogIterator(volume, parentID); // invalidate any iterators for this parentID // save copy of parent's parentID and name. if (isHFSPlus) { if (parentThreadData.recordType != kHFSPlusFolderThreadRecord) return dirNFErr; parentsParentID = parentThreadData.hfsPlusThread.parentID; parentNamePtr = (CatalogName*) &parentThreadData.hfsPlusThread.nodeName; } else { if (parentThreadData.recordType != kHFSFolderThreadRecord) return dirNFErr; parentsParentID = parentThreadData.hfsThread.parentID; parentNamePtr = (CatalogName*) &parentThreadData.hfsThread.nodeName; } // invalidate cache for parent since its about to change InvalidateCatalogNodeCache(volume, parentsParentID); //--- build key for new catalog node result = BuildCatalogKeyUTF8(volume, parentID, name, kUndefinedStrLen, nodeKey, &textEncoding); ReturnIfError(result); /* make sure it doesn't exist */ result = BTSearchRecord(fcb, &iterator, kInvalidMRUCacheKey, NULL, NULL, &iterator); if (result != btNotFound) return (cmExists); nextCNID = volume->vcbNxtCNID; if (!isHFSPlus && nextCNID == 0xFFFFFFFF) return (dskFulErr); //--- build thread record for new CNode if (isHFSPlus || nodeType == kCatalogFolderNode) { CatalogRecord threadData; // 520 bytes UInt32 threadSize; btRecord.bufferAddress = &threadData; btRecord.itemSize = threadSize; btRecord.itemCount = 1; InitCatalogThreadRecord(volume, nodeType, nodeKey, &threadData, &threadSize); TryNextID: BuildCatalogKey(nextCNID, NULL, isHFSPlus, (CatalogKey*) &threadIter.key); result = BTInsertRecord(fcb, &threadIter, &btRecord, threadSize); if (result == btExists && isHFSPlus) { /* * Allow CNIDs on HFS Plus volumes to wrap around */ ++nextCNID; if (nextCNID < kHFSFirstUserCatalogNodeID) { volume->vcbAtrb |= kHFSCatalogNodeIDsReusedMask; volume->vcbFlags |= 0xFF00; nextCNID = kHFSFirstUserCatalogNodeID; } goto TryNextID; } ReturnIfError(result); } //--- initialize catalog data record (for folder or file) btRecord.bufferAddress = &nodeData; btRecord.itemSize = nodeDataSize; btRecord.itemCount = 1; InitCatalogRecord(volume, nodeType, textEncoding, &nodeData, &nodeDataSize, nextCNID); //--- add new folder/file record to catalog BTree result = BTInsertRecord(fcb, &iterator, &btRecord, nodeDataSize ); if (result) { if (result == btExists) result = cmExists; if (isHFSPlus || nodeType == kCatalogFolderNode) (void) BTDeleteRecord(fcb, &threadIter); return result; } /* * Return the CNID actually used. Update the volume's next ID. */ *catalogNodeID = nextCNID; if (++nextCNID < kHFSFirstUserCatalogNodeID) { volume->vcbAtrb |= kHFSCatalogNodeIDsReusedMask; volume->vcbFlags |= 0xFF00; nextCNID = kHFSFirstUserCatalogNodeID; } volume->vcbNxtCNID = nextCNID; //--- update counters... result = UpdateFolderCount( volume, parentsParentID, parentNamePtr, nodeData.recordType, kNoHint, +1); ReturnIfError(result); /* XXX what about cleanup ??? */ AdjustVolumeCounts(volume, nodeData.recordType, +1); result = FlushCatalog(volume); return result; } // end CreateCatalogNode /* * initialize catalog data record (for folder or file) */ void InitCatalogRecord(ExtendedVCB *volume, UInt32 nodeType, UInt32 textEncoding, CatalogRecord *record, UInt32 *recordSize, HFSCatalogNodeID nodeID) { UInt32 timeStamp; ClearMemory(record, sizeof(CatalogRecord)); // first clear the record if (volume->vcbSigWord == kHFSPlusSigWord) { timeStamp = GetTimeUTC(); // get current date/time (universal) UpdateVolumeEncodings(volume, textEncoding); if (nodeType == kCatalogFolderNode ) { record->recordType = kHFSPlusFolderRecord; record->hfsPlusFolder.folderID = nodeID; record->hfsPlusFolder.createDate = timeStamp; record->hfsPlusFolder.contentModDate = timeStamp; record->hfsPlusFolder.accessDate = timeStamp; record->hfsPlusFolder.attributeModDate = timeStamp; record->hfsPlusFolder.textEncoding = textEncoding; *recordSize = sizeof(HFSPlusCatalogFolder); // threadType = kHFSPlusFolderThreadRecord; } else if (nodeType == kCatalogFileNode ) { record->recordType = kHFSPlusFileRecord; record->hfsPlusFile.fileID = nodeID; record->hfsPlusFile.createDate = timeStamp; record->hfsPlusFile.contentModDate = timeStamp; record->hfsPlusFile.accessDate = timeStamp; record->hfsPlusFile.attributeModDate = timeStamp; record->hfsPlusFile.flags |= kHFSThreadExistsMask; record->hfsPlusFile.textEncoding = textEncoding; *recordSize = sizeof(HFSPlusCatalogFile); // threadType = kHFSPlusFileThreadRecord; } } else /* standard hfs */ { timeStamp = GetTimeLocal(true); // get current local date/time if (nodeType == kCatalogFolderNode ) { record->recordType = kHFSFolderRecord; record->hfsFolder.folderID = nodeID; record->hfsFolder.createDate = timeStamp; record->hfsFolder.modifyDate = timeStamp; *recordSize = sizeof(HFSCatalogFolder); // threadType = kHFSFolderThreadRecord; } else if (nodeType == kCatalogFileNode ) { record->recordType = kHFSFileRecord; record->hfsFile.fileID = nodeID; record->hfsFile.createDate = timeStamp; record->hfsFile.modifyDate = timeStamp; *recordSize = sizeof(HFSCatalogFile); } } } void InitCatalogThreadRecord(ExtendedVCB *volume, UInt32 nodeType, CatalogKey *nodeKey, CatalogRecord *record, UInt32 *threadSize) { ClearMemory(record, sizeof(CatalogRecord) ); // first clear the record if (volume->vcbSigWord == kHFSPlusSigWord) { if (nodeType == kCatalogFolderNode) record->recordType = kHFSPlusFolderThreadRecord; else record->recordType = kHFSPlusFileThreadRecord; record->hfsPlusThread.parentID = nodeKey->hfsPlus.parentID; *threadSize = sizeof(record->hfsPlusThread); // HFS Plus has varaible sized threads so adjust to actual length *threadSize -= ( sizeof(record->hfsPlusThread.nodeName.unicode) - (nodeKey->hfsPlus.nodeName.length * sizeof(UniChar)) ); BlockMoveData(&nodeKey->hfsPlus.nodeName, &record->hfsPlusThread.nodeName, sizeof(UniChar) * (nodeKey->hfsPlus.nodeName.length + 1)); } else // classic HFS { if (nodeType == kCatalogFolderNode) record->recordType = kHFSFolderThreadRecord; else record->recordType = kHFSFileThreadRecord; record->hfsThread.parentID = nodeKey->hfs.parentID; *threadSize = sizeof(record->hfsThread); BlockMoveData(&nodeKey->hfs.nodeName, &record->hfsThread.nodeName, nodeKey->hfs.nodeName[0] + 1); } } //_________________________________________________________________________________ // Routine: DeleteCatalogNode // // Function: Deletes an existing folder or file CNode. The thread record // is also deleted for directories and files that have thread // records. // // The valence for a folder must be zero before it can be deleted. // The rootfolder cannot be deleted. // //_________________________________________________________________________________ OSErr DeleteCatalogNode(ExtendedVCB *volume, HFSCatalogNodeID parentID, ConstUTF8Param name, UInt32 hint) { CatalogKey key; // 518 bytes CatalogRecord data; // 520 bytes UInt32 nodeHint; HFSCatalogNodeID nodeID; HFSCatalogNodeID nodeParentID; UInt16 nodeType; OSErr result; Boolean isHFSPlus = (volume->vcbSigWord == kHFSPlusSigWord); //--- locate subject catalog node result = BuildCatalogKeyUTF8(volume, parentID, name, kUndefinedStrLen, &key, NULL); ReturnIfError(result); result = LocateCatalogNodeByKey(volume, hint, &key, &data, &nodeHint); // if we did not find it by name, then look for an embedded file ID in a mangled name if ( (result == cmNotFound) && isHFSPlus ) result = LocateCatalogNodeByMangledName(volume, parentID, name, kUndefinedStrLen, &key, &data, &nodeHint); ReturnIfError(result); nodeParentID = isHFSPlus ? key.hfsPlus.parentID : key.hfs.parentID; // establish real parent cnid nodeType = data.recordType; // establish cnode type nodeID = 0; switch (nodeType) { case kHFSFolderRecord: if (data.hfsFolder.valence != 0) // is it empty? return cmNotEmpty; nodeID = data.hfsFolder.folderID; break; case kHFSPlusFolderRecord: if (data.hfsPlusFolder.valence != 0) // is it empty? return cmNotEmpty; nodeID = data.hfsPlusFolder.folderID; break; case kHFSFileRecord: if (data.hfsFile.flags & kHFSThreadExistsMask) nodeID = data.hfsFile.fileID; break; case kHFSPlusFileRecord: nodeID = data.hfsPlusFile.fileID; // note: HFS Plus files always have a thread break; default: return cmNotFound; } if (nodeID == fsRtDirID) // is this the root folder? return cmRootCN; // sorry, you can't delete the root! TrashCatalogIterator(volume, nodeParentID); // invalidate any iterators for this parentID InvalidateCatalogNodeCache(volume, nodeParentID); // and invalidate node cache //--- delete catalog records for CNode and file threads if they exist result = DeleteBTreeRecord(volume->catalogRefNum, &key); ReturnIfError(result); if ( nodeID ) { CatalogKey threadKey; // 518 bytes BuildCatalogKey(nodeID, NULL, isHFSPlus, &threadKey); (void) DeleteBTreeRecord(volume->catalogRefNum, &threadKey); // ignore errors for thread deletes } //--- update counters... result = UpdateFolderCount(volume, nodeParentID, NULL, nodeType, kNoHint, -1); ReturnIfError(result); AdjustVolumeCounts(volume, nodeType, -1); // all done with this file or folder result = FlushCatalog(volume); return result; } // end DeleteCatalogNode //_________________________________________________________________________________ // Routine: GetCatalogNode // // Function: Locates an existing folder or file CNode and pointer to the CNode data record. // //_________________________________________________________________________________ OSErr GetCatalogNode( ExtendedVCB *volume, HFSCatalogNodeID parentID, ConstUTF8Param name, UInt32 nameLen, UInt32 hint, CatalogNodeData *nodeData, UInt32 *newHint) { CatalogKey *key; CatalogRecord *record; BTreeIterator searchIterator; FSBufferDescriptor btRecord; ByteCount utf8len; UInt32 heuristicHint; UInt32 *cachedHint; FCB *fcb; OSErr result = noErr; UInt16 dataSize; Boolean isHFSPlus = (volume->vcbSigWord == kHFSPlusSigWord); if (isHFSPlus) { btRecord.bufferAddress = nodeData; btRecord.itemSize = sizeof(CatalogNodeData); } else { btRecord.bufferAddress = &nodeData->cnd_extra; btRecord.itemSize = sizeof(HFSCatalogFile); } btRecord.itemCount = 1; record = (CatalogRecord *) btRecord.bufferAddress; key = (CatalogKey *) &searchIterator.key; if (name && nameLen == kUndefinedStrLen) nameLen = strlen(name); result = BuildCatalogKeyUTF8(volume, parentID, name, nameLen, key, NULL); ReturnIfError(result); fcb = GetFileControlBlock(volume->catalogRefNum); searchIterator.hint.nodeNum = *newHint; searchIterator.hint.index = 0; /* * We pass a 2nd hint/guess into BTSearchRecord. The heuristicHint * is a mapping of dirID and nodeNumber, in hopes that the current * search will be in the same node as the last search with the same * parentID. */ if (name != NULL && GetMRUCacheBlock(parentID, volume->hintCachePtr, (Ptr *)&cachedHint) == 0) heuristicHint = *cachedHint; else heuristicHint = kInvalidMRUCacheKey; result = BTSearchRecord(fcb, &searchIterator, heuristicHint, &btRecord, &dataSize, &searchIterator); if (result == btNotFound) result = cmNotFound; if (name != NULL && result == noErr) InsertMRUCacheBlock(volume->hintCachePtr, parentID, (Ptr) &(searchIterator.hint.nodeNum)); if (result == noErr) { CatalogName *nodeName = NULL; HFSCatalogNodeID threadParentID; /* if we got a thread record, then go look up real record */ switch (record->recordType) { case kHFSFileThreadRecord: case kHFSFolderThreadRecord: threadParentID = record->hfsThread.parentID; nodeName = (CatalogName *) &record->hfsThread.nodeName; break; case kHFSPlusFileThreadRecord: case kHFSPlusFolderThreadRecord: threadParentID = record->hfsPlusThread.parentID; nodeName = (CatalogName *) &record->hfsPlusThread.nodeName; break; default: threadParentID = 0; *newHint = searchIterator.hint.nodeNum; break; } if (threadParentID) { BuildCatalogKey(threadParentID, nodeName, isHFSPlus, key); searchIterator.hint.nodeNum = kNoHint; searchIterator.hint.index = 0; result = BTSearchRecord(fcb, &searchIterator, kInvalidMRUCacheKey, &btRecord, &dataSize, &searchIterator); if (result == btNotFound) result = cmNotFound; if (result == noErr) *newHint = searchIterator.hint.nodeNum; } } /* * If we did not find it by name, then look for an embedded * file ID in a mangled name. */ if ( result == cmNotFound && isHFSPlus) result = LocateCatalogNodeByMangledName(volume, parentID, name, nameLen, key, record, newHint); /* * In Mac OS X there can also be HFS filenames that * could not be encoded using the default encoding. * In which case they were encoded as MacRoman. */ if (result == cmNotFound && !isHFSPlus) { Str31 hfsName; utf8_to_mac_roman(nameLen, name, hfsName); result = LocateCatalogNode(volume, parentID, (CatalogName*)hfsName, 0, key, record, newHint); } ReturnIfError(result); nodeData->cnm_parID = isHFSPlus ? key->hfsPlus.parentID : key->hfs.parentID; if (nodeData->cnm_flags & kCatNameNoCopyName) { if (! isHFSPlus) { if (record->recordType == kHFSFolderRecord || record->recordType == kHFSFileRecord) CopyCatalogNodeData(volume, record, nodeData); else result = cmNotFound; } } else { nodeData->cnm_nameptr = nodeData->cnm_namespace; if ( isHFSPlus ) { result = utf8_encodestr(key->hfsPlus.nodeName.unicode, key->hfsPlus.nodeName.length * sizeof(UniChar), nodeData->cnm_namespace, (size_t *)&utf8len, MAXHFSVNODELEN + 1, ':', 0); /* Need to allocate buffer large enough */ if (result == ENAMETOOLONG) { utf8len = utf8_encodelen(key->hfsPlus.nodeName.unicode, key->hfsPlus.nodeName.length * sizeof(UniChar), ':', 0); nodeData->cnm_nameptr = NewPtr(utf8len + 1); nodeData->cnm_flags |= kCatNameIsAllocated; result = utf8_encodestr(key->hfsPlus.nodeName.unicode, key->hfsPlus.nodeName.length * sizeof(UniChar), nodeData->cnm_nameptr, (size_t *)&utf8len, utf8len + 1, ':', 0); } } else { // classic HFS /* convert data to HFS Plus format */ if (record->recordType == kHFSFolderRecord || record->recordType == kHFSFileRecord) { CopyCatalogNodeData(volume, record, nodeData); result = hfs_to_utf8(volume, key->hfs.nodeName, MAXHFSVNODELEN + 1, &utf8len, nodeData->cnm_namespace); /* Need to allocate buffer large enough */ if (result == ENAMETOOLONG) { nodeData->cnm_nameptr = NewPtr(utf8len + 1); nodeData->cnm_flags |= kCatNameIsAllocated; result = hfs_to_utf8(volume, key->hfs.nodeName, utf8len + 1, &utf8len, nodeData->cnm_nameptr); } else if (result) { /* * When an HFS name cannot be encoded with the current * volume encoding we use MacRoman as a fallback. */ result = mac_roman_to_utf8(key->hfs.nodeName, MAXHFSVNODELEN + 1, &utf8len, nodeData->cnm_namespace); } } else result = cmNotFound; } nodeData->cnm_length = utf8len; if (result && (nodeData->cnm_flags & kCatNameIsAllocated)) { DisposePtr(nodeData->cnm_nameptr); nodeData->cnm_flags &= ~kCatNameIsAllocated; nodeData->cnm_nameptr = 0; /* Just to be clean */ } } #if DEBUG_BUILD if ( nodeData->cnd_nodeID > volume->vcbNxtCNID || nodeData->cnd_nodeID == 0) DebugStr("\pGetCatalogNode bad file ID found!"); #endif return result; } // end GetCatalogNode UInt32 GetDirEntrySize(BTreeIterator *bip, ExtendedVCB * vol) { CatalogKey * ckp; CatalogName * cnp; ByteCount utf8chars; UInt8 name[kdirentMaxNameBytes + 1]; OSErr result; ckp = (CatalogKey*) &bip->key; if (vol->vcbSigWord == kHFSPlusSigWord) { cnp = (CatalogName*) &ckp->hfsPlus.nodeName; utf8chars = utf8_encodelen(cnp->ustr.unicode, cnp->ustr.length * sizeof(UniChar), ':', 0); if (utf8chars > kdirentMaxNameBytes) utf8chars = kdirentMaxNameBytes; } else { /* hfs */ cnp = (CatalogName*) ckp->hfs.nodeName; result = hfs_to_utf8(vol, cnp->pstr, kdirentMaxNameBytes + 1, &utf8chars, name); /*XXX ignoring error */ } return DIRENTRY_SIZE(utf8chars); } /* * NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE * * This is assuming maxinum size of a name is 255 (kdirentMaxNameBytes), which is incorrect. * Any caller of this has to make sure names > 255 are mangled!!!!!!!! */ OSErr PositionIterator(CatalogIterator *cip, UInt32 offset, BTreeIterator *bip, UInt16 *op) { #define CAT_START_OFFSET (2 * sizeof(struct hfsdotentry)) ExtendedVCB * vol; FCB * fcb; OSErr result = 0; /* are we past the end of a directory? */ if (cip->folderID != cip->parentID) return(cmNotFound); vol = cip->volume; fcb = GetFileControlBlock(vol->catalogRefNum); /* make a btree iterator from catalog iterator */ UpdateBtreeIterator(cip, bip); if (cip->currentOffset == offset) { *op = kBTreeCurrentRecord; } else if (cip->nextOffset == offset) { *op = kBTreeNextRecord; } else { /* start from beginning */ UInt32 heuristicHint; UInt32 *cachedHint; *op = kBTreeNextRecord; /* * We pass a 2nd hint/guess into BTSearchRecord. The heuristicHint * is a mapping of dirID and nodeNumber, in hopes that the current * search will be in the same node as the last search with the same * parentID. */ result = GetMRUCacheBlock( cip->folderID, vol->hintCachePtr, (Ptr *)&cachedHint ); heuristicHint = (result == noErr) ? *cachedHint : kInvalidMRUCacheKey; /* Position iterator at the folder's thread record */ result = BTSearchRecord(fcb, bip, heuristicHint, NULL, NULL, bip); if (result) goto exit; InsertMRUCacheBlock( vol->hintCachePtr, cip->folderID, (Ptr) &bip->hint.nodeNum ); /* find offset (note: n^2 / 2) */ if (offset > CAT_START_OFFSET) { HFSCatalogNodeID pid, *idp; UInt32 curOffset, nextOffset; /* get first record (ie offset 24) */ result = BTIterateRecord( fcb, kBTreeNextRecord, bip, NULL, NULL ); if (result) goto exit; if (vol->vcbSigWord == kHFSPlusSigWord) idp = &((CatalogKey*) &bip->key)->hfsPlus.parentID; else idp = &((CatalogKey*) &bip->key)->hfs.parentID; pid = *idp; curOffset = CAT_START_OFFSET; nextOffset = CAT_START_OFFSET + GetDirEntrySize(bip, vol); while (nextOffset < offset) { result = BTIterateRecord( fcb, kBTreeNextRecord, bip, NULL, NULL ); if (result) goto exit; /* check for parent change */ if (pid != *idp) { result = cmNotFound; /* offset past end of directory */ goto exit; } curOffset = nextOffset; nextOffset += GetDirEntrySize(bip, vol); }; if (nextOffset != offset) { result = cmNotFound; goto exit; } UpdateCatalogIterator(bip, cip); cip->currentOffset = curOffset; cip->nextOffset = nextOffset; } } exit: if (result == btNotFound) result = cmNotFound; return result; } /* end PositionIterator */ //_________________________________________________________________________________ // Routine: GetCatalogOffspring // // Function: Gets an offspring record from a specified folder. The folder // is identified by it's folderID. The desired offspring CNode is // indicated by the value of the offspring index (1 = 1st offspring // CNode, 2 = 2nd offspring CNode, etc.). // //_________________________________________________________________________________ OSErr GetCatalogOffspring(ExtendedVCB *volume, HFSCatalogNodeID folderID, UInt16 index, CatalogNodeData *nodeData, HFSCatalogNodeID *nodeID, SInt16 *nodeType) { CatalogIterator * catalogIterator; OSErr result; if ( folderID == 0 ) return cmNotFound; /* * return cmNotFound for index 32767, to prevent overflowing * the index into negative numbers. */ if ( index == 32767 ) return cmNotFound; // get best catalog iterator... catalogIterator = oGetCatalogIterator(volume, folderID, index); result = IterateCatalogNode(volume, catalogIterator, index, nodeData, nodeID, nodeType); (void) ReleaseCatalogIterator(catalogIterator); return result; } // end GetCatalogOffspring //_________________________________________________________________________________ // Routine: IterateCatalogNode // // Function: Gets an offspring record from a specified folder. The folder // is identified by it's folderID. The desired offspring CNode is // indicated by the value of the offspring index (1 = 1st offspring // CNode, 2 = 2nd offspring CNode, etc.). // //_________________________________________________________________________________ static OSErr IterateCatalogNode( ExtendedVCB *volume, CatalogIterator *catalogIterator, UInt16 index, CatalogNodeData *nodeData, HFSCatalogNodeID *nodeID, SInt16 *nodeType ) { HFSCatalogNodeID offspringParentID; CatalogKey * offspringKey; CatalogName * offspringName; BTreeIterator btreeIterator; FSBufferDescriptor btRecord; CatalogRecord * record; UInt8 databuf[32]; /* space for partial record */ FCB * fcb; SInt16 selectionIndex; UInt16 tempSize; UInt16 operation; OSErr result; Boolean isHFSPlus; ByteCount utf8len; isHFSPlus = (volume->vcbSigWord == kHFSPlusSigWord); fcb = GetFileControlBlock(volume->catalogRefNum); // make a btree iterator from catalog iterator UpdateBtreeIterator(catalogIterator, &btreeIterator); /* if client doesn't want data (ie readdir), just get type and id */ if (nodeData == NULL) { /* data buf has space to cover all type/id offsets */ btRecord.bufferAddress = databuf; btRecord.itemSize = sizeof(databuf); } else if (isHFSPlus) { btRecord.bufferAddress = nodeData; btRecord.itemSize = sizeof(CatalogNodeData); } else { btRecord.bufferAddress = &nodeData->cnd_extra; btRecord.itemSize = sizeof(HFSCatalogFile); } btRecord.itemCount = 1; //--- if neccessary position the iterator at the thread record for the specified folder if ( catalogIterator->currentIndex == 0 ) // is this a new iterator? { UInt32 heuristicHint; UInt32 *cachedHint; // We pass a 2nd hint/guess into BTSearchRecord. The heuristicHint is a mapping of // dirID and nodeNumber, in hopes that the current search will be in the same node // as the last search with the same parentID. result = GetMRUCacheBlock( catalogIterator->folderID, volume->hintCachePtr, (Ptr *)&cachedHint ); heuristicHint = (result == noErr) ? *cachedHint : kInvalidMRUCacheKey; result = BTSearchRecord( fcb, &btreeIterator, heuristicHint, &btRecord, &tempSize, &btreeIterator ); ExitOnError(result); UpdateCatalogIterator(&btreeIterator, catalogIterator); // update btree hint and key InsertMRUCacheBlock( volume->hintCachePtr, catalogIterator->folderID, (Ptr) &btreeIterator.hint.nodeNum ); } //--- get offspring record (relative to catalogIterator's position) selectionIndex = index - catalogIterator->currentIndex; // now we have to map index into next/prev operations... if (selectionIndex == 1) { operation = kBTreeNextRecord; } else if (selectionIndex == -1) { operation = kBTreePrevRecord; } else if (selectionIndex == 0) { operation = kBTreeCurrentRecord; } else if (selectionIndex > 1) { UInt32 i; for (i = 1; i < selectionIndex; ++i) { result = BTIterateRecord( fcb, kBTreeNextRecord, &btreeIterator, &btRecord, &tempSize ); ExitOnError(result); } operation = kBTreeNextRecord; } else // (selectionIndex < -1) { SInt32 i; for (i = -1; i > selectionIndex; --i) { result = BTIterateRecord( fcb, kBTreePrevRecord, &btreeIterator, &btRecord, &tempSize ); ExitOnError(result); } operation = kBTreePrevRecord; } result = BTIterateRecord( fcb, operation, &btreeIterator, &btRecord, &tempSize ); ExitOnError(result); offspringKey = (CatalogKey*) &btreeIterator.key; if (isHFSPlus) { offspringParentID = offspringKey->hfsPlus.parentID; offspringName = (CatalogName*) &offspringKey->hfsPlus.nodeName; } else { offspringParentID = offspringKey->hfs.parentID; offspringName = (CatalogName*) offspringKey->hfs.nodeName; } if (offspringParentID != catalogIterator->folderID) // different parent? { AgeCatalogIterator(catalogIterator); // we reached the end, so don't hog the cache! result = cmNotFound; // must be done with this folder goto ErrorExit; } UpdateCatalogIterator(&btreeIterator, catalogIterator); // update btree hint and key catalogIterator->currentIndex = index; // update the offspring index marker record = (CatalogRecord *) btRecord.bufferAddress; if (nodeData == NULL) { /* Just copy the id and type...not name */ if (isHFSPlus) { *nodeType = record->recordType; *nodeID = record->hfsPlusFolder.folderID; } else /* hfs name */ { if (record->recordType == kHFSFileRecord) { *nodeType = kCatalogFileNode; *nodeID = record->hfsFile.fileID; } else if (record->recordType == kHFSFolderRecord) { *nodeType = kCatalogFolderNode; *nodeID = record->hfsFolder.folderID; } else result = cmNotFound; } } else { nodeData->cnm_parID = isHFSPlus ? offspringKey->hfsPlus.parentID : offspringKey->hfs.parentID; nodeData->cnm_nameptr = nodeData->cnm_namespace; if (isHFSPlus) { result = utf8_encodestr(offspringName->ustr.unicode, offspringName->ustr.length * sizeof(UniChar), nodeData->cnm_namespace, (size_t *)&utf8len, MAXHFSVNODELEN + 1, ':', 0); /* Need to allocate buffer large enough */ if (result == ENAMETOOLONG) { utf8len = utf8_encodelen(offspringName->ustr.unicode, offspringName->ustr.length * sizeof(UniChar), ':', 0); nodeData->cnm_nameptr = NewPtr(utf8len + 1); nodeData->cnm_flags |= kCatNameIsAllocated; result = utf8_encodestr(offspringName->ustr.unicode, offspringName->ustr.length * sizeof(UniChar), nodeData->cnm_nameptr, (size_t *)&utf8len, utf8len + 1, ':', 0); } } else /* hfs name */ { if (record->recordType == kHFSFolderRecord || record->recordType == kHFSFileRecord) { CopyCatalogNodeData(volume, record, nodeData); result = hfs_to_utf8(volume, offspringName->pstr, MAXHFSVNODELEN + 1, &utf8len, nodeData->cnm_namespace); if (result == ENAMETOOLONG) { /* Need to allocate buffer large enough */ nodeData->cnm_nameptr = NewPtr(utf8len+1); nodeData->cnm_flags |= kCatNameIsAllocated; result = hfs_to_utf8(volume, offspringName->pstr, utf8len + 1, &utf8len, nodeData->cnm_nameptr); } } else { result = cmNotFound; } } } return result; ErrorExit: if ( result == btNotFound ) result = cmNotFound; return result; } // end IterateCatalogNode //_________________________________________________________________________________ // Routine: MoveRenameCatalogNode // // Function: Moves and/or rename an existing folder or file CNode. // Note that when moving a folder, all decendants (its offspring, // their offspring, etc.) are also moved. // // Assumes srcHint contains a text encoding that was set by a GetCatalogNode call //_________________________________________________________________________________ OSErr MoveRenameCatalogNode(ExtendedVCB *volume, HFSCatalogNodeID srcParentID, ConstUTF8Param srcName, UInt32 srcHint, HFSCatalogNodeID dstParentID, ConstUTF8Param dstName, UInt32 *newHint) { CatalogKey srcKey; // 518 bytes CatalogRecord srcRecord; // 520 bytes CatalogKey dstKey; // 518 bytes CatalogKey dstFolderKey; // 518 bytes HFSCatalogNodeID dstFolderParentID = 0; UInt32 dstFolderHint; CatalogName *dstFolderNamePtr = NULL; CatalogRecord tmpRecord; // 520 bytes HFSCatalogNodeID threadID; UInt32 textEncoding; OSErr result; Boolean isNewName; Boolean isHFSPlus = (volume->vcbSigWord == kHFSPlusSigWord); Boolean isOrigDeleted = false; short srcNameLen; short dstNameLen; result = BuildCatalogKeyUTF8(volume, srcParentID, srcName, kUndefinedStrLen, &srcKey, &textEncoding); ReturnIfError(result); /* XXX can strlen and bcmp handle NULL pointers? */ srcNameLen = strlen(srcName); dstNameLen = strlen(dstName); //--- check if names match if ((srcNameLen == dstNameLen) && (bcmp(srcName, dstName, srcNameLen) == 0)) { isNewName = false; dstKey = srcKey; if ( isHFSPlus ) { dstKey.hfsPlus.parentID = dstParentID; // set parent ID } else { dstKey.hfs.parentID = dstParentID; // set parent ID } } else /* names are different */ { isNewName = true; result = BuildCatalogKeyUTF8(volume, dstParentID, dstName, kUndefinedStrLen, &dstKey, &textEncoding); ReturnIfError(result); } //--- make sure source record exists result = LocateCatalogNodeByKey(volume, srcHint, &srcKey, &srcRecord, &srcHint); // if we did not find it by name, then look for an embedded file ID in a mangled name if ( (result == cmNotFound) && isHFSPlus ) result = LocateCatalogNodeByMangledName(volume, srcParentID, srcName, kUndefinedStrLen, &srcKey, &srcRecord, &srcHint); ReturnIfError(result); srcParentID = (isHFSPlus ? srcKey.hfsPlus.parentID : srcKey.hfs.parentID); // if we're moving then do some additional preflighting... if (srcParentID != dstParentID) { //--- make sure destination folder exists result = LocateCatalogNode(volume, dstParentID, NULL, kNoHint, &dstFolderKey, &tmpRecord, &dstFolderHint); ReturnIfError(result); if (tmpRecord.recordType == kHFSPlusFolderRecord) { dstParentID = tmpRecord.hfsPlusFolder.folderID; dstFolderParentID = dstFolderKey.hfsPlus.parentID; dstFolderNamePtr = (CatalogName*) &dstFolderKey.hfsPlus.nodeName; } else if (tmpRecord.recordType == kHFSFolderRecord) { dstParentID = tmpRecord.hfsFolder.folderID; dstFolderParentID = dstFolderKey.hfs.parentID; dstFolderNamePtr = (CatalogName*) &dstFolderKey.hfs.nodeName; } else { return badMovErr; } //--- if source is a folder, make sure its a proper move if (srcRecord.recordType == kHFSPlusFolderRecord || srcRecord.recordType == kHFSFolderRecord) { HFSCatalogNodeID srcFolderID; HFSCatalogNodeID ancestorParentID; CatalogKey tempKey; // 518 bytes UInt32 tempHint; if (isHFSPlus) { srcFolderID = srcRecord.hfsPlusFolder.folderID; ancestorParentID = dstFolderKey.hfsPlus.parentID; } else { srcFolderID = srcRecord.hfsFolder.folderID; ancestorParentID = dstFolderKey.hfs.parentID; } if ( srcFolderID == fsRtDirID || // source == root? srcFolderID == dstParentID || // source == destination? srcFolderID == ancestorParentID ) // source == destination's parent? { return badMovErr; } while (ancestorParentID > fsRtDirID) // loop until we reach the root folder { // locate next folder up the tree... result = LocateCatalogNode(volume, ancestorParentID, NULL, kNoHint, &tempKey, &tmpRecord, &tempHint); ReturnIfError(result); ancestorParentID = isHFSPlus ? tempKey.hfsPlus.parentID : tempKey.hfs.parentID; if (srcFolderID == ancestorParentID) // source = destination ancestor? return badMovErr; } } TrashCatalogIterator(volume, dstParentID); // invalidate any iterators for destination parentID } else /* (srcParentID == dstParentID) */ { if ( !isNewName ) { *newHint = srcHint; // they match, so we're all done! return noErr; } } TrashCatalogIterator(volume, srcParentID); // invalidate any iterators for source's parentID InvalidateCatalogNodeCache(volume, srcParentID); // invalidate node cache since parent changed if (isNewName && isHFSPlus) { // update textEncoding hint (works for folders and files) srcRecord.hfsPlusFolder.textEncoding = textEncoding; UpdateVolumeEncodings(volume, textEncoding); } //--- insert source CNode record in BTree with new key (a new parent id and/or new name) result = InsertBTreeRecord(volume->catalogRefNum, &dstKey, &srcRecord, GetCatalogRecordSize(&srcRecord), newHint); if (result == btExists) { UInt16 dataSize; /* XXX what about the case: move id1,foo to id2,FOO ?? */ if (srcParentID != dstParentID || isNewName == false) return cmExists; //--- new CNode name already exists in the same folder, locate the existing one result = SearchBTreeRecord(volume->catalogRefNum, &dstKey, srcHint, &dstFolderKey, &tmpRecord, &dataSize, newHint); if (result == btNotFound) result = cmNotFound; ReturnIfError(result); //--- check if its the same CNode (same name but different upper/lower case) if (srcRecord.recordType != tmpRecord.recordType) return cmExists; switch (srcRecord.recordType) { case kHFSPlusFileRecord: /* HFS Plus records share same cnid location */ case kHFSPlusFolderRecord: if (srcRecord.hfsPlusFolder.folderID != tmpRecord.hfsPlusFolder.folderID) return cmExists; break; case kHFSFolderRecord: if (srcRecord.hfsFolder.folderID != tmpRecord.hfsFolder.folderID) return cmExists; break; case kHFSFileRecord: if (srcRecord.hfsFile.fileID != tmpRecord.hfsFile.fileID) return cmExists; break; default: return cmExists; } //--- same name but different case, so delete old and insert with new name... result = DeleteBTreeRecord(volume->catalogRefNum, &srcKey); ReturnIfError(result); isOrigDeleted = true; // So we dont delete it again down below result = InsertBTreeRecord(volume->catalogRefNum, &dstKey, &srcRecord, dataSize, newHint); } ReturnIfError(result); // // from this point on we need to cleanup (ie delete the new record) if we encounter errors! // //--- update thread record for node (if it exists) switch (srcRecord.recordType) { case kHFSPlusFileRecord: case kHFSPlusFolderRecord: threadID = srcRecord.hfsPlusFolder.folderID; break; case kHFSFolderRecord: threadID = srcRecord.hfsFolder.folderID; break; case kHFSFileRecord: if (srcRecord.hfsFile.flags & kHFSThreadExistsMask) { threadID = srcRecord.hfsFile.fileID; break; } /* fall through if no thread... */ default: threadID = 0; } if (threadID) { UInt32 threadHint; CatalogKey threadKey; // 518 bytes CatalogRecord threadRecord; // 520 bytes UInt16 threadSize; result = LocateCatalogRecord(volume, threadID, NULL, kNoHint, &threadKey, &threadRecord, &threadHint); if (result != noErr) goto Exit_Delete; if (isHFSPlus) { if (srcParentID != dstParentID) threadRecord.hfsPlusThread.parentID = dstParentID; if (isNewName) CopyCatalogName((CatalogName *)&dstKey.hfsPlus.nodeName, (CatalogName *) &threadRecord.hfsPlusThread.nodeName, isHFSPlus); threadSize = sizeof(threadRecord.hfsPlusThread); // HFS Plus has varaible sized threads so adjust to actual length threadSize -= ( sizeof(threadRecord.hfsPlusThread.nodeName.unicode) - (threadRecord.hfsPlusThread.nodeName.length * sizeof(UniChar)) ); } else { if (srcParentID != dstParentID) threadRecord.hfsThread.parentID = dstParentID; if (isNewName) CopyCatalogName((CatalogName *)&dstKey.hfs.nodeName,(CatalogName *) threadRecord.hfsThread.nodeName, isHFSPlus); threadSize = sizeof(threadRecord.hfsThread); } result = DeleteBTreeRecord(volume->catalogRefNum, &threadKey); if (result != noErr) goto Exit_Delete; result = InsertBTreeRecord(volume->catalogRefNum, &threadKey, &threadRecord, threadSize, &threadHint); if (result != noErr) goto Exit_Delete; //XXX exiting with a missing thread! } //--- we successfully added the new node so delete the old source CNode record if (! isOrigDeleted) { result = DeleteBTreeRecord(volume->catalogRefNum, &srcKey); if (result) { // uh oh, we could not delete the original // so we better get rid of the new node... (void) DeleteBTreeRecord(volume->catalogRefNum, &dstKey); //XXX also need to fix up the thread... return result; } } if (srcParentID != dstParentID) { result = UpdateFolderCount(volume, srcParentID, NULL, srcRecord.recordType, kNoHint, -1); result = UpdateFolderCount(volume, dstFolderParentID, dstFolderNamePtr, srcRecord.recordType, dstFolderHint, +1); } //--- make sure changes get flushed out VCB_LOCK(volume); volume->vcbFlags |= 0xFF00; // Mark the VCB dirty volume->vcbLsMod = GetTimeUTC(); // update last modified date VCB_UNLOCK(volume); (void) FlushCatalog(volume); return result; Exit_Delete: (void) DeleteBTreeRecord(volume->catalogRefNum, &dstKey); return result; } // end MoveRenameCatalogNode //_________________________________________________________________________________ // Routine: UpdateCatalogNode // // Function: Marks the Catalog BTree node identified by the given catalog hint // as 'dirty'. // //_________________________________________________________________________________ OSErr UpdateCatalogNode(ExtendedVCB *volume, HFSCatalogNodeID parentID, ConstUTF8Param name, UInt32 catalogHint, const CatalogNodeData *nodeData) { CatalogKey *key; CatalogRecord *record; UInt32 hint; UInt16 recordSize; OSErr result; CatalogKey catalogKey; // 518 bytes CatalogRecord catalogRecord; // 520 bytes Boolean isHFSPlus = volume->vcbSigWord == kHFSPlusSigWord; /* XXX no reason to have ptrs to local variables... */ key = &catalogKey; record = &catalogRecord; result = BuildCatalogKeyUTF8(volume, parentID, name, kUndefinedStrLen, key, NULL); ReturnIfError(result); //--- locate subject catalog node result = LocateCatalogNodeByKey(volume, catalogHint, key, record, &hint); // if we did not find it by name, then look for an embedded file ID in a mangled name if ( (result == cmNotFound) && isHFSPlus ) result = LocateCatalogNodeByMangledName(volume, parentID, name, kUndefinedStrLen, key, record, &hint); if (result == btNotFound) result = cmNotFound; if (catalogHint != hint) PRINTIT(("UpdateCatalogNode: catalogHint does not match (in: %ld, out: %ld)\n", catalogHint, hint)); ReturnIfError(result); // update user modifiable fields in the catalog node record... switch (record->recordType) { case kHFSFolderRecord: { #if DEBUG_BUILD if (nodeData->cnd_type != kCatalogFolderNode) DebugStr("\p UpdateCatalogNode: folder/file mismatch!"); #endif record->hfsFolder.createDate = UTCToLocal(nodeData->cnd_createDate); record->hfsFolder.modifyDate = UTCToLocal(nodeData->cnd_contentModDate); record->hfsFolder.backupDate = UTCToLocal(nodeData->cnd_backupDate); *(DInfo*) &record->hfsFolder.userInfo = *(DInfo*) &nodeData->cnd_finderInfo; *(DXInfo*) &record->hfsFolder.finderInfo = *(DXInfo*) ((UInt32)&nodeData->cnd_finderInfo + 16); recordSize = sizeof(HFSCatalogFolder); break; } case kHFSFileRecord: { UInt32 i; #if DEBUG_BUILD if (nodeData->cnd_type != kCatalogFileNode) DebugStr("UpdateCatalogNode: folder/file mismatch!"); if ((nodeData->cnd_datafork.totalBlocks > (0x7FFFFFFF/volume->blockSize)) || (nodeData->cnd_rsrcfork.totalBlocks > (0x7FFFFFFF/volume->blockSize))) DebugStr("HFS file size is larger than 2Gig"); #endif record->hfsFile.flags = (UInt8) nodeData->cnd_flags; record->hfsFile.createDate = UTCToLocal(nodeData->cnd_createDate); record->hfsFile.modifyDate = UTCToLocal(nodeData->cnd_contentModDate); record->hfsFile.backupDate = UTCToLocal(nodeData->cnd_backupDate); record->hfsFile.dataLogicalSize = nodeData->cnd_datafork.logicalSize; record->hfsFile.dataPhysicalSize = nodeData->cnd_datafork.totalBlocks * volume->blockSize; record->hfsFile.rsrcLogicalSize = nodeData->cnd_rsrcfork.logicalSize; record->hfsFile.rsrcPhysicalSize = nodeData->cnd_rsrcfork.totalBlocks * volume->blockSize; *(FInfo*) &record->hfsFile.userInfo = *(FInfo*) &nodeData->cnd_finderInfo; *(FXInfo*) &record->hfsFile.finderInfo = *(FXInfo*) ((UInt32)&nodeData->cnd_finderInfo + 16); // copy extent info for (i = 0; i < kHFSExtentDensity; ++i) { record->hfsFile.dataExtents[i].startBlock = (UInt16) nodeData->cnd_datafork.extents[i].startBlock; record->hfsFile.dataExtents[i].blockCount = (UInt16) nodeData->cnd_datafork.extents[i].blockCount; record->hfsFile.rsrcExtents[i].startBlock = (UInt16) nodeData->cnd_rsrcfork.extents[i].startBlock; record->hfsFile.rsrcExtents[i].blockCount = (UInt16) nodeData->cnd_rsrcfork.extents[i].blockCount; } recordSize = sizeof(HFSCatalogFile); break; } case kHFSPlusFolderRecord: { record->hfsPlusFolder.createDate = nodeData->cnd_createDate; record->hfsPlusFolder.contentModDate = nodeData->cnd_contentModDate; record->hfsPlusFolder.backupDate = nodeData->cnd_backupDate; record->hfsPlusFolder.accessDate = nodeData->cnd_accessDate; record->hfsPlusFolder.attributeModDate = nodeData->cnd_attributeModDate; record->hfsPlusFolder.bsdInfo.ownerID = nodeData->cnd_ownerID; record->hfsPlusFolder.bsdInfo.groupID = nodeData->cnd_groupID; record->hfsPlusFolder.bsdInfo.ownerFlags = nodeData->cnd_ownerFlags; record->hfsPlusFolder.bsdInfo.adminFlags = nodeData->cnd_adminFlags; record->hfsPlusFolder.bsdInfo.fileMode = nodeData->cnd_mode; record->hfsPlusFolder.textEncoding = nodeData->cnd_textEncoding; BlockMoveData(&nodeData->cnd_finderInfo, &record->hfsPlusFolder.userInfo, 32); recordSize = sizeof(HFSPlusCatalogFolder); break; } case kHFSPlusFileRecord: { record->hfsPlusFile.flags = nodeData->cnd_flags; record->hfsPlusFile.createDate = nodeData->cnd_createDate; record->hfsPlusFile.contentModDate = nodeData->cnd_contentModDate; record->hfsPlusFile.backupDate = nodeData->cnd_backupDate; record->hfsPlusFile.accessDate = nodeData->cnd_accessDate; record->hfsPlusFile.attributeModDate = nodeData->cnd_attributeModDate; record->hfsPlusFile.bsdInfo.ownerID = nodeData->cnd_ownerID; record->hfsPlusFile.bsdInfo.groupID = nodeData->cnd_groupID; record->hfsPlusFile.bsdInfo.ownerFlags = nodeData->cnd_ownerFlags; record->hfsPlusFile.bsdInfo.adminFlags = nodeData->cnd_adminFlags; record->hfsPlusFile.bsdInfo.fileMode = nodeData->cnd_mode; /* get special value (iNodeNum, linkCount or rawDevice) */ record->hfsPlusFile.bsdInfo.special.rawDevice = nodeData->cnd_rawDevice; record->hfsPlusFile.textEncoding = nodeData->cnd_textEncoding; record->hfsPlusFile.dataFork.logicalSize = nodeData->cnd_datafork.logicalSize; record->hfsPlusFile.dataFork.totalBlocks = nodeData->cnd_datafork.totalBlocks; BlockMoveData(&nodeData->cnd_datafork.extents, &record->hfsPlusFile.dataFork.extents, sizeof(HFSPlusExtentRecord)); record->hfsPlusFile.resourceFork.logicalSize = nodeData->cnd_rsrcfork.logicalSize; record->hfsPlusFile.resourceFork.totalBlocks = nodeData->cnd_rsrcfork.totalBlocks; BlockMoveData(&nodeData->cnd_rsrcfork.extents, &record->hfsPlusFile.resourceFork.extents, sizeof(HFSPlusExtentRecord)); BlockMoveData(&nodeData->cnd_finderInfo, &record->hfsPlusFile.userInfo, 32); #if HFS_HARDLINKS && DEBUG_BUILD /* Must swap opaque finder data */ if (SWAP_BE32 (record->hfsPlusFile.userInfo.fdType) == kHardLinkFileType && SWAP_BE32 (record->hfsPlusFile.userInfo.fdCreator) == kHardLinkCreator) { if (record->hfsPlusFile.dataFork.logicalSize != 0) DebugStr("UpdateCatalogNode: link has data fork!"); } #endif recordSize = sizeof(HFSPlusCatalogFile); break; } default: return cmNotFound; } result = ReplaceBTreeRecord(volume->catalogRefNum, key, catalogHint, record, recordSize, &hint); if ( result == btNotFound ) { result = cmNotFound; } else if ( result == noErr ) { /* if we're just updating the accessDate then no need to change volume mod date */ if (nodeData->cnd_contentModDate > volume->vcbLsMod || (isHFSPlus && nodeData->cnd_attributeModDate > volume->vcbLsMod)) { VCB_LOCK(volume); volume->vcbFlags |= 0xFF00; // Mark the VCB dirty volume->vcbLsMod = GetTimeUTC(); // update last modified date VCB_UNLOCK(volume); } result = FlushCatalog(volume); // flush the catalog } return result; } //_________________________________________________________________________________ // Routine: CreateFileIDRef // // Function: Creates a file thread record for hfs file node // //_________________________________________________________________________________ OSErr CreateFileIDRef(ExtendedVCB *volume, HFSCatalogNodeID parentID, ConstUTF8Param name, UInt32 hint, HFSCatalogNodeID *threadID) { CatalogKey nodeKey; // 518 bytes CatalogRecord nodeData; // 520 bytes HFSCatalogKey threadKey; HFSCatalogThread threadData; UInt32 nodeHint; UInt32 tempHint; OSErr result; Boolean isHFSPlus = (volume->vcbSigWord == kHFSPlusSigWord); *threadID = 0; result = BuildCatalogKeyUTF8(volume, parentID, name, kUndefinedStrLen, &nodeKey, NULL); ReturnIfError(result); //--- locate subject catalog node result = LocateCatalogNodeByKey(volume, hint, &nodeKey, &nodeData, &nodeHint); // if we did not find it by name, then look for an embedded file ID in a mangled name if ( (result == cmNotFound) && isHFSPlus ) result = LocateCatalogNodeByMangledName(volume, parentID, name, kUndefinedStrLen, &nodeKey, &nodeData, &nodeHint); ReturnIfError(result); if (nodeData.recordType == kHFSPlusFileRecord) { *threadID = nodeData.hfsPlusFile.fileID; return noErr; // already have one } if (nodeData.recordType != kHFSFileRecord) { return notAFileErr; } if (nodeData.hfsFile.flags & kHFSThreadExistsMask) { *threadID = nodeData.hfsFile.fileID; return noErr; // already have one } result = VolumeWritable( volume ); if ( result != noErr ) return result; // // need to insert a thread record // BuildCatalogKey(nodeData.hfsFile.fileID, NULL, false, (CatalogKey *)&threadKey); ClearMemory(&threadData, sizeof(HFSCatalogThread)); threadData.recordType = kHFSFileThreadRecord; threadData.parentID = nodeKey.hfs.parentID; BlockMoveData(&nodeKey.hfs.nodeName, &threadData.nodeName, nodeKey.hfs.nodeName[0] + 1); result = InsertBTreeRecord(volume->catalogRefNum, &threadKey, &threadData, sizeof(HFSCatalogThread), &tempHint); if (result == btExists) result = noErr; //XXX could return cmExists or fidExists ReturnIfError(result); // // Finally, set the flag in the file record to say this file has a thread record. // nodeData.hfsFile.flags |= kHFSThreadExistsMask; result = ReplaceBTreeRecord(volume->catalogRefNum, &nodeKey, nodeHint, &nodeData, sizeof(HFSCatalogFile), &nodeHint ); if (result == noErr) { (void) FlushCatalog(volume); *threadID = nodeData.hfsFile.fileID; } return result; } //_________________________________________________________________________________ // Routine: CompareCatalogKeys // // Function: Compares two catalog keys (a search key and a trial key). // // Result: +n search key > trial key // 0 search key = trial key // -n search key < trial key //_________________________________________________________________________________ SInt32 CompareCatalogKeys(HFSCatalogKey *searchKey, HFSCatalogKey *trialKey) { HFSCatalogNodeID searchParentID, trialParentID; SInt32 result; searchParentID = searchKey->parentID; trialParentID = trialKey->parentID; if ( searchParentID > trialParentID ) // parent dirID is unsigned result = 1; else if ( searchParentID < trialParentID ) result = -1; else // parent dirID's are equal, compare names { #if ( ! FORDISKFIRSTAID ) LogStartTime(kTraceRelString); result = FastRelString(searchKey->nodeName, trialKey->nodeName); LogEndTime(kTraceRelString, noErr); #else result = (SInt32) RelString_Glue(searchKey->nodeName, trialKey->nodeName); #endif } return result; } //_________________________________________________________________________________ // Routine: CompareExtendedCatalogKeys // // Function: Compares two large catalog keys (a search key and a trial key). // // Result: +n search key > trial key // 0 search key = trial key // -n search key < trial key //_________________________________________________________________________________ SInt32 CompareExtendedCatalogKeys(HFSPlusCatalogKey *searchKey, HFSPlusCatalogKey *trialKey) { SInt32 result; HFSCatalogNodeID searchParentID, trialParentID; searchParentID = searchKey->parentID; trialParentID = trialKey->parentID; if ( searchParentID > trialParentID ) // parent node IDs are unsigned { result = 1; } else if ( searchParentID < trialParentID ) { result = -1; } else // parent node ID's are equal, compare names { if ( searchKey->nodeName.length == 0 || trialKey->nodeName.length == 0 ) result = searchKey->nodeName.length - trialKey->nodeName.length; else result = FastUnicodeCompare(&searchKey->nodeName.unicode[0], searchKey->nodeName.length, &trialKey->nodeName.unicode[0], trialKey->nodeName.length); } return result; }