//-- Delete all extents in extent file that have the ID given. static OSErr DeleteExtents( ExtendedVCB *vcb, u_int32_t fileID, Boolean isHFSPlus ) { FCB * fcb; ExtentKey * extentKeyPtr; ExtentRecord extentData; BTreeIterator btIterator; FSBufferDescriptor btRecord; u_int16_t btRecordSize; OSErr err; fcb = GetFileControlBlock(vcb->extentsRefNum); (void) BTInvalidateHint(&btIterator); extentKeyPtr = (ExtentKey*) &btIterator.key; btRecord.bufferAddress = &extentData; btRecord.itemCount = 1; // The algorithm is to position the BTree just before any extent records for fileID. // Then just keep getting successive records. If the record is still for fileID, // then delete it. if (isHFSPlus) { btRecord.itemSize = sizeof(HFSPlusExtentRecord); extentKeyPtr->hfsPlus.keyLength = kHFSPlusExtentKeyMaximumLength; extentKeyPtr->hfsPlus.forkType = 0; extentKeyPtr->hfsPlus.pad = 0; extentKeyPtr->hfsPlus.fileID = fileID; extentKeyPtr->hfsPlus.startBlock = 0; } else { btRecord.itemSize = sizeof(HFSExtentRecord); extentKeyPtr->hfs.keyLength = kHFSExtentKeyMaximumLength; extentKeyPtr->hfs.forkType = 0; extentKeyPtr->hfs.fileID = fileID; extentKeyPtr->hfs.startBlock = 0; } err = BTSearchRecord(fcb, &btIterator, &btRecord, &btRecordSize, &btIterator); if ( err != btNotFound ) { if (err == noErr) { // Did we find a bogus extent record? err = cmBadNews; // Yes, so indicate things are messed up. } return err; // Got some unexpected error, so return it } do { BTreeIterator tmpIterator; HFSCatalogNodeID foundFileID; err = BTIterateRecord(fcb, kBTreeNextRecord, &btIterator, &btRecord, &btRecordSize); if ( err != noErr ) { if (err == btNotFound) // If we hit the end of the BTree err = noErr; // then it's OK break; // We're done now. } foundFileID = isHFSPlus ? extentKeyPtr->hfsPlus.fileID : extentKeyPtr->hfs.fileID; if ( foundFileID != fileID ) break; // numbers don't match, we must be done tmpIterator = btIterator; err = BTDeleteRecord( fcb, &tmpIterator ); if (err != noErr) break; } while ( true ); return( err ); }
/* * Function to traverse all the records of a btree and then call caller-provided * callback function for every record found. The type of btree is chosen based * on the fileID provided by the caller. This fuction grabs the correct locks * depending on the type of btree it will be traversing and flags provided * by the caller. * * Note: It might drop and reacquire the locks during execution. */ static errno_t traverse_btree(struct hfsmount *hfsmp, uint32_t btree_fileID, traverse_btree_flag_t flags, void *fsinfo, int (*callback)(struct hfsmount *, HFSPlusKey *, HFSPlusRecord *, void *)) { int error = 0; int lockflags = 0; int ret_lockflags = 0; FCB *fcb; struct BTreeIterator *iterator = NULL; struct FSBufferDescriptor btdata; int btree_operation; HFSPlusRecord record; HFSPlusKey *key; uint64_t start, timeout_abs; switch(btree_fileID) { case kHFSExtentsFileID: fcb = VTOF(hfsmp->hfs_extents_vp); lockflags = SFL_EXTENTS; break; case kHFSCatalogFileID: fcb = VTOF(hfsmp->hfs_catalog_vp); lockflags = SFL_CATALOG; break; case kHFSAttributesFileID: // Attributes file doesn’t exist, There are no records to iterate. if (hfsmp->hfs_attribute_vp == NULL) return error; fcb = VTOF(hfsmp->hfs_attribute_vp); lockflags = SFL_ATTRIBUTE; break; default: return EINVAL; } MALLOC(iterator, struct BTreeIterator *, sizeof(struct BTreeIterator), M_TEMP, M_WAITOK | M_ZERO); /* The key is initialized to zero because we are traversing entire btree */ key = (HFSPlusKey *)&iterator->key; if (flags & TRAVERSE_BTREE_EXTENTS) { lockflags |= SFL_EXTENTS; } btdata.bufferAddress = &record; btdata.itemSize = sizeof(HFSPlusRecord); btdata.itemCount = 1; /* Lock btree for duration of traversal */ ret_lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_SHARED_LOCK); btree_operation = kBTreeFirstRecord; nanoseconds_to_absolutetime(HFS_FSINFO_MAX_LOCKHELD_TIME, &timeout_abs); start = mach_absolute_time(); while (1) { if (msleep(NULL, NULL, PINOD | PCATCH, "hfs_fsinfo", NULL) == EINTR) { error = EINTR; break; } error = BTIterateRecord(fcb, btree_operation, iterator, &btdata, NULL); if (error != 0) { if (error == fsBTRecordNotFoundErr || error == fsBTEndOfIterationErr) { error = 0; } break; } /* Lookup next btree record on next call to BTIterateRecord() */ btree_operation = kBTreeNextRecord; /* Call our callback function and stop iteration if there are any errors */ error = callback(hfsmp, key, &record, fsinfo); if (error) { break; } /* let someone else use the tree after we've processed over HFS_FSINFO_MAX_LOCKHELD_TIME */ if ((mach_absolute_time() - start) >= timeout_abs) { /* release b-tree locks and let someone else get the lock */ hfs_systemfile_unlock (hfsmp, ret_lockflags); /* add tsleep here to force context switch and fairness */ tsleep((caddr_t)hfsmp, PRIBIO, "hfs_fsinfo", 1); /* * re-acquire the locks in the same way that we wanted them originally. * note: it is subtle but worth pointing out that in between the time that we * released and now want to re-acquire these locks that the b-trees may have shifted * slightly but significantly. For example, the catalog or other b-tree could have grown * past 8 extents and now requires the extents lock to be held in order to be safely * manipulated. We can't be sure of the state of the b-tree from where we last left off. */ ret_lockflags = hfs_systemfile_lock (hfsmp, lockflags, HFS_SHARED_LOCK); /* * It's highly likely that the search key we stashed away before dropping lock * no longer points to an existing item. Iterator's IterateRecord is able to * re-position itself and process the next record correctly. With lock dropped, * there might be records missed for statistic gathering, which is ok. The * point is to get aggregate values. */ start = mach_absolute_time(); /* loop back around and get another record */ } } hfs_systemfile_unlock(hfsmp, ret_lockflags); FREE (iterator, M_TEMP); return MacToVFSError(error); }
static OSErr MoveExtents( ExtendedVCB *vcb, u_int32_t srcFileID, u_int32_t destFileID, Boolean isHFSPlus ) { FCB * fcb; ExtentsRecBuffer extentsBuffer[kNumExtentsToCache]; ExtentKey * extentKeyPtr; ExtentRecord extentData; BTreeIterator btIterator; FSBufferDescriptor btRecord; u_int16_t btKeySize; u_int16_t btRecordSize; int16_t i, j; OSErr err; fcb = GetFileControlBlock(vcb->extentsRefNum); (void) BTInvalidateHint(&btIterator); extentKeyPtr = (ExtentKey*) &btIterator.key; btRecord.bufferAddress = &extentData; btRecord.itemCount = 1; //-- Collect the extent records // // A search on the following key will cause the BTree to be positioned immediately // before the first extent record for file #srcFileID, but not actually positioned // on any record. This is because there cannot be an extent record with FABN = 0 // (the first extent of the fork, which would be in the catalog entry, not an extent // record). // // Using BTIterateRecord with kBTreeNextRecord will then get that first extent record. // if (isHFSPlus) { btRecord.itemSize = sizeof(HFSPlusExtentRecord); btKeySize = sizeof(HFSPlusExtentKey); extentKeyPtr->hfsPlus.keyLength = kHFSPlusExtentKeyMaximumLength; extentKeyPtr->hfsPlus.forkType = 0; extentKeyPtr->hfsPlus.pad = 0; extentKeyPtr->hfsPlus.fileID = srcFileID; extentKeyPtr->hfsPlus.startBlock = 0; } else { btRecord.itemSize = sizeof(HFSExtentRecord); btKeySize = sizeof(HFSExtentKey); extentKeyPtr->hfs.keyLength = kHFSExtentKeyMaximumLength; extentKeyPtr->hfs.forkType = 0; extentKeyPtr->hfs.fileID = srcFileID; extentKeyPtr->hfs.startBlock = 0; } // // We do an initial BTSearchRecord to position the BTree's iterator just before any extent // records for srcFileID. We then do a few BTIterateRecord and BTInsertRecord of those found // records, but with destFileID as the file number in the key. Keep doing this sequence of // BTIterateRecord and BTInsertRecord until we find an extent for another file, or there are // no more extent records in the tree. // // Basically, we're copying records kNumExtentsToCache at a time. The copies have their file ID // set to destFileID. // // This depends on BTInsertRecord not effecting the iterator used by BTIterateRecord. If it // _did_ effect the iterator, then we would need to do a BTSearchRecord before each series // of BTIterateRecord. We'd need to set up the key for BTSearchRecord to find the last record // we found, so that BTIterateRecord would get the next one (the first we haven't processed). // err = BTSearchRecord(fcb, &btIterator, &btRecord, &btRecordSize, &btIterator); // We expect a btNotFound here, since there shouldn't be an extent record with FABN = 0. if (err != btNotFound) { if ( DEBUG_BUILD ) DebugStr("Unexpected error from SearchBTreeRecord"); if (err == noErr) // If we found such a bogus extent record, then the tree is really messed up err = cmBadNews; // so return an error that conveys the disk is hosed. return err; } do { btRecord.bufferAddress = &extentData; btRecord.itemCount = 1; for ( i=0 ; i<kNumExtentsToCache ; i++ ) { HFSCatalogNodeID foundFileID; err = BTIterateRecord(fcb, kBTreeNextRecord, &btIterator, &btRecord, &btRecordSize); if ( err == btNotFound ) // Did we run out of extent records in the extents tree? break; // if xkrFNum(A0) is cleared on this error, then this test is bogus! else if ( err != noErr ) return( err ); // must be ioError foundFileID = isHFSPlus ? extentKeyPtr->hfsPlus.fileID : extentKeyPtr->hfs.fileID; if ( foundFileID == srcFileID ) { CopyExtentInfo(extentKeyPtr, &extentData, extentsBuffer, i); } else { break; } } //-- edit each extent key, and reinsert each extent record in the extent file if (isHFSPlus) btRecordSize = sizeof(HFSPlusExtentRecord); else btRecordSize = sizeof(HFSExtentRecord); for ( j=0 ; j<i ; j++ ) { BTreeIterator tmpIterator; if (isHFSPlus) extentsBuffer[j].extentKey.hfsPlus.fileID = destFileID; // change only the id in the key to dest ID else extentsBuffer[j].extentKey.hfs.fileID = destFileID; // change only the id in the key to dest ID // get iterator and buffer descriptor ready... (void) BTInvalidateHint(&tmpIterator); BlockMoveData(&(extentsBuffer[j].extentKey), &tmpIterator.key, btKeySize); btRecord.bufferAddress = &(extentsBuffer[j].extentData); err = BTInsertRecord(fcb, &tmpIterator, &btRecord, btRecordSize); if ( err != noErr ) { // parse the error if ( err == btExists ) { if ( DEBUG_BUILD ) DebugStr("Can't insert record -- already exists"); return( cmBadNews ); } else return( err ); } } //-- okay, done with this buffered batch, go get the next set of extent records // If our buffer is not full, we must be done, or recieved an error if ( i != kNumExtentsToCache ) // if the buffer is not full, we must be done { err = DeleteExtents( vcb, srcFileID, isHFSPlus ); // Now delete all the extent entries with the sourceID if ( DEBUG_BUILD && err != noErr ) DebugStr("Error from DeleteExtents"); break; // we're done! } } while ( true ); return( err ); }
/* * This is a helper function that counts the total number of valid * extents in all the overflow extent records for given fileID * in overflow extents btree */ static errno_t hfs_count_overflow_extents(struct hfsmount *hfsmp, uint32_t fileID, uint32_t *num_extents) { int error; FCB *fcb; struct BTreeIterator *iterator = NULL; FSBufferDescriptor btdata; HFSPlusExtentKey *extentKey; HFSPlusExtentRecord extentData; uint32_t extent_count = 0; int i; fcb = VTOF(hfsmp->hfs_extents_vp); MALLOC(iterator, struct BTreeIterator *, sizeof(struct BTreeIterator), M_TEMP, M_WAITOK | M_ZERO); extentKey = (HFSPlusExtentKey *) &iterator->key; extentKey->keyLength = kHFSPlusExtentKeyMaximumLength; extentKey->forkType = kHFSDataForkType; extentKey->fileID = fileID; extentKey->startBlock = 0; btdata.bufferAddress = &extentData; btdata.itemSize = sizeof(HFSPlusExtentRecord); btdata.itemCount = 1; /* Search for overflow extent record */ error = BTSearchRecord(fcb, iterator, &btdata, NULL, iterator); /* * We used startBlock of zero, so we will not find any records and errors * are expected. It will also position the iterator just before the first * overflow extent record for given fileID (if any). */ if (error && error != fsBTRecordNotFoundErr && error != fsBTEndOfIterationErr) goto out; error = 0; for (;;) { if (msleep(NULL, NULL, PINOD | PCATCH, "hfs_fsinfo", NULL) == EINTR) { error = EINTR; break; } error = BTIterateRecord(fcb, kBTreeNextRecord, iterator, &btdata, NULL); if (error != 0) { /* These are expected errors, so mask them */ if (error == fsBTRecordNotFoundErr || error == fsBTEndOfIterationErr) { error = 0; } break; } /* If we encounter different fileID, stop the iteration */ if (extentKey->fileID != fileID) { break; } if (extentKey->forkType != kHFSDataForkType) break; /* This is our record of interest; only count the datafork extents. */ for (i = 0; i < kHFSPlusExtentDensity; i++) { if (extentData[i].blockCount == 0) { break; } extent_count++; } } out: FREE(iterator, M_TEMP); if (error == 0) { *num_extents = extent_count; } return MacToVFSError(error); }