+#define HFS_MIN_SIZE (32LL * 1024LL * 1024LL)
+
+/*
+ * Truncate a file system (while still mounted).
+ */
+__private_extern__
+int
+hfs_truncatefs(struct hfsmount *hfsmp, u_int64_t newsize, __unused vfs_context_t context)
+{
+ struct buf *bp = NULL;
+ u_int64_t oldsize;
+ u_int32_t newblkcnt;
+ u_int32_t reclaimblks;
+ int lockflags = 0;
+ int transaction_begun = 0;
+ int error;
+
+
+ lck_mtx_lock(&hfsmp->hfs_mutex);
+ if (hfsmp->hfs_flags & HFS_RESIZE_IN_PROGRESS) {
+ lck_mtx_unlock(&hfsmp->hfs_mutex);
+ return (EALREADY);
+ }
+ hfsmp->hfs_flags |= HFS_RESIZE_IN_PROGRESS;
+ hfsmp->hfs_resize_filesmoved = 0;
+ hfsmp->hfs_resize_totalfiles = 0;
+ lck_mtx_unlock(&hfsmp->hfs_mutex);
+
+ /*
+ * - Journaled HFS Plus volumes only.
+ * - No embedded volumes.
+ */
+ if ((hfsmp->jnl == NULL) ||
+ (hfsmp->hfsPlusIOPosOffset != 0)) {
+ error = EPERM;
+ goto out;
+ }
+ oldsize = (u_int64_t)hfsmp->totalBlocks * (u_int64_t)hfsmp->blockSize;
+ newblkcnt = newsize / hfsmp->blockSize;
+ reclaimblks = hfsmp->totalBlocks - newblkcnt;
+
+ /* Make sure new size is valid. */
+ if ((newsize < HFS_MIN_SIZE) ||
+ (newsize >= oldsize) ||
+ (newsize % hfsmp->hfs_phys_block_size)) {
+ error = EINVAL;
+ goto out;
+ }
+ /* Make sure there's enough space to work with. */
+ if (reclaimblks >= hfs_freeblks(hfsmp, 1)) {
+ error = ENOSPC;
+ goto out;
+ }
+ /* Start with a clean journal. */
+ journal_flush(hfsmp->jnl);
+
+ if (hfs_start_transaction(hfsmp) != 0) {
+ error = EINVAL;
+ goto out;
+ }
+ transaction_begun = 1;
+
+ /*
+ * Look for files that have blocks beyond newblkcnt.
+ */
+ if (hfs_isallocated(hfsmp, newblkcnt, reclaimblks - 1)) {
+ /*
+ * hfs_reclaimspace will use separate transactions when
+ * relocating files (so we don't overwhelm the journal).
+ */
+ hfs_end_transaction(hfsmp);
+ transaction_begun = 0;
+
+ /* Attempt to reclaim some space. */
+ if (hfs_reclaimspace(hfsmp, newblkcnt, reclaimblks) != 0) {
+ printf("hfs_truncatefs: couldn't reclaim space on %s\n", hfsmp->vcbVN);
+ error = ENOSPC;
+ goto out;
+ }
+ if (hfs_start_transaction(hfsmp) != 0) {
+ error = EINVAL;
+ goto out;
+ }
+ transaction_begun = 1;
+
+ /* Check if we're clear now. */
+ if (hfs_isallocated(hfsmp, newblkcnt, reclaimblks - 1)) {
+ printf("hfs_truncatefs: didn't reclaim enough space on %s\n", hfsmp->vcbVN);
+ error = EAGAIN; /* tell client to try again */
+ goto out;
+ }
+ }
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_EXTENTS | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
+
+ /*
+ * Mark the old alternate volume header as free.
+ * We don't bother shrinking allocation bitmap file.
+ */
+ if (hfsmp->blockSize == 512)
+ (void) BlockMarkFree(hfsmp, hfsmp->totalBlocks - 2, 2);
+ else
+ (void) BlockMarkFree(hfsmp, hfsmp->totalBlocks - 1, 1);
+
+ /*
+ * Allocate last block for alternate volume header.
+ */
+ if (hfsmp->blockSize == 512)
+ error = BlockMarkAllocated(hfsmp, newblkcnt - 2, 2);
+ else
+ error = BlockMarkAllocated(hfsmp, newblkcnt - 1, 1);
+
+ if (error) {
+ goto out;
+ }
+
+ /*
+ * Invalidate the existing alternate volume header.
+ *
+ * Don't do this as a transaction (don't call journal_modify_block)
+ * since this block will be outside of the truncated file system!
+ */
+ if (hfsmp->hfs_alt_id_sector) {
+ if (buf_meta_bread(hfsmp->hfs_devvp, hfsmp->hfs_alt_id_sector,
+ hfsmp->hfs_phys_block_size, NOCRED, &bp) == 0) {
+
+ bzero((void*)((char *)buf_dataptr(bp) + HFS_ALT_OFFSET(hfsmp->hfs_phys_block_size)), kMDBSize);
+ (void) VNOP_BWRITE(bp);
+ } else if (bp) {
+ buf_brelse(bp);
+ }
+ bp = NULL;
+ }
+
+ /* Log successful shrinking. */
+ printf("hfs_truncatefs: shrank \"%s\" to %d blocks (was %d blocks)\n",
+ hfsmp->vcbVN, newblkcnt, hfsmp->totalBlocks);
+
+ /*
+ * Adjust file system variables and flush them to disk.
+ */
+ hfsmp->freeBlocks -= hfsmp->totalBlocks - newblkcnt;
+ hfsmp->totalBlocks = newblkcnt;
+ hfsmp->hfs_phys_block_count = newsize / hfsmp->hfs_phys_block_size;
+ hfsmp->hfs_alt_id_sector = HFS_ALT_SECTOR(hfsmp->hfs_phys_block_size, hfsmp->hfs_phys_block_count);
+ MarkVCBDirty(hfsmp);
+ error = hfs_flushvolumeheader(hfsmp, MNT_WAIT, HFS_ALTFLUSH);
+ if (error)
+ panic("hfs_truncatefs: unexpected error flushing volume header (%d)\n", error);
+out:
+ if (lockflags) {
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ }
+ if (transaction_begun) {
+ hfs_end_transaction(hfsmp);
+ journal_flush(hfsmp->jnl);
+ }
+
+ lck_mtx_lock(&hfsmp->hfs_mutex);
+ hfsmp->hfs_flags &= ~HFS_RESIZE_IN_PROGRESS;
+ lck_mtx_unlock(&hfsmp->hfs_mutex);
+
+ return (error);
+}
+
+
+/*
+ * Reclaim space at the end of a file system.
+ */
+static int
+hfs_reclaimspace(struct hfsmount *hfsmp, u_long startblk, u_long reclaimblks)
+{
+ struct vnode *vp = NULL;
+ FCB *fcb;
+ struct BTreeIterator * iterator = NULL;
+ struct FSBufferDescriptor btdata;
+ struct HFSPlusCatalogFile filerec;
+ struct filefork *fp;
+ u_int32_t saved_next_allocation;
+ cnid_t * cnidbufp;
+ size_t cnidbufsize;
+ int filecnt = 0;
+ int maxfilecnt;
+ u_long block;
+ u_long datablks;
+ u_long rsrcblks;
+ u_long blkstomove = 0;
+ int lockflags;
+ int i;
+ int error;
+ int lastprogress = 0;
+
+
+ /* Check if Attributes file overlaps reclaim area. */
+ if (hfsmp->hfs_attribute_vp) {
+ fp = VTOF(hfsmp->hfs_attribute_vp);
+ datablks = 0;
+ for (i = 0; i < kHFSPlusExtentDensity; ++i) {
+ if (fp->ff_extents[i].blockCount == 0) {
+ break;
+ }
+ datablks += fp->ff_extents[i].blockCount;
+ block = fp->ff_extents[i].startBlock + fp->ff_extents[i].blockCount;
+ if (block >= startblk) {
+ printf("hfs_reclaimspace: Attributes file can't move\n");
+ return (EPERM);
+ }
+ }
+ if ((i == kHFSPlusExtentDensity) && (fp->ff_blocks > datablks)) {
+ if (hfs_overlapped_overflow_extents(hfsmp, startblk, datablks, kHFSAttributesFileID, 0)) {
+ printf("hfs_reclaimspace: Attributes file can't move\n");
+ return (EPERM);
+ }
+ }
+ }
+ /* Check if Catalog file overlaps reclaim area. */
+ fp = VTOF(hfsmp->hfs_catalog_vp);
+ datablks = 0;
+ for (i = 0; i < kHFSPlusExtentDensity; ++i) {
+ if (fp->ff_extents[i].blockCount == 0) {
+ break;
+ }
+ datablks += fp->ff_extents[i].blockCount;
+ block = fp->ff_extents[i].startBlock + fp->ff_extents[i].blockCount;
+ if (block >= startblk) {
+ printf("hfs_reclaimspace: Catalog file can't move\n");
+ return (EPERM);
+ }
+ }
+ if ((i == kHFSPlusExtentDensity) && (fp->ff_blocks > datablks)) {
+ if (hfs_overlapped_overflow_extents(hfsmp, startblk, datablks, kHFSCatalogFileID, 0)) {
+ printf("hfs_reclaimspace: Catalog file can't move\n");
+ return (EPERM);
+ }
+ }
+
+ /* For now move a maximum of 250,000 files. */
+ maxfilecnt = MIN(hfsmp->hfs_filecount, 250000);
+ maxfilecnt = MIN((u_long)maxfilecnt, reclaimblks);
+ cnidbufsize = maxfilecnt * sizeof(cnid_t);
+ if (kmem_alloc(kernel_map, (vm_offset_t *)&cnidbufp, cnidbufsize)) {
+ return (ENOMEM);
+ }
+ if (kmem_alloc(kernel_map, (vm_offset_t *)&iterator, sizeof(*iterator))) {
+ kmem_free(kernel_map, (vm_offset_t)cnidbufp, cnidbufsize);
+ return (ENOMEM);
+ }
+
+ saved_next_allocation = hfsmp->nextAllocation;
+ hfsmp->nextAllocation = hfsmp->hfs_metazone_start;
+
+ fcb = VTOF(hfsmp->hfs_catalog_vp);
+ bzero(iterator, sizeof(*iterator));
+
+ btdata.bufferAddress = &filerec;
+ btdata.itemSize = sizeof(filerec);
+ btdata.itemCount = 1;
+
+ /* Keep the Catalog and extents files locked during iteration. */
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_EXTENTS, HFS_SHARED_LOCK);
+
+ error = BTIterateRecord(fcb, kBTreeFirstRecord, iterator, NULL, NULL);
+ if (error) {
+ goto end_iteration;
+ }
+ /*
+ * Iterate over all the catalog records looking for files
+ * that overlap into the space we're trying to free up.
+ */
+ for (filecnt = 0; filecnt < maxfilecnt; ) {
+ error = BTIterateRecord(fcb, kBTreeNextRecord, iterator, &btdata, NULL);
+ if (error) {
+ if (error == fsBTRecordNotFoundErr || error == fsBTEndOfIterationErr) {
+ error = 0;
+ }
+ break;
+ }
+ if (filerec.recordType != kHFSPlusFileRecord) {
+ continue;
+ }
+ datablks = rsrcblks = 0;
+ /*
+ * Check if either fork overlaps target space.
+ */
+ for (i = 0; i < kHFSPlusExtentDensity; ++i) {
+ if (filerec.dataFork.extents[i].blockCount != 0) {
+ datablks += filerec.dataFork.extents[i].blockCount;
+ block = filerec.dataFork.extents[i].startBlock +
+ filerec.dataFork.extents[i].blockCount;
+ if (block >= startblk) {
+ if ((filerec.fileID == hfsmp->hfs_jnlfileid) ||
+ (filerec.fileID == hfsmp->hfs_jnlinfoblkid)) {
+ printf("hfs_reclaimspace: cannot move active journal\n");
+ error = EPERM;
+ goto end_iteration;
+ }
+ cnidbufp[filecnt++] = filerec.fileID;
+ blkstomove += filerec.dataFork.totalBlocks;
+ break;
+ }
+ }
+ if (filerec.resourceFork.extents[i].blockCount != 0) {
+ rsrcblks += filerec.resourceFork.extents[i].blockCount;
+ block = filerec.resourceFork.extents[i].startBlock +
+ filerec.resourceFork.extents[i].blockCount;
+ if (block >= startblk) {
+ cnidbufp[filecnt++] = filerec.fileID;
+ blkstomove += filerec.resourceFork.totalBlocks;
+ break;
+ }
+ }
+ }
+ /*
+ * Check for any overflow extents that overlap.
+ */
+ if (i == kHFSPlusExtentDensity) {
+ if (filerec.dataFork.totalBlocks > datablks) {
+ if (hfs_overlapped_overflow_extents(hfsmp, startblk, datablks, filerec.fileID, 0)) {
+ cnidbufp[filecnt++] = filerec.fileID;
+ blkstomove += filerec.dataFork.totalBlocks;
+ }
+ } else if (filerec.resourceFork.totalBlocks > rsrcblks) {
+ if (hfs_overlapped_overflow_extents(hfsmp, startblk, rsrcblks, filerec.fileID, 1)) {
+ cnidbufp[filecnt++] = filerec.fileID;
+ blkstomove += filerec.resourceFork.totalBlocks;
+ }
+ }
+ }
+ }
+
+end_iteration:
+ if (filecnt == 0) {
+ error = ENOSPC;
+ }
+ /* All done with catalog. */
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ if (error)
+ goto out;
+
+ /*
+ * Double check space requirements to make sure
+ * there is enough space to relocate any files
+ * that reside in the reclaim area.
+ *
+ * Blocks To Move --------------
+ * | | |
+ * V V V
+ * ------------------------------------------------------------------------
+ * | | / /// // |
+ * | | / /// // |
+ * | | / /// // |
+ * ------------------------------------------------------------------------
+ *
+ * <------------------- New Total Blocks ------------------><-- Reclaim -->
+ *
+ * <------------------------ Original Total Blocks ----------------------->
+ *
+ */
+ if ((reclaimblks + blkstomove) >= hfs_freeblks(hfsmp, 1)) {
+ error = ENOSPC;
+ goto out;
+ }
+ hfsmp->hfs_resize_filesmoved = 0;
+ hfsmp->hfs_resize_totalfiles = filecnt;
+
+ /* Now move any files that are in the way. */
+ for (i = 0; i < filecnt; ++i) {
+ struct vnode * rvp;
+
+ if (hfs_vget(hfsmp, cnidbufp[i], &vp, 0) != 0)
+ continue;
+
+ /* Relocate any data fork blocks. */
+ if (VTOF(vp)->ff_blocks > 0) {
+ error = hfs_relocate(vp, hfsmp->hfs_metazone_end + 1, kauth_cred_get(), current_proc());
+ }
+ if (error)
+ break;
+
+ /* Relocate any resource fork blocks. */
+ if ((VTOC((vp))->c_blocks - VTOF((vp))->ff_blocks) > 0) {
+ error = hfs_vgetrsrc(hfsmp, vp, &rvp, current_proc());
+ if (error)
+ break;
+ error = hfs_relocate(rvp, hfsmp->hfs_metazone_end + 1, kauth_cred_get(), current_proc());
+ vnode_put(rvp);
+ if (error)
+ break;
+ }
+ hfs_unlock(VTOC(vp));
+ vnode_put(vp);
+ vp = NULL;
+
+ ++hfsmp->hfs_resize_filesmoved;
+
+ /* Report intermediate progress. */
+ if (filecnt > 100) {
+ int progress;
+
+ progress = (i * 100) / filecnt;
+ if (progress > (lastprogress + 9)) {
+ printf("hfs_reclaimspace: %d%% done...\n", progress);
+ lastprogress = progress;
+ }
+ }
+ }
+ if (vp) {
+ hfs_unlock(VTOC(vp));
+ vnode_put(vp);
+ vp = NULL;
+ }
+ if (hfsmp->hfs_resize_filesmoved != 0) {
+ printf("hfs_reclaimspace: relocated %d files on \"%s\"\n",
+ (int)hfsmp->hfs_resize_filesmoved, hfsmp->vcbVN);
+ }
+out:
+ kmem_free(kernel_map, (vm_offset_t)iterator, sizeof(*iterator));
+ kmem_free(kernel_map, (vm_offset_t)cnidbufp, cnidbufsize);
+
+ /*
+ * Restore the roving allocation pointer on errors.
+ * (but only if we didn't move any files)
+ */
+ if (error && hfsmp->hfs_resize_filesmoved == 0) {
+ hfsmp->nextAllocation = saved_next_allocation;
+ }
+ return (error);
+}
+
+
+/*
+ * Check if there are any overflow extents that overlap.
+ */
+static int
+hfs_overlapped_overflow_extents(struct hfsmount *hfsmp, u_int32_t startblk, u_int32_t catblks, u_int32_t fileID, int rsrcfork)
+{
+ struct BTreeIterator * iterator = NULL;
+ struct FSBufferDescriptor btdata;
+ HFSPlusExtentRecord extrec;
+ HFSPlusExtentKey *extkeyptr;
+ FCB *fcb;
+ u_int32_t block;
+ u_int8_t forktype;
+ int overlapped = 0;
+ int i;
+ int error;
+
+ forktype = rsrcfork ? 0xFF : 0;
+ if (kmem_alloc(kernel_map, (vm_offset_t *)&iterator, sizeof(*iterator))) {
+ return (0);
+ }
+ bzero(iterator, sizeof(*iterator));
+ extkeyptr = (HFSPlusExtentKey *)&iterator->key;
+ extkeyptr->keyLength = kHFSPlusExtentKeyMaximumLength;
+ extkeyptr->forkType = forktype;
+ extkeyptr->fileID = fileID;
+ extkeyptr->startBlock = catblks;
+
+ btdata.bufferAddress = &extrec;
+ btdata.itemSize = sizeof(extrec);
+ btdata.itemCount = 1;
+
+ fcb = VTOF(hfsmp->hfs_extents_vp);
+
+ error = BTSearchRecord(fcb, iterator, &btdata, NULL, iterator);
+ while (error == 0) {
+ /* Stop when we encounter a different file. */
+ if ((extkeyptr->fileID != fileID) ||
+ (extkeyptr->forkType != forktype)) {
+ break;
+ }
+ /*
+ * Check if the file overlaps target space.
+ */
+ for (i = 0; i < kHFSPlusExtentDensity; ++i) {
+ if (extrec[i].blockCount == 0) {
+ break;
+ }
+ block = extrec[i].startBlock + extrec[i].blockCount;
+ if (block >= startblk) {
+ overlapped = 1;
+ break;
+ }
+ }
+ /* Look for more records. */
+ error = BTIterateRecord(fcb, kBTreeNextRecord, iterator, &btdata, NULL);
+ }
+
+ kmem_free(kernel_map, (vm_offset_t)iterator, sizeof(*iterator));
+ return (overlapped);
+}
+
+
+/*
+ * Calculate the progress of a file system resize operation.
+ */
+__private_extern__
+int
+hfs_resize_progress(struct hfsmount *hfsmp, u_int32_t *progress)
+{
+ if ((hfsmp->hfs_flags & HFS_RESIZE_IN_PROGRESS) == 0) {
+ return (ENXIO);
+ }
+
+ if (hfsmp->hfs_resize_totalfiles > 0)
+ *progress = (hfsmp->hfs_resize_filesmoved * 100) / hfsmp->hfs_resize_totalfiles;
+ else
+ *progress = 0;
+
+ return (0);
+}
+
+
+/*
+ * Get file system attributes.
+ */
+static int
+hfs_vfs_getattr(struct mount *mp, struct vfs_attr *fsap, __unused vfs_context_t context)
+{
+ ExtendedVCB *vcb = VFSTOVCB(mp);
+ struct hfsmount *hfsmp = VFSTOHFS(mp);
+ u_long freeCNIDs;
+
+ freeCNIDs = (u_long)0xFFFFFFFF - (u_long)hfsmp->vcbNxtCNID;
+
+ VFSATTR_RETURN(fsap, f_objcount, (uint64_t)hfsmp->vcbFilCnt + (uint64_t)hfsmp->vcbDirCnt);
+ VFSATTR_RETURN(fsap, f_filecount, (uint64_t)hfsmp->vcbFilCnt);
+ VFSATTR_RETURN(fsap, f_dircount, (uint64_t)hfsmp->vcbDirCnt);
+ VFSATTR_RETURN(fsap, f_maxobjcount, (uint64_t)0xFFFFFFFF);
+ VFSATTR_RETURN(fsap, f_iosize, (size_t)(MAX_UPL_TRANSFER * PAGE_SIZE));
+ VFSATTR_RETURN(fsap, f_blocks, (uint64_t)hfsmp->totalBlocks);
+ VFSATTR_RETURN(fsap, f_bfree, (uint64_t)hfs_freeblks(hfsmp, 0));
+ VFSATTR_RETURN(fsap, f_bavail, (uint64_t)hfs_freeblks(hfsmp, 1));
+ VFSATTR_RETURN(fsap, f_bsize, (uint32_t)vcb->blockSize);
+ /* XXX needs clarification */
+ VFSATTR_RETURN(fsap, f_bused, hfsmp->totalBlocks - hfs_freeblks(hfsmp, 1));
+ /* Maximum files is constrained by total blocks. */
+ VFSATTR_RETURN(fsap, f_files, (uint64_t)(hfsmp->totalBlocks - 2));
+ VFSATTR_RETURN(fsap, f_ffree, MIN((uint64_t)freeCNIDs, (uint64_t)hfs_freeblks(hfsmp, 1)));
+
+ fsap->f_fsid.val[0] = hfsmp->hfs_raw_dev;
+ fsap->f_fsid.val[1] = vfs_typenum(mp);
+ VFSATTR_SET_SUPPORTED(fsap, f_fsid);
+
+ VFSATTR_RETURN(fsap, f_signature, vcb->vcbSigWord);
+ VFSATTR_RETURN(fsap, f_carbon_fsid, 0);
+
+ if (VFSATTR_IS_ACTIVE(fsap, f_capabilities)) {
+ vol_capabilities_attr_t *cap;
+
+ cap = &fsap->f_capabilities;
+
+ if (hfsmp->hfs_flags & HFS_STANDARD) {
+ cap->capabilities[VOL_CAPABILITIES_FORMAT] =
+ VOL_CAP_FMT_PERSISTENTOBJECTIDS |
+ VOL_CAP_FMT_CASE_PRESERVING |
+ VOL_CAP_FMT_FAST_STATFS;
+ } else {
+ cap->capabilities[VOL_CAPABILITIES_FORMAT] =
+ VOL_CAP_FMT_PERSISTENTOBJECTIDS |
+ VOL_CAP_FMT_SYMBOLICLINKS |
+ VOL_CAP_FMT_HARDLINKS |
+ VOL_CAP_FMT_JOURNAL |
+ (hfsmp->jnl ? VOL_CAP_FMT_JOURNAL_ACTIVE : 0) |
+ (hfsmp->hfs_flags & HFS_CASE_SENSITIVE ? VOL_CAP_FMT_CASE_SENSITIVE : 0) |
+ VOL_CAP_FMT_CASE_PRESERVING |
+ VOL_CAP_FMT_FAST_STATFS |
+ VOL_CAP_FMT_2TB_FILESIZE;
+ }
+ cap->capabilities[VOL_CAPABILITIES_INTERFACES] =
+ VOL_CAP_INT_SEARCHFS |
+ VOL_CAP_INT_ATTRLIST |
+ VOL_CAP_INT_NFSEXPORT |
+ VOL_CAP_INT_READDIRATTR |
+ VOL_CAP_INT_EXCHANGEDATA |
+ VOL_CAP_INT_ALLOCATE |
+ VOL_CAP_INT_VOL_RENAME |
+ VOL_CAP_INT_ADVLOCK |
+ VOL_CAP_INT_FLOCK;
+ cap->capabilities[VOL_CAPABILITIES_RESERVED1] = 0;
+ cap->capabilities[VOL_CAPABILITIES_RESERVED2] = 0;
+
+ cap->valid[VOL_CAPABILITIES_FORMAT] =
+ VOL_CAP_FMT_PERSISTENTOBJECTIDS |
+ VOL_CAP_FMT_SYMBOLICLINKS |
+ VOL_CAP_FMT_HARDLINKS |
+ VOL_CAP_FMT_JOURNAL |
+ VOL_CAP_FMT_JOURNAL_ACTIVE |
+ VOL_CAP_FMT_NO_ROOT_TIMES |
+ VOL_CAP_FMT_SPARSE_FILES |
+ VOL_CAP_FMT_ZERO_RUNS |
+ VOL_CAP_FMT_CASE_SENSITIVE |
+ VOL_CAP_FMT_CASE_PRESERVING |
+ VOL_CAP_FMT_FAST_STATFS |
+ VOL_CAP_FMT_2TB_FILESIZE;
+ cap->valid[VOL_CAPABILITIES_INTERFACES] =
+ VOL_CAP_INT_SEARCHFS |
+ VOL_CAP_INT_ATTRLIST |
+ VOL_CAP_INT_NFSEXPORT |
+ VOL_CAP_INT_READDIRATTR |
+ VOL_CAP_INT_EXCHANGEDATA |
+ VOL_CAP_INT_COPYFILE |
+ VOL_CAP_INT_ALLOCATE |
+ VOL_CAP_INT_VOL_RENAME |
+ VOL_CAP_INT_ADVLOCK |
+ VOL_CAP_INT_FLOCK;
+ cap->valid[VOL_CAPABILITIES_RESERVED1] = 0;
+ cap->valid[VOL_CAPABILITIES_RESERVED2] = 0;
+ VFSATTR_SET_SUPPORTED(fsap, f_capabilities);
+ }
+ if (VFSATTR_IS_ACTIVE(fsap, f_attributes)) {
+ vol_attributes_attr_t *attrp = &fsap->f_attributes;
+
+ attrp->validattr.commonattr = ATTR_CMN_VALIDMASK;
+ attrp->validattr.volattr = ATTR_VOL_VALIDMASK & ~ATTR_VOL_INFO;
+ attrp->validattr.dirattr = ATTR_DIR_VALIDMASK;
+ attrp->validattr.fileattr = ATTR_FILE_VALIDMASK;
+ attrp->validattr.forkattr = 0;
+
+ attrp->nativeattr.commonattr = ATTR_CMN_VALIDMASK;
+ attrp->nativeattr.volattr = ATTR_VOL_VALIDMASK & ~ATTR_VOL_INFO;
+ attrp->nativeattr.dirattr = ATTR_DIR_VALIDMASK;
+ attrp->nativeattr.fileattr = ATTR_FILE_VALIDMASK;
+ attrp->nativeattr.forkattr = 0;
+ VFSATTR_SET_SUPPORTED(fsap, f_attributes);
+ }
+ fsap->f_create_time.tv_sec = hfsmp->vcbCrDate;
+ fsap->f_create_time.tv_nsec = 0;
+ VFSATTR_SET_SUPPORTED(fsap, f_create_time);
+ fsap->f_modify_time.tv_sec = hfsmp->vcbLsMod;
+ fsap->f_modify_time.tv_nsec = 0;
+ VFSATTR_SET_SUPPORTED(fsap, f_modify_time);
+
+ fsap->f_backup_time.tv_sec = hfsmp->vcbVolBkUp;
+ fsap->f_backup_time.tv_nsec = 0;
+ VFSATTR_SET_SUPPORTED(fsap, f_backup_time);
+ if (VFSATTR_IS_ACTIVE(fsap, f_fssubtype)) {
+ uint16_t subtype = 0;
+
+ /*
+ * Subtypes (flavors) for HFS
+ * 0: Mac OS Extended
+ * 1: Mac OS Extended (Journaled)
+ * 2: Mac OS Extended (Case Sensitive)
+ * 3: Mac OS Extended (Case Sensitive, Journaled)
+ * 4 - 127: Reserved
+ * 128: Mac OS Standard
+ *
+ */
+ if (hfsmp->hfs_flags & HFS_STANDARD) {
+ subtype = HFS_SUBTYPE_STANDARDHFS;
+ } else /* HFS Plus */ {
+ if (hfsmp->jnl)
+ subtype |= HFS_SUBTYPE_JOURNALED;
+ if (hfsmp->hfs_flags & HFS_CASE_SENSITIVE)
+ subtype |= HFS_SUBTYPE_CASESENSITIVE;
+ }
+ fsap->f_fssubtype = subtype;
+ VFSATTR_SET_SUPPORTED(fsap, f_fssubtype);
+ }
+
+ if (VFSATTR_IS_ACTIVE(fsap, f_vol_name)) {
+ strncpy(fsap->f_vol_name, hfsmp->vcbVN, MAXPATHLEN);
+ fsap->f_vol_name[MAXPATHLEN - 1] = 0;
+ VFSATTR_SET_SUPPORTED(fsap, f_vol_name);
+ }
+ return (0);
+}
+
+/*
+ * Perform a volume rename. Requires the FS' root vp.
+ */
+static int
+hfs_rename_volume(struct vnode *vp, const char *name, proc_t p)
+{
+ ExtendedVCB *vcb = VTOVCB(vp);
+ struct cnode *cp = VTOC(vp);
+ struct hfsmount *hfsmp = VTOHFS(vp);
+ struct cat_desc to_desc;
+ struct cat_desc todir_desc;
+ struct cat_desc new_desc;
+ cat_cookie_t cookie;
+ int lockflags;
+ int error = 0;
+
+ /*
+ * Ignore attempts to rename a volume to a zero-length name.
+ */
+ if (name[0] == 0)
+ return(0);
+
+ bzero(&to_desc, sizeof(to_desc));
+ bzero(&todir_desc, sizeof(todir_desc));
+ bzero(&new_desc, sizeof(new_desc));
+ bzero(&cookie, sizeof(cookie));
+
+ todir_desc.cd_parentcnid = kHFSRootParentID;
+ todir_desc.cd_cnid = kHFSRootFolderID;
+ todir_desc.cd_flags = CD_ISDIR;
+
+ to_desc.cd_nameptr = name;
+ to_desc.cd_namelen = strlen(name);
+ to_desc.cd_parentcnid = kHFSRootParentID;
+ to_desc.cd_cnid = cp->c_cnid;
+ to_desc.cd_flags = CD_ISDIR;
+
+ if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK)) == 0) {
+ if ((error = hfs_start_transaction(hfsmp)) == 0) {
+ if ((error = cat_preflight(hfsmp, CAT_RENAME, &cookie, p)) == 0) {
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
+
+ error = cat_rename(hfsmp, &cp->c_desc, &todir_desc, &to_desc, &new_desc);
+
+ /*
+ * If successful, update the name in the VCB, ensure it's terminated.
+ */
+ if (!error) {
+ strncpy(vcb->vcbVN, name, sizeof(vcb->vcbVN));
+ vcb->vcbVN[sizeof(vcb->vcbVN) - 1] = 0;
+ }
+
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ cat_postflight(hfsmp, &cookie, p);
+
+ if (error)
+ vcb->vcbFlags |= 0xFF00;
+ (void) hfs_flushvolumeheader(hfsmp, MNT_WAIT, 0);
+ }
+ hfs_end_transaction(hfsmp);
+ }
+ if (!error) {
+ /* Release old allocated name buffer */
+ if (cp->c_desc.cd_flags & CD_HASBUF) {
+ char *name = cp->c_desc.cd_nameptr;
+
+ cp->c_desc.cd_nameptr = 0;
+ cp->c_desc.cd_namelen = 0;
+ cp->c_desc.cd_flags &= ~CD_HASBUF;
+ vfs_removename(name);
+ }
+ /* Update cnode's catalog descriptor */
+ replace_desc(cp, &new_desc);
+ vcb->volumeNameEncodingHint = new_desc.cd_encoding;
+ cp->c_touch_chgtime = TRUE;
+ }
+
+ hfs_unlock(cp);
+ }
+
+ return(error);
+}
+
+/*
+ * Get file system attributes.
+ */
+static int
+hfs_vfs_setattr(struct mount *mp, struct vfs_attr *fsap, __unused vfs_context_t context)
+{
+ kauth_cred_t cred = vfs_context_ucred(context);
+ int error = 0;
+
+ /*
+ * Must be superuser or owner of filesystem to change volume attributes
+ */
+ if (!kauth_cred_issuser(cred) && (kauth_cred_getuid(cred) != vfs_statfs(mp)->f_owner))
+ return(EACCES);
+
+ if (VFSATTR_IS_ACTIVE(fsap, f_vol_name)) {
+ vnode_t root_vp;
+
+ error = hfs_vfs_root(mp, &root_vp, context);
+ if (error)
+ goto out;
+
+ error = hfs_rename_volume(root_vp, fsap->f_vol_name, vfs_context_proc(context));
+ (void) vnode_put(root_vp);
+ if (error)
+ goto out;
+
+ VFSATTR_SET_SUPPORTED(fsap, f_vol_name);
+ }
+
+out:
+ return error;
+}
+