+ if (cur_id_index) {
+ reset_file_ids(hfsmp, fileids, cur_id_index);
+ cleared += cur_id_index;
+ cur_id_index = 0;
+ }
+
+ printf("hfs: cleared HotFileCache related bits on %d files out of %d (dircount %d)\n", cleared, filecount, dircount);
+
+ (void) BTScanTerminate(&scanstate, &data, &data, &data);
+
+out:
+ if (fileids)
+ FREE(fileids, M_TEMP);
+
+ if (iterator)
+ FREE(iterator, M_TEMP);
+
+ //
+ // If the hotfile btree exists, delete it. We need to open
+ // it to be able to delete it because we need the hfc_filevp
+ // for deletion.
+ //
+ error = hfc_btree_open_ext(hfsmp, &hfsmp->hfc_filevp, 1);
+ if (!error) {
+ printf("hfs: hotfile_reset: deleting existing hotfile btree\n");
+ hfc_btree_delete(hfsmp);
+ }
+
+ if (hfsmp->hfc_filevp) {
+ (void) hfc_btree_close(hfsmp, hfsmp->hfc_filevp);
+ hfsmp->hfc_filevp = NULL;
+ }
+
+ hfsmp->hfs_hotfile_blk_adjust = 0;
+ hfsmp->hfs_hotfile_freeblks = hfsmp->hfs_hotfile_maxblks;
+}
+
+
+//
+// This should ONLY be called by hfs_recording_init() and the special fsctl.
+//
+// We assume that the hotfile btree is already opened.
+//
+static int
+hfs_hotfile_repin_files(struct hfsmount *hfsmp)
+{
+ BTreeIterator * iterator = NULL;
+ HotFileKey * key;
+ filefork_t * filefork;
+ int error = 0;
+ int bt_op;
+ enum hfc_stage stage;
+ uint32_t pinned_blocks;
+ uint32_t num_files=0, nrsrc=0;
+ uint32_t total_pinned=0;
+
+ if (!(hfsmp->hfs_flags & HFS_CS_HOTFILE_PIN) || !hfsmp->hfc_filevp) {
+ //
+ // this is only meaningful if we're pinning hotfiles
+ // (as opposed to the regular form of hotfiles that
+ // get relocated to the hotfile zone)
+ //
+ return 0;
+ }
+
+#if HFC_VERBOSE
+ printf("hfs: %s: %s\n", hfsmp->vcbVN, __FUNCTION__);
+#endif
+
+ if (hfs_lock(VTOC(hfsmp->hfc_filevp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) != 0) {
+ return (EPERM);
+ }
+
+
+ MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
+ if (iterator == NULL) {
+ hfs_unlock(VTOC(hfsmp->hfc_filevp));
+ return (ENOMEM);
+ }
+
+ stage = hfsmp->hfc_stage;
+ hfsmp->hfc_stage = HFC_BUSY;
+
+ bt_op = kBTreeFirstRecord;
+
+ bzero(iterator, sizeof(*iterator));
+ key = (HotFileKey*) &iterator->key;
+
+ filefork = VTOF(hfsmp->hfc_filevp);
+ int lockflags;
+
+ while (1) {
+
+ lockflags = 0;
+ /*
+ * Obtain the first record (ie the coldest one).
+ */
+ if (BTIterateRecord(filefork, bt_op, iterator, NULL, NULL) != 0) {
+ // no more records
+ error = 0;
+ break;
+ }
+ if (key->keyLength != HFC_KEYLENGTH) {
+ // printf("hfs: hotfiles_repin_files: invalid key length %d\n", key->keyLength);
+ error = EFTYPE;
+ break;
+ }
+ if (key->temperature == HFC_LOOKUPTAG) {
+ // ran into thread records in the hotfile btree
+ error = 0;
+ break;
+ }
+
+ //
+ // Just lookup the records in the catalog and pin the direct
+ // mapped extents. Faster than instantiating full vnodes
+ // (and thereby thrashing the system vnode cache).
+ //
+ struct cat_desc fdesc;
+ struct cat_attr attr;
+ struct cat_fork fork;
+ uint8_t forktype = 0;
+
+ lockflags = hfs_systemfile_lock(hfsmp, (SFL_CATALOG | SFL_EXTENTS), HFS_SHARED_LOCK);
+ /*
+ * Snoop the cnode hash to find out if the item we want is in-core already.
+ *
+ * We largely expect this function to fail (the items we want are probably not in the hash).
+ * we use the special variant which bails out as soon as it finds a vnode (even if it is
+ * marked as open-unlinked or actually removed on-disk. If we find a vnode, then we
+ * release the systemfile locks and go through the pin-vnode path instead.
+ */
+ if (hfs_chash_snoop (hfsmp, key->fileID, 1, NULL, NULL) == 0) {
+ pinned_blocks = 0;
+
+ /* unlock immediately and go through the in-core path */
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ lockflags = 0;
+
+ error = hfs_getvnode_and_pin (hfsmp, key->fileID, &pinned_blocks);
+ if (error) {
+ /* if ENOENT, then it was deleted in the catalog. Remove from our hotfiles tracking */
+ if (error == ENOENT) {
+ hfc_btree_delete_record(hfsmp, iterator, key);
+ }
+ /* other errors, just ignore and move on with life */
+ }
+ else { //!error
+ total_pinned += pinned_blocks;
+ num_files++;
+ }
+
+ goto next;
+ }
+
+ /* If we get here, we're still holding the systemfile locks */
+ error = cat_idlookup(hfsmp, key->fileID, 1, 0, &fdesc, &attr, &fork);
+ if (error) {
+ //
+ // this file system could have been mounted while booted from a
+ // different partition and thus the hotfile btree would not have
+ // been maintained. thus a file that was hotfile cached could
+ // have been deleted while booted from a different partition which
+ // means we need to delete it from the hotfile btree.
+ //
+ // block accounting is taken care of at the end: we re-assign
+ // hfsmp->hfs_hotfile_freeblks based on how many blocks we actually
+ // pinned.
+ //
+ hfc_btree_delete_record(hfsmp, iterator, key);
+
+ goto next;
+ }
+
+ if (fork.cf_size == 0) {
+ // hmmm, the data is probably in the resource fork (aka a compressed file)
+ error = cat_idlookup(hfsmp, key->fileID, 1, 1, &fdesc, &attr, &fork);
+ if (error) {
+ hfc_btree_delete_record(hfsmp, iterator, key);
+ goto next;
+ }
+ forktype = 0xff;
+ nrsrc++;
+ }
+
+ pinned_blocks = 0;
+
+ /* Can't release the catalog /extents lock yet, we may need to go find the overflow blocks */
+ error = hfs_pin_extent_record (hfsmp, fork.cf_extents, &pinned_blocks);
+ if (error) {
+ goto next; //skip to next
+ }
+ /* add in the blocks from the inline 8 */
+ total_pinned += pinned_blocks;
+ pinned_blocks = 0;
+
+ /* Could this file have overflow extents? */
+ if (fork.cf_extents[kHFSPlusExtentDensity-1].startBlock) {
+ /* better pin them, too */
+ error = hfs_pin_overflow_extents (hfsmp, key->fileID, forktype, &pinned_blocks);
+ if (error) {
+ /* If we fail to pin all of the overflow extents, then just skip to the next file */
+ goto next;
+ }
+ }
+
+ num_files++;
+ if (pinned_blocks) {
+ /* now add in any overflow also */
+ total_pinned += pinned_blocks;
+ }
+
+ next:
+ if (lockflags) {
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ lockflags = 0;
+ }
+ bt_op = kBTreeNextRecord;
+
+ } /* end while */
+
+#if HFC_VERBOSE
+ printf("hfs: hotfiles_repin_files: re-pinned %d files (nrsrc %d, total pinned %d blks; freeblock %d, maxblocks %d, calculated free: %d)\n",
+ num_files, nrsrc, total_pinned, hfsmp->hfs_hotfile_freeblks, hfsmp->hfs_hotfile_maxblks,
+ hfsmp->hfs_hotfile_maxblks - total_pinned);
+#endif
+ //
+ // make sure this is accurate based on how many blocks we actually pinned
+ //
+ hfsmp->hfs_hotfile_freeblks = hfsmp->hfs_hotfile_maxblks - total_pinned;
+
+ hfs_unlock(VTOC(hfsmp->hfc_filevp));
+
+ FREE(iterator, M_TEMP);
+ hfsmp->hfc_stage = stage;
+ wakeup((caddr_t)&hfsmp->hfc_stage);
+ return (error);
+}
+
+void
+hfs_repin_hotfiles(struct hfsmount *hfsmp)
+{
+ int error, need_close;
+
+ lck_mtx_lock(&hfsmp->hfc_mutex);
+
+ if (hfsmp->hfc_filevp == NULL) {
+ error = hfc_btree_open(hfsmp, &hfsmp->hfc_filevp);
+ if (!error) {
+ need_close = 1;
+ } else {
+ printf("hfs: failed to open the btree err=%d. Unable to re-pin hotfiles.\n", error);
+ lck_mtx_unlock(&hfsmp->hfc_mutex);
+ return;
+ }
+ } else {
+ need_close = 0;
+ }
+
+ hfs_pin_vnode(hfsmp, hfsmp->hfc_filevp, HFS_PIN_IT, NULL, vfs_context_kernel());
+
+ hfs_hotfile_repin_files(hfsmp);
+
+ if (need_close) {
+ (void) hfc_btree_close(hfsmp, hfsmp->hfc_filevp);
+ hfsmp->hfc_filevp = NULL;
+ }
+
+ lck_mtx_unlock(&hfsmp->hfc_mutex);
+}
+
+/*
+ * For a given file ID, find and pin all of its overflow extents to the underlying CS
+ * device. Assumes that the extents overflow b-tree is locked for the duration of this call.
+ *
+ * Emit the number of blocks pinned in output argument 'pinned'
+ *
+ * Return success or failure (errno) in return value.
+ *
+ */
+int hfs_pin_overflow_extents (struct hfsmount *hfsmp, uint32_t fileid,
+ uint8_t forktype, uint32_t *pinned) {
+
+ struct BTreeIterator *ext_iter = NULL;
+ ExtentKey *ext_key_ptr = NULL;
+ ExtentRecord ext_data;
+ FSBufferDescriptor btRecord;
+ uint16_t btRecordSize;
+ int error = 0;
+
+ uint32_t pinned_blocks = 0;
+
+
+ MALLOC (ext_iter, struct BTreeIterator*, sizeof (struct BTreeIterator), M_TEMP, M_WAITOK);
+ if (ext_iter == NULL) {
+ return ENOMEM;
+ }
+ bzero (ext_iter, sizeof(*ext_iter));
+
+ BTInvalidateHint (ext_iter);
+ ext_key_ptr = (ExtentKey*)&ext_iter->key;
+ btRecord.bufferAddress = &ext_data;
+ btRecord.itemCount = 1;
+
+ /*
+ * This is like when you delete a file; we don't actually need most of the search machinery because
+ * we are going to need all of the extent records that belong to this file (for a given fork type),
+ * so we might as well use a straight-up iterator.
+ *
+ * Position the B-Tree iterator at the first record with this file ID
+ */
+ btRecord.itemSize = sizeof (HFSPlusExtentRecord);
+ ext_key_ptr->hfsPlus.keyLength = kHFSPlusExtentKeyMaximumLength;
+ ext_key_ptr->hfsPlus.forkType = forktype;
+ ext_key_ptr->hfsPlus.pad = 0;
+ ext_key_ptr->hfsPlus.fileID = fileid;
+ ext_key_ptr->hfsPlus.startBlock = 0;
+
+ error = BTSearchRecord (VTOF(hfsmp->hfs_extents_vp), ext_iter, &btRecord, &btRecordSize, ext_iter);
+ if (error == btNotFound) {
+ /* empty b-tree, so that's ok. we'll fall out during error check below. */
+ error = 0;
+ }
+
+ while (1) {
+ uint32_t found_fileid;
+ uint32_t pblocks;
+
+ error = BTIterateRecord (VTOF(hfsmp->hfs_extents_vp), kBTreeNextRecord, ext_iter, &btRecord, &btRecordSize);
+ if (error) {
+ /* swallow it if it's btNotFound, otherwise just bail out */
+ if (error == btNotFound)
+ error = 0;
+ break;
+ }
+
+ found_fileid = ext_key_ptr->hfsPlus.fileID;
+ /*
+ * We only do one fork type at a time. So if either the fork-type doesn't
+ * match what we are looking for (resource or data), OR the file id doesn't match
+ * which indicates that there's nothing more with this file ID as the key, then bail out
+ */
+ if ((found_fileid != fileid) || (ext_key_ptr->hfsPlus.forkType != forktype)) {
+ error = 0;
+ break;
+ }
+
+ /* Otherwise, we now have an extent record. Process and pin all of the file extents. */
+ pblocks = 0;
+ error = hfs_pin_extent_record (hfsmp, ext_data.hfsPlus, &pblocks);
+
+ if (error) {
+ break;
+ }
+ pinned_blocks += pblocks;
+
+ /* if 8th extent is empty, then bail out */
+ if (ext_data.hfsPlus[kHFSPlusExtentDensity-1].startBlock == 0) {
+ error = 0;
+ break;
+ }
+
+ } // end extent-getting loop
+
+ /* dump the iterator */
+ FREE (ext_iter, M_TEMP);
+
+ if (error == 0) {
+ /*
+ * In the event that the file has no overflow extents, pinned_blocks
+ * will never be updated, so we'll properly export 0 pinned blocks to caller
+ */
+ *pinned = pinned_blocks;
+ }
+
+ return error;
+
+}
+
+
+static int
+hfs_getvnode_and_pin (struct hfsmount *hfsmp, uint32_t fileid, uint32_t *pinned) {
+ struct vnode *vp;
+ int error = 0;
+ *pinned = 0;
+ uint32_t pblocks;
+
+ /*
+ * Acquire the vnode for this file. This returns a locked cnode on success
+ */
+ error = hfs_vget(hfsmp, fileid, &vp, 0, 0);
+ if (error) {
+ /* It's possible the file was open-unlinked. In this case, we'll get ENOENT back. */
+ return error;
+ }
+
+ /*
+ * Symlinks that may have been inserted into the hotfile zone during a previous OS are now stuck
+ * here. We do not want to move them.
+ */
+ if (!vnode_isreg(vp)) {
+ hfs_unlock(VTOC(vp));
+ vnode_put(vp);
+ return EPERM;
+ }
+
+ if (!(VTOC(vp)->c_attr.ca_recflags & kHFSFastDevPinnedMask)) {
+ hfs_unlock(VTOC(vp));
+ vnode_put(vp);
+ return EINVAL;
+ }
+
+ error = hfs_pin_vnode(hfsmp, vp, HFS_PIN_IT, &pblocks, vfs_context_kernel());
+ if (error == 0) {
+ *pinned = pblocks;
+ }
+
+ hfs_unlock(VTOC(vp));
+ vnode_put(vp);
+
+ return error;
+
+}
+
+/*
+ * Pins an HFS Extent record to the underlying CoreStorage. Assumes that Catalog & Extents overflow
+ * B-trees are held locked, as needed.
+ *
+ * Returns the number of blocks pinned in the output argument 'pinned'
+ *
+ * Returns error status (0 || errno) in return value.
+ */
+static int hfs_pin_extent_record (struct hfsmount *hfsmp, HFSPlusExtentRecord extents, uint32_t *pinned) {
+ uint32_t pb = 0;
+ int i;
+ int error;
+
+ if (pinned == NULL) {
+ return EINVAL;
+ }
+ *pinned = 0;
+
+
+
+ /* iterate through the extents */
+ for ( i = 0; i < kHFSPlusExtentDensity; i++) {
+ if (extents[i].startBlock == 0) {
+ break;
+ }
+
+ error = hfs_pin_block_range (hfsmp, HFS_PIN_IT, extents[i].startBlock,
+ extents[i].blockCount, vfs_context_kernel());
+
+ if (error) {
+ break;
+ }
+ pb += extents[i].blockCount;
+ }
+
+ *pinned = pb;
+
+ return error;
+}
+
+/*
+ * Consume an HFS Plus on-disk catalog record and pin its blocks
+ * to the underlying CS devnode.
+ *
+ * NOTE: This is an important distinction!
+ * This function takes in an HFSPlusCatalogFile* which is the actual
+ * 200-some-odd-byte on-disk representation in the Catalog B-Tree (not
+ * one of the run-time structs that we normally use.
+ *
+ * This assumes that the catalog and extents-overflow btrees
+ * are locked, at least in shared mode
+ */
+static int hfs_pin_catalog_rec (struct hfsmount *hfsmp, HFSPlusCatalogFile *cfp, int rsrc) {
+ uint32_t pinned_blocks = 0;
+ HFSPlusForkData *forkdata;
+ int error = 0;
+ uint8_t forktype = 0;
+
+ if (rsrc) {
+ forkdata = &cfp->resourceFork;
+ forktype = 0xff;
+ }
+ else {
+ forkdata = &cfp->dataFork;
+ }
+
+ uint32_t pblocks = 0;
+
+ /* iterate through the inline extents */
+ error = hfs_pin_extent_record (hfsmp, forkdata->extents, &pblocks);
+ if (error) {
+ return error;
+ }
+
+ pinned_blocks += pblocks;
+ pblocks = 0;
+
+ /* it may have overflow extents */
+ if (forkdata->extents[kHFSPlusExtentDensity-1].startBlock != 0) {
+ error = hfs_pin_overflow_extents (hfsmp, cfp->fileID, forktype, &pblocks);
+ }
+ pinned_blocks += pblocks;
+
+ hfsmp->hfs_hotfile_freeblks -= pinned_blocks;
+
+ return error;
+}
+
+
+/*
+ *
+ */
+int
+hfs_recording_init(struct hfsmount *hfsmp)
+{
+ CatalogKey * keyp;
+ CatalogRecord * datap;
+ u_int32_t dataSize;
+ HFSPlusCatalogFile *filep;
+ BTScanState scanstate;
+ BTreeIterator * iterator = NULL;
+ FSBufferDescriptor record;
+ HotFileKey * key;
+ filefork_t * filefork;
+ u_int32_t data;
+ struct cat_attr cattr;
+ u_int32_t cnid;
+ int error = 0;
+ long starting_temp;
+
+ int started_tr = 0;
+ int started_scan = 0;
+
+ int inserted = 0; /* debug variables */
+ int filecount = 0;
+ int uncacheable = 0;
+
+ /*
+ * For now, only the boot volume is supported.
+ */
+ if ((vfs_flags(HFSTOVFS(hfsmp)) & MNT_ROOTFS) == 0) {
+ hfsmp->hfc_stage = HFC_DISABLED;
+ return (EPERM);
+ }
+
+ /* We grab the HFC mutex even though we're not fully mounted yet, just for orderliness */
+ lck_mtx_lock (&hfsmp->hfc_mutex);
+
+ /*
+ * Tracking of hot files requires up-to-date access times.
+ * So if access time updates are disabled, then we disable
+ * hot files, too.
+ */
+ if (vfs_flags(HFSTOVFS(hfsmp)) & MNT_NOATIME) {
+ hfsmp->hfc_stage = HFC_DISABLED;
+ lck_mtx_unlock (&hfsmp->hfc_mutex);
+ return EPERM;
+ }
+
+ //
+ // Check if we've been asked to suspend operation
+ //
+ cnid = GetFileInfo(HFSTOVCB(hfsmp), kRootDirID, ".hotfile-suspend", &cattr, NULL);
+ if (cnid != 0) {
+ printf("hfs: %s: %s: hotfiles explicitly disabled! remove /.hotfiles-suspend to re-enable\n", hfsmp->vcbVN, __FUNCTION__);
+ hfsmp->hfc_stage = HFC_DISABLED;
+ lck_mtx_unlock (&hfsmp->hfc_mutex);
+ return EPERM;
+ }
+
+ //
+ // Check if we've been asked to reset our state.
+ //
+ cnid = GetFileInfo(HFSTOVCB(hfsmp), kRootDirID, ".hotfile-reset", &cattr, NULL);
+ if (cnid != 0) {
+ hfs_hotfile_reset(hfsmp);
+ }
+
+ if (hfsmp->hfs_flags & HFS_CS_HOTFILE_PIN) {
+ //
+ // Cooperative Fusion (CF) systems use different constants
+ // than traditional hotfile systems. These were picked after a bit of
+ // experimentation - we can cache many more files on the
+ // ssd in an CF system and we can do so more rapidly
+ // so bump the limits considerably (and turn down the
+ // duration so that it doesn't take weeks to adopt all
+ // the files).
+ //
+ hfc_default_file_count = 20000;
+ hfc_default_duration = 300; // 5min
+ hfc_max_file_count = 50000;
+ hfc_max_file_size = (512ULL * 1024ULL * 1024ULL);
+ }
+
+ /*
+ * If the Hot File btree exists then metadata zone is ready.
+ */
+ cnid = GetFileInfo(HFSTOVCB(hfsmp), kRootDirID, HFC_FILENAME, &cattr, NULL);
+ if (cnid != 0 && S_ISREG(cattr.ca_mode)) {
+ int recreate = 0;
+
+ if (hfsmp->hfc_stage == HFC_DISABLED)
+ hfsmp->hfc_stage = HFC_IDLE;
+ hfsmp->hfs_hotfile_freeblks = 0;
+
+ if ((hfsmp->hfs_flags & HFS_CS_HOTFILE_PIN) && cattr.ca_blocks > 0) {
+ //
+ // make sure the hotfile btree is pinned
+ //
+ error = hfc_btree_open(hfsmp, &hfsmp->hfc_filevp);
+ if (!error) {
+ /* XXX: must fix hfs_pin_vnode too */
+ hfs_pin_vnode(hfsmp, hfsmp->hfc_filevp, HFS_PIN_IT, NULL, vfs_context_kernel());
+
+ } else {
+ printf("hfs: failed to open the btree err=%d. Recreating hotfile btree.\n", error);
+ recreate = 1;
+ }
+
+ hfs_hotfile_repin_files(hfsmp);
+
+ if (hfsmp->hfc_filevp) {
+ (void) hfc_btree_close(hfsmp, hfsmp->hfc_filevp);
+ hfsmp->hfc_filevp = NULL;
+ }
+
+ } else if (hfsmp->hfs_flags & HFS_CS_HOTFILE_PIN) {
+ // hmmm, the hotfile btree is zero bytes long? how odd. let's recreate it.
+ printf("hfs: hotfile btree is zero bytes long?! recreating it.\n");
+ recreate = 1;
+ }
+
+ if (!recreate) {
+ /* don't forget to unlock the mutex */
+ lck_mtx_unlock (&hfsmp->hfc_mutex);
+ return (0);
+ } else {
+ //
+ // open the hotfile btree file ignoring errors because
+ // we need the vnode pointer for hfc_btree_delete() to
+ // be able to do its work
+ //
+ error = hfc_btree_open_ext(hfsmp, &hfsmp->hfc_filevp, 1);
+ if (!error) {
+ // and delete it!
+ error = hfc_btree_delete(hfsmp);
+ (void) hfc_btree_close(hfsmp, hfsmp->hfc_filevp);
+ hfsmp->hfc_filevp = NULL;
+ }
+ }
+ }
+
+ printf("hfs: %s: %s: creating the hotfile btree\n", hfsmp->vcbVN, __FUNCTION__);
+ if (hfs_start_transaction(hfsmp) != 0) {
+ lck_mtx_unlock (&hfsmp->hfc_mutex);
+ return EINVAL;
+ }
+
+ /* B-tree creation must be journaled */
+ started_tr = 1;
+
+ error = hfc_btree_create(hfsmp, HFSTOVCB(hfsmp)->blockSize, HFC_DEFAULT_FILE_COUNT);
+ if (error) {
+#if HFC_VERBOSE
+ printf("hfs: Error %d creating hot file b-tree on %s \n", error, hfsmp->vcbVN);
+#endif
+ goto recording_init_out;
+ }
+
+ hfs_end_transaction (hfsmp);
+ started_tr = 0;
+ /*
+ * Do a journal flush + flush track cache. We have to ensure that the async I/Os have been issued to the media
+ * before proceeding.
+ */
+ hfs_flush (hfsmp, HFS_FLUSH_FULL);
+
+ /* now re-start a new transaction */
+ if (hfs_start_transaction (hfsmp) != 0) {
+ lck_mtx_unlock (&hfsmp->hfc_mutex);
+ return EINVAL;
+ }
+ started_tr = 1;
+
+ /*
+ * Open the Hot File B-tree file for writing.
+ */
+ if (hfsmp->hfc_filevp)
+ panic("hfs_recording_init: hfc_filevp exists (vp = %p)", hfsmp->hfc_filevp);
+
+ error = hfc_btree_open(hfsmp, &hfsmp->hfc_filevp);
+ if (error) {
+#if HFC_VERBOSE
+ printf("hfs: Error %d opening hot file b-tree on %s \n", error, hfsmp->vcbVN);
+#endif
+ goto recording_init_out;
+ }
+
+ /*
+ * This function performs work similar to namei; we must NOT hold the catalog lock while
+ * calling it. This will decorate catalog records as being pinning candidates. (no hotfiles work)
+ */
+ hfs_setup_default_cf_hotfiles(hfsmp);
+
+ /*
+ * now grab the hotfiles b-tree vnode/cnode lock first, as it is not classified as a systemfile.
+ */
+ if (hfs_lock(VTOC(hfsmp->hfc_filevp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) != 0) {
+ error = EPERM;
+ (void) hfc_btree_close(hfsmp, hfsmp->hfc_filevp);
+ /* zero it out to avoid pinning later on */
+ hfsmp->hfc_filevp = NULL;
+ goto recording_init_out;
+ }
+
+ MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
+ if (iterator == NULL) {
+ error = ENOMEM;
+ hfs_unlock (VTOC(hfsmp->hfc_filevp));
+ (void) hfc_btree_close(hfsmp, hfsmp->hfc_filevp);
+ /* zero it out to avoid pinning */
+ hfsmp->hfc_filevp = NULL;
+ goto recording_init_out;
+ }
+
+ bzero(iterator, sizeof(*iterator));
+ key = (HotFileKey*) &iterator->key;
+ key->keyLength = HFC_KEYLENGTH;
+
+ record.bufferAddress = &data;
+ record.itemSize = sizeof(u_int32_t);
+ record.itemCount = 1;
+
+#if HFC_VERBOSE
+ printf("hfs: Evaluating space for \"%s\" metadata zone... (freeblks %d)\n", HFSTOVCB(hfsmp)->vcbVN,
+ hfsmp->hfs_hotfile_freeblks);
+#endif
+
+ /*
+ * Get ready to scan the Catalog file. We explicitly do NOT grab the catalog lock because
+ * we're fully single-threaded at the moment (by virtue of being called during mount()),
+ * and if we have to grow the hotfile btree, then we would need to grab the catalog lock
+ * and if we take a shared lock here, it would deadlock (see <rdar://problem/21486585>)
+ *
+ * We already started a transaction so we should already be holding the journal lock at this point.
+ * Note that we have to hold the journal lock / start a txn BEFORE the systemfile locks.
+ */
+
+ error = BTScanInitialize(VTOF(HFSTOVCB(hfsmp)->catalogRefNum), 0, 0, 0,
+ kCatSearchBufferSize, &scanstate);
+ if (error) {
+ printf("hfs_recording_init: err %d BTScanInit\n", error);
+
+ /* drop the systemfile locks */
+ hfs_unlock(VTOC(hfsmp->hfc_filevp));
+
+ (void) hfc_btree_close(hfsmp, hfsmp->hfc_filevp);
+
+ /* zero it out to avoid pinning */
+ hfsmp->hfc_filevp = NULL;
+ goto recording_init_out;
+ }
+
+ started_scan = 1;
+
+ filefork = VTOF(hfsmp->hfc_filevp);
+
+ starting_temp = random() % HF_TEMP_RANGE;
+
+ /*
+ * Visit all the catalog btree leaf records. We have to hold the catalog lock to do this.
+ *
+ * NOTE: The B-Tree scanner reads from the media itself. Under normal circumstances it would be
+ * fine to simply use b-tree routines to read blocks that correspond to b-tree nodes, because the
+ * block cache is going to ensure you always get the cached copy of a block (even if a journal
+ * txn has modified one of those blocks). That is NOT true when
+ * using the scanner. In particular, it will always read whatever is on-disk. So we have to ensure
+ * that the journal has flushed and that the async I/Os to the metadata files have been issued.
+ */
+ for (;;) {
+ error = BTScanNextRecord(&scanstate, 0, (void **)&keyp, (void **)&datap, &dataSize);
+ if (error) {
+ if (error == btNotFound)
+ error = 0;
+ else
+ printf("hfs_recording_init: err %d BTScanNext\n", error);
+ break;
+ }
+ if ((datap->recordType != kHFSPlusFileRecord) ||
+ (dataSize != sizeof(HFSPlusCatalogFile))) {
+ continue;
+ }
+ filep = (HFSPlusCatalogFile *)datap;
+ filecount++;
+
+ if (hfsmp->hfs_flags & HFS_CS_HOTFILE_PIN) {
+ if (filep->flags & kHFSDoNotFastDevPinMask) {
+ uncacheable++;
+ }
+
+ //
+ // If the file does not have the FastDevPinnedMask set, we
+ // can ignore it and just go to the next record.
+ //
+ if ((filep->flags & kHFSFastDevPinnedMask) == 0) {
+ continue;
+ }
+ } else if (filep->dataFork.totalBlocks == 0) {
+ continue;
+ }
+
+ /*
+ * On a regular hdd, any file that has blocks inside
+ * the hot file space is recorded for later eviction.
+ *
+ * For now, resource forks are ignored.
+ *
+ * We don't do this on CF systems as there is no real
+ * hotfile area - we just pin/unpin blocks belonging to
+ * interesting files.
+ */
+ if (!(hfsmp->hfs_flags & HFS_CS_HOTFILE_PIN) && !hotextents(hfsmp, &filep->dataFork.extents[0])) {
+ continue;
+ }
+ cnid = filep->fileID;
+
+ /* Skip over journal files. */
+ if (cnid == hfsmp->hfs_jnlfileid || cnid == hfsmp->hfs_jnlinfoblkid) {
+ continue;
+ }
+ /*
+ * XXX - need to skip quota files as well.
+ */
+
+ uint32_t temp;
+
+ if (hfsmp->hfs_flags & HFS_CS_HOTFILE_PIN) {
+ int rsrc = 0;
+
+ temp = (uint32_t)starting_temp++;
+ if (filep->flags & kHFSAutoCandidateMask) {
+ temp += MAX_NORMAL_TEMP;
+ }
+
+ /* use the data fork by default */
+ if (filep->dataFork.totalBlocks == 0) {
+ /*
+ * but if empty, switch to rsrc as its likely
+ * a compressed file
+ */
+ rsrc = 1;
+ }
+
+ error = hfs_pin_catalog_rec (hfsmp, filep, rsrc);
+ if (error)
+ break;
+
+ } else {
+ temp = HFC_MINIMUM_TEMPERATURE;
+ }
+
+ /* Insert a hot file entry. */
+ key->keyLength = HFC_KEYLENGTH;
+ key->temperature = temp;
+ key->fileID = cnid;
+ key->forkType = 0;
+ data = 0x3f3f3f3f;
+ error = BTInsertRecord(filefork, iterator, &record, record.itemSize);
+ if (error) {
+ printf("hfs_recording_init: BTInsertRecord failed %d (fileid %d)\n", error, key->fileID);
+ error = MacToVFSError(error);
+ break;
+ }
+
+ /* Insert the corresponding thread record. */
+ key->keyLength = HFC_KEYLENGTH;
+ key->temperature = HFC_LOOKUPTAG;
+ key->fileID = cnid;
+ key->forkType = 0;
+ data = temp;
+ error = BTInsertRecord(filefork, iterator, &record, record.itemSize);