+ hfs_unlock_mount(hfsmp);
+ break;
+
+ case HFS_MARK_BOOT_CORRUPT:
+ /* Mark the boot volume corrupt by setting
+ * kHFSVolumeInconsistentBit in the volume header. This will
+ * force fsck_hfs on next mount.
+ */
+ if (!kauth_cred_issuser(kauth_cred_get())) {
+ return EACCES;
+ }
+
+ /* Allowed only on the root vnode of the boot volume */
+ if (!(vfs_flags(HFSTOVFS(hfsmp)) & MNT_ROOTFS) ||
+ !vnode_isvroot(vp)) {
+ return EINVAL;
+ }
+ if (hfsmp->hfs_flags & HFS_READ_ONLY) {
+ return (EROFS);
+ }
+ printf ("hfs_vnop_ioctl: Marking the boot volume corrupt.\n");
+ hfs_mark_inconsistent(hfsmp, HFS_FSCK_FORCED);
+ break;
+
+ case HFS_FSCTL_GET_JOURNAL_INFO:
+ jip = (struct hfs_journal_info*)ap->a_data;
+
+ if (vp == NULLVP)
+ return EINVAL;
+
+ if (hfsmp->jnl == NULL) {
+ jnl_start = 0;
+ jnl_size = 0;
+ } else {
+ jnl_start = hfs_blk_to_bytes(hfsmp->jnl_start, hfsmp->blockSize) + hfsmp->hfsPlusIOPosOffset;
+ jnl_size = hfsmp->jnl_size;
+ }
+
+ jip->jstart = jnl_start;
+ jip->jsize = jnl_size;
+ break;
+
+ case HFS_SET_ALWAYS_ZEROFILL: {
+ struct cnode *cp = VTOC(vp);
+
+ if (*(int *)ap->a_data) {
+ cp->c_flag |= C_ALWAYS_ZEROFILL;
+ } else {
+ cp->c_flag &= ~C_ALWAYS_ZEROFILL;
+ }
+ break;
+ }
+
+ case HFS_DISABLE_METAZONE: {
+ /* Only root can disable metadata zone */
+ if (!kauth_cred_issuser(kauth_cred_get())) {
+ return EACCES;
+ }
+ if (hfsmp->hfs_flags & HFS_READ_ONLY) {
+ return (EROFS);
+ }
+
+ /* Disable metadata zone now */
+ (void) hfs_metadatazone_init(hfsmp, true);
+ printf ("hfs: Disabling metadata zone on %s\n", hfsmp->vcbVN);
+ break;
+ }
+
+
+ case HFS_FSINFO_METADATA_BLOCKS: {
+ int error;
+ struct hfsinfo_metadata *hinfo;
+
+ hinfo = (struct hfsinfo_metadata *)ap->a_data;
+
+ /* Get information about number of metadata blocks */
+ error = hfs_getinfo_metadata_blocks(hfsmp, hinfo);
+ if (error) {
+ return error;
+ }
+
+ break;
+ }
+
+ case HFS_GET_FSINFO: {
+ hfs_fsinfo *fsinfo = (hfs_fsinfo *)ap->a_data;
+
+ /* Only root is allowed to get fsinfo */
+ if (!kauth_cred_issuser(kauth_cred_get())) {
+ return EACCES;
+ }
+
+ /*
+ * Make sure that the caller's version number matches with
+ * the kernel's version number. This will make sure that
+ * if the structures being read/written into are changed
+ * by the kernel, the caller will not read incorrect data.
+ *
+ * The first three fields --- request_type, version and
+ * flags are same for all the hfs_fsinfo structures, so
+ * we can access the version number by assuming any
+ * structure for now.
+ */
+ if (fsinfo->header.version != HFS_FSINFO_VERSION) {
+ return ENOTSUP;
+ }
+
+ /* Make sure that the current file system is not marked inconsistent */
+ if (hfsmp->vcbAtrb & kHFSVolumeInconsistentMask) {
+ return EIO;
+ }
+
+ return hfs_get_fsinfo(hfsmp, ap->a_data);
+ }
+
+ case HFS_CS_FREESPACE_TRIM: {
+ int error = 0;
+ int lockflags = 0;
+
+ /* Only root allowed */
+ if (!kauth_cred_issuser(kauth_cred_get())) {
+ return EACCES;
+ }
+
+ /*
+ * This core functionality is similar to hfs_scan_blocks().
+ * The main difference is that hfs_scan_blocks() is called
+ * as part of mount where we are assured that the journal is
+ * empty to start with. This fcntl() can be called on a
+ * mounted volume, therefore it has to flush the content of
+ * the journal as well as ensure the state of summary table.
+ *
+ * This fcntl scans over the entire allocation bitmap,
+ * creates list of all the free blocks, and issues TRIM
+ * down to the underlying device. This can take long time
+ * as it can generate up to 512MB of read I/O.
+ */
+
+ if ((hfsmp->hfs_flags & HFS_SUMMARY_TABLE) == 0) {
+ error = hfs_init_summary(hfsmp);
+ if (error) {
+ printf("hfs: fsctl() could not initialize summary table for %s\n", hfsmp->vcbVN);
+ return error;
+ }
+ }
+
+ /*
+ * The journal maintains list of recently deallocated blocks to
+ * issue DKIOCUNMAPs when the corresponding journal transaction is
+ * flushed to the disk. To avoid any race conditions, we only
+ * want one active trim list and only one thread issuing DKIOCUNMAPs.
+ * Therefore we make sure that the journal trim list is sync'ed,
+ * empty, and not modifiable for the duration of our scan.
+ *
+ * Take the journal lock before flushing the journal to the disk.
+ * We will keep on holding the journal lock till we don't get the
+ * bitmap lock to make sure that no new journal transactions can
+ * start. This will make sure that the journal trim list is not
+ * modified after the journal flush and before getting bitmap lock.
+ * We can release the journal lock after we acquire the bitmap
+ * lock as it will prevent any further block deallocations.
+ */
+ hfs_journal_lock(hfsmp);
+
+ /* Flush the journal and wait for all I/Os to finish up */
+ error = hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
+ if (error) {
+ hfs_journal_unlock(hfsmp);
+ return error;
+ }
+
+ /* Take bitmap lock to ensure it is not being modified */
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
+
+ /* Release the journal lock */
+ hfs_journal_unlock(hfsmp);
+
+ /*
+ * ScanUnmapBlocks reads the bitmap in large block size
+ * (up to 1MB) unlike the runtime which reads the bitmap
+ * in the 4K block size. This can cause buf_t collisions
+ * and potential data corruption. To avoid this, we
+ * invalidate all the existing buffers associated with
+ * the bitmap vnode before scanning it.
+ *
+ * Note: ScanUnmapBlock() cleans up all the buffers
+ * after itself, so there won't be any large buffers left
+ * for us to clean up after it returns.
+ */
+ error = buf_invalidateblks(hfsmp->hfs_allocation_vp, 0, 0, 0);
+ if (error) {
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ return error;
+ }
+
+ /* Traverse bitmap and issue DKIOCUNMAPs */
+ error = ScanUnmapBlocks(hfsmp);
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ if (error) {
+ return error;
+ }
+
+ break;
+ }
+
+ case HFS_SET_HOTFILE_STATE: {
+ int error;
+ struct cnode *cp = VTOC(vp);
+ uint32_t hf_state = *((uint32_t*)ap->a_data);
+ uint32_t num_unpinned = 0;
+
+ error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
+ if (error) {
+ return error;
+ }
+
+ // printf("hfs: setting hotfile state %d on %s\n", hf_state, vp->v_name);
+ if (hf_state == HFS_MARK_FASTDEVCANDIDATE) {
+ vnode_setfastdevicecandidate(vp);
+
+ cp->c_attr.ca_recflags |= kHFSFastDevCandidateMask;
+ cp->c_attr.ca_recflags &= ~kHFSDoNotFastDevPinMask;
+ cp->c_flag |= C_MODIFIED;
+ } else if (hf_state == HFS_UNMARK_FASTDEVCANDIDATE || hf_state == HFS_NEVER_FASTDEVCANDIDATE) {
+ vnode_clearfastdevicecandidate(vp);
+ hfs_removehotfile(vp);
+
+ if (cp->c_attr.ca_recflags & kHFSFastDevPinnedMask) {
+ hfs_pin_vnode(hfsmp, vp, HFS_UNPIN_IT, &num_unpinned, ap->a_context);
+ }
+
+ if (hf_state == HFS_NEVER_FASTDEVCANDIDATE) {
+ cp->c_attr.ca_recflags |= kHFSDoNotFastDevPinMask;
+ }
+ cp->c_attr.ca_recflags &= ~(kHFSFastDevCandidateMask|kHFSFastDevPinnedMask);
+ cp->c_flag |= C_MODIFIED;
+
+ } else {
+ error = EINVAL;
+ }
+
+ if (num_unpinned != 0) {
+ lck_mtx_lock(&hfsmp->hfc_mutex);
+ hfsmp->hfs_hotfile_freeblks += num_unpinned;
+ lck_mtx_unlock(&hfsmp->hfc_mutex);
+ }
+
+ hfs_unlock(cp);
+ return error;
+ break;
+ }
+
+ case HFS_REPIN_HOTFILE_STATE: {
+ int error=0;
+ uint32_t repin_what = *((uint32_t*)ap->a_data);
+
+ /* Only root allowed */
+ if (!kauth_cred_issuser(kauth_cred_get())) {
+ return EACCES;
+ }
+
+ if (!(hfsmp->hfs_flags & (HFS_CS_METADATA_PIN | HFS_CS_HOTFILE_PIN))) {
+ // this system is neither regular Fusion or Cooperative Fusion
+ // so this fsctl makes no sense.
+ return EINVAL;
+ }
+
+ //
+ // After a converting a CoreStorage volume to be encrypted, the
+ // extents could have moved around underneath us. This call
+ // allows corestoraged to re-pin everything that should be
+ // pinned (it would happen on the next reboot too but that could
+ // be a long time away).
+ //
+ if ((repin_what & HFS_REPIN_METADATA) && (hfsmp->hfs_flags & HFS_CS_METADATA_PIN)) {
+ hfs_pin_fs_metadata(hfsmp);
+ }
+ if ((repin_what & HFS_REPIN_USERDATA) && (hfsmp->hfs_flags & HFS_CS_HOTFILE_PIN)) {
+ hfs_repin_hotfiles(hfsmp);
+ }
+ if ((repin_what & HFS_REPIN_USERDATA) && (hfsmp->hfs_flags & HFS_CS_SWAPFILE_PIN)) {
+ //XXX Swapfiles (marked SWAP_PINNED) may have moved too.
+ //XXX Do we care? They have a more transient/dynamic nature/lifetime.
+ }
+
+ return error;