/*
- * Copyright (c) 2013-2014 Apple Inc. All rights reserved.
+ * Copyright (c) 2013-2015 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
#include <sys/ubc.h>
#include <sys/vnode_internal.h>
#include <sys/mount_internal.h>
+
#include <sys/buf_internal.h>
#include <vfs/vfs_journal.h>
#include <miscfs/specfs/specdev.h>
#include "hfs_cnode.h"
#include "hfs_endian.h"
#include "hfs_btreeio.h"
-
-#if CONFIG_PROTECT
-#include <sys/cprotect.h>
-#endif
+#include "hfs_cprotect.h"
/* Enable/disable debugging code for live volume resizing */
int hfs_resize_debug = 0;
-static int hfs_file_extent_overlaps(struct hfsmount *hfsmp, u_int32_t allocLimit, struct HFSPlusCatalogFile *filerec);
+static errno_t hfs_file_extent_overlaps(struct hfsmount *hfsmp, u_int32_t allocLimit,
+ struct HFSPlusCatalogFile *filerec, bool *overlaps);
static int hfs_reclaimspace(struct hfsmount *hfsmp, u_int32_t allocLimit, u_int32_t reclaimblks, vfs_context_t context);
static int hfs_extend_journal(struct hfsmount *hfsmp, u_int32_t sector_size, u_int64_t sector_count, vfs_context_t context);
}
hfsmp->hfs_flags |= HFS_RESIZE_IN_PROGRESS;
hfs_unlock_mount (hfsmp);
-
+
/* Start with a clean journal. */
- hfs_journal_flush(hfsmp, TRUE);
+ hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
/*
* Enclose changes inside a transaction.
vcb->totalBlocks += addblks;
vcb->freeBlocks += addblks;
MarkVCBDirty(vcb);
- error = hfs_flushvolumeheader(hfsmp, MNT_WAIT, HFS_ALTFLUSH);
+ error = hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT | HFS_FVH_WRITE_ALT);
if (error) {
printf("hfs_extendfs: couldn't flush volume headers (%d)", error);
/*
}
if (transaction_begun) {
hfs_end_transaction(hfsmp);
- hfs_journal_flush(hfsmp, TRUE);
+ hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
transaction_begun = 0;
}
}
if (transaction_begun) {
hfs_end_transaction(hfsmp);
- hfs_journal_flush(hfsmp, FALSE);
/* Just to be sure, sync all data to the disk */
- (void) VNOP_IOCTL(hfsmp->hfs_devvp, DKIOCSYNCHRONIZECACHE, NULL, FWRITE, context);
+ int flush_error = hfs_flush(hfsmp, HFS_FLUSH_FULL);
+ if (flush_error && !error)
+ error = flush_error;
}
if (error) {
printf ("hfs_extentfs: failed error=%d on vol=%s\n", MacToVFSError(error), hfsmp->vcbVN);
error = EINVAL;
goto out;
}
-
+
/*
* Make sure that the file system has enough free blocks reclaim.
*
error = ENOSPC;
goto out;
}
-
+
/* Start with a clean journal. */
- hfs_journal_flush(hfsmp, TRUE);
+ hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
if (hfs_start_transaction(hfsmp) != 0) {
error = EINVAL;
* an extent being relocated is more than the free blocks that
* will exist after the volume is resized.
*/
+ hfsmp->reclaimBlocks = reclaimblks;
hfsmp->freeBlocks -= reclaimblks;
updateFreeBlocks = true;
hfs_unlock_mount(hfsmp);
*/
hfs_end_transaction(hfsmp);
transaction_begun = 0;
-
+
/* Attempt to reclaim some space. */
error = hfs_reclaimspace(hfsmp, hfsmp->allocLimit, reclaimblks, context);
if (error != 0) {
error = ENOSPC;
goto out;
}
+
if (hfs_start_transaction(hfsmp) != 0) {
error = EINVAL;
goto out;
hfsmp->totalBlocks = newblkcnt;
hfsmp->hfs_logical_block_count = newsize / hfsmp->hfs_logical_block_size;
hfsmp->hfs_logical_bytes = (uint64_t) hfsmp->hfs_logical_block_count * (uint64_t) hfsmp->hfs_logical_block_size;
-
+ hfsmp->reclaimBlocks = 0;
+
/*
* At this point, a smaller HFS file system exists in a larger volume.
* As per volume format, the alternate volume header is located 1024 bytes
}
MarkVCBDirty(hfsmp);
- error = hfs_flushvolumeheader(hfsmp, MNT_WAIT, HFS_ALTFLUSH);
+ error = hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT | HFS_FVH_WRITE_ALT);
if (error) {
panic("hfs_truncatefs: unexpected error flushing volume header (%d)\n", error);
}
if (error && (updateFreeBlocks == true)) {
hfsmp->freeBlocks += reclaimblks;
}
-
+ hfsmp->reclaimBlocks = 0;
+
if (hfsmp->nextAllocation >= hfsmp->allocLimit) {
hfsmp->nextAllocation = hfsmp->hfs_metazone_end + 1;
}
}
if (transaction_begun) {
hfs_end_transaction(hfsmp);
- hfs_journal_flush(hfsmp, FALSE);
/* Just to be sure, sync all data to the disk */
- (void) VNOP_IOCTL(hfsmp->hfs_devvp, DKIOCSYNCHRONIZECACHE, NULL, FWRITE, context);
+ int flush_error = hfs_flush(hfsmp, HFS_FLUSH_FULL);
+ if (flush_error && !error)
+ error = flush_error;
}
if (error) {
u_int32_t oldStart, /* The start of the source extent. */
u_int32_t newStart, /* The start of the destination extent. */
u_int32_t blockCount, /* The number of allocation blocks to copy. */
- vfs_context_t context)
+ __unused vfs_context_t context)
{
int err = 0;
size_t bufferSize;
* a special cpentry to the IOMedia/LwVM code for handling.
*/
if (!vnode_issystem (vp) && vnode_isreg(vp) && cp_fs_protected (hfsmp->hfs_mp)) {
- int cp_err = 0;
- /*
- * Ideally, the file whose extents we are about to manipulate is using the
- * newer offset-based IVs so that we can manipulate it regardless of the
- * current lock state. However, we must maintain support for older-style
- * EAs.
- *
- * For the older EA case, the IV was tied to the device LBA for file content.
- * This means that encrypted data cannot be moved from one location to another
- * in the filesystem without garbling the IV data. As a result, we need to
- * access the file's plaintext because we cannot do our AES-symmetry trick
- * here. This requires that we attempt a key-unwrap here (via cp_handle_relocate)
- * to make forward progress. If the keys are unavailable then we will
- * simply stop the resize in its tracks here since we cannot move
- * this extent at this time.
- */
- if ((cp->c_cpentry->cp_flags & CP_OFF_IV_ENABLED) == 0) {
- cp_err = cp_handle_relocate(cp, hfsmp);
- }
-
- if (cp_err) {
- printf ("hfs_copy_extent: cp_handle_relocate failed (%d) \n", cp_err);
- return cp_err;
- }
-
cpenabled = 1;
}
#endif
-
-
+
/*
* Determine the I/O size to use
*
*/
vfs_ioattr(hfsmp->hfs_mp, &ioattr);
bufferSize = MIN(ioattr.io_maxreadcnt, ioattr.io_maxwritecnt);
- if (kmem_alloc(kernel_map, (vm_offset_t*) &buffer, bufferSize))
+ if (kmem_alloc(kernel_map, (vm_offset_t*) &buffer, bufferSize, VM_KERN_MEMORY_FILE))
return ENOMEM;
/* Get a buffer for doing the I/O */
/* Attach the new CP blob to the buffer if needed */
#if CONFIG_PROTECT
if (cpenabled) {
- if (cp->c_cpentry->cp_flags & CP_OFF_IV_ENABLED) {
- /* attach the RELOCATION_INFLIGHT flag for the underlying call to VNOP_STRATEGY */
- cp->c_cpentry->cp_flags |= CP_RELOCATION_INFLIGHT;
- buf_setcpaddr(bp, hfsmp->hfs_resize_cpentry);
- }
- else {
- /*
- * Use the cnode's cp key. This file is tied to the
- * LBAs of the physical blocks that it occupies.
- */
- buf_setcpaddr (bp, cp->c_cpentry);
- }
-
+ /* attach the RELOCATION_INFLIGHT flag for the underlying call to VNOP_STRATEGY */
+ cp->c_cpentry->cp_flags |= CP_RELOCATION_INFLIGHT;
+ bufattr_setcpx(buf_attr(bp), hfsmp->hfs_resize_cpx);
+
/* Initialize the content protection file offset to start at 0 */
buf_setcpoff (bp, 0);
}
#endif
-
+
/* Do the read */
err = VNOP_STRATEGY(bp);
if (!err)
#if CONFIG_PROTECT
/* Attach the CP to the buffer if needed */
if (cpenabled) {
- if (cp->c_cpentry->cp_flags & CP_OFF_IV_ENABLED) {
- buf_setcpaddr(bp, hfsmp->hfs_resize_cpentry);
- }
- else {
- /*
- * Use the cnode's CP key. This file is still tied
- * to the LBAs of the physical blocks that it occupies.
- */
- buf_setcpaddr (bp, cp->c_cpentry);
- }
+ bufattr_setcpx(buf_attr(bp), hfsmp->hfs_resize_cpx);
/*
* The last STRATEGY call may have updated the cp file offset behind our
* back, so we cannot trust it. Re-initialize the content protection
/* Make sure all writes have been flushed to disk. */
if (vnode_issystem(vp) && !journal_uses_fua(hfsmp->jnl)) {
- err = VNOP_IOCTL(hfsmp->hfs_devvp, DKIOCSYNCHRONIZECACHE, NULL, FWRITE, context);
+
+ err = hfs_flush(hfsmp, HFS_FLUSH_CACHE);
if (err) {
- printf("hfs_copy_extent: DKIOCSYNCHRONIZECACHE failed (%d)\n", err);
+ printf("hfs_copy_extent: hfs_flush failed (%d)\n", err);
err = 0; /* Don't fail the copy. */
}
}
cp->c_flag |= C_MODIFIED;
/* If this is a system file, sync volume headers on disk */
if (extent_info->is_sysfile) {
- error = hfs_flushvolumeheader(hfsmp, MNT_WAIT, HFS_ALTFLUSH);
+ error = hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT | HFS_FVH_WRITE_ALT);
}
}
} else {
*/
if ((extent_info->catalog_fp) &&
(extent_info->is_sysfile == false)) {
- (void) hfs_update(extent_info->vp, MNT_WAIT);
+ hfs_update(extent_info->vp, 0);
}
hfs_end_transaction(hfsmp);
/* If the current vnode is system vnode, flush journal
* to make sure that all data is written to the disk.
*/
- error = hfs_journal_flush(hfsmp, TRUE);
+ error = hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
if (error) {
printf ("hfs_reclaim_file: journal_flush returned %d\n", error);
goto out;
FREE(extent_info->dirlink_fork, M_TEMP);
}
if ((extent_info->blocks_relocated != 0) && (extent_info->is_sysfile == false)) {
- (void) hfs_update(vp, MNT_WAIT);
+ hfs_update(vp, 0);
}
if (took_truncate_lock) {
hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
JournalInfoBlock *jibp;
error = buf_meta_bread(hfsmp->hfs_devvp,
- hfsmp->vcbJinfoBlock * (hfsmp->blockSize/hfsmp->hfs_logical_block_size),
+ (uint64_t)hfsmp->vcbJinfoBlock * (hfsmp->blockSize/hfsmp->hfs_logical_block_size),
hfsmp->blockSize, vfs_context_ucred(args->context), &bp);
if (error) {
printf("hfs_journal_relocate_callback: failed to read JIB (%d)\n", error);
return error;
}
if (!journal_uses_fua(hfsmp->jnl)) {
- error = VNOP_IOCTL(hfsmp->hfs_devvp, DKIOCSYNCHRONIZECACHE, NULL, FWRITE, args->context);
+ error = hfs_flush(hfsmp, HFS_FLUSH_CACHE);
if (error) {
- printf("hfs_journal_relocate_callback: DKIOCSYNCHRONIZECACHE failed (%d)\n", error);
+ printf("hfs_journal_relocate_callback: hfs_flush failed (%d)\n", error);
error = 0; /* Don't fail the operation. */
}
}
}
/* Update the catalog record for .journal */
- journal_fork.cf_size = newBlockCount * hfsmp->blockSize;
+ journal_fork.cf_size = hfs_blk_to_bytes(newBlockCount, hfsmp->blockSize);
journal_fork.cf_extents[0].startBlock = newStartBlock;
journal_fork.cf_extents[0].blockCount = newBlockCount;
journal_fork.cf_blocks = newBlockCount;
return 0;
}
- error = hfs_relocate_journal_file(hfsmp, blockCount * hfsmp->blockSize, HFS_RESIZE_TRUNCATE, context);
+ error = hfs_relocate_journal_file(hfsmp, hfs_blk_to_bytes(blockCount, hfsmp->blockSize),
+ HFS_RESIZE_TRUNCATE, context);
if (error == 0) {
hfsmp->hfs_resize_blocksmoved += blockCount;
hfs_truncatefs_progress(hfsmp);
/* Copy the old journal info block content to the new location */
error = buf_meta_bread(hfsmp->hfs_devvp,
- hfsmp->vcbJinfoBlock * (hfsmp->blockSize/hfsmp->hfs_logical_block_size),
+ (uint64_t)hfsmp->vcbJinfoBlock * (hfsmp->blockSize/hfsmp->hfs_logical_block_size),
hfsmp->blockSize, vfs_context_ucred(context), &old_bp);
if (error) {
printf("hfs_reclaim_journal_info_block: failed to read JIB (%d)\n", error);
goto free_fail;
}
new_bp = buf_getblk(hfsmp->hfs_devvp,
- newBlock * (hfsmp->blockSize/hfsmp->hfs_logical_block_size),
+ (uint64_t)newBlock * (hfsmp->blockSize/hfsmp->hfs_logical_block_size),
hfsmp->blockSize, 0, 0, BLK_META);
bcopy((char*)buf_dataptr(old_bp), (char*)buf_dataptr(new_bp), hfsmp->blockSize);
buf_brelse(old_bp);
goto free_fail;
}
if (!journal_uses_fua(hfsmp->jnl)) {
- error = VNOP_IOCTL(hfsmp->hfs_devvp, DKIOCSYNCHRONIZECACHE, NULL, FWRITE, context);
+ error = hfs_flush(hfsmp, HFS_FLUSH_CACHE);
if (error) {
- printf("hfs_reclaim_journal_info_block: DKIOCSYNCHRONIZECACHE failed (%d)\n", error);
+ printf("hfs_reclaim_journal_info_block: hfs_flush failed (%d)\n", error);
/* Don't fail the operation. */
}
}
/* Update the pointer to the journal info block in the volume header. */
hfsmp->vcbJinfoBlock = newBlock;
- error = hfs_flushvolumeheader(hfsmp, MNT_WAIT, HFS_ALTFLUSH);
+ error = hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT | HFS_FVH_WRITE_ALT);
if (error) {
printf("hfs_reclaim_journal_info_block: hfs_flushvolumeheader returned %d\n", error);
goto fail;
if (error) {
printf("hfs_reclaim_journal_info_block: hfs_end_transaction returned %d\n", error);
}
- error = hfs_journal_flush(hfsmp, FALSE);
+ error = hfs_flush(hfsmp, HFS_FLUSH_JOURNAL);
if (error) {
printf("hfs_reclaim_journal_info_block: journal_flush returned %d\n", error);
}
/* Store the value to print total blocks moved by this function in end */
prev_blocksmoved = hfsmp->hfs_resize_blocksmoved;
- if (kmem_alloc(kernel_map, (vm_offset_t *)&iterator, sizeof(*iterator))) {
+ if (kmem_alloc(kernel_map, (vm_offset_t *)&iterator, sizeof(*iterator), VM_KERN_MEMORY_FILE)) {
return ENOMEM;
}
bzero(iterator, sizeof(*iterator));
/* Store the value to print total blocks moved by this function at the end */
prev_blocksmoved = hfsmp->hfs_resize_blocksmoved;
- if (kmem_alloc(kernel_map, (vm_offset_t *)&iterator, sizeof(*iterator))) {
+ if (kmem_alloc(kernel_map, (vm_offset_t *)&iterator, sizeof(*iterator), VM_KERN_MEMORY_FILE)) {
error = ENOMEM;
goto reclaim_filespace_done;
}
* end of the function.
*/
if (cp_fs_protected (hfsmp->hfs_mp)) {
- int needs = 0;
- error = cp_needs_tempkeys(hfsmp, &needs);
-
- if ((error == 0) && (needs)) {
- error = cp_entry_gentempkeys(&hfsmp->hfs_resize_cpentry, hfsmp);
- if (error == 0) {
- keys_generated = 1;
- }
+ error = cpx_gentempkeys(&hfsmp->hfs_resize_cpx, hfsmp);
+ if (error == 0) {
+ keys_generated = 1;
}
-
+
if (error) {
printf("hfs_reclaimspace: Error generating temporary keys for resize (%d)\n", error);
goto reclaim_filespace_done;
}
/* Check if any of the extents require relocation */
- if (hfs_file_extent_overlaps(hfsmp, allocLimit, &filerec) == false) {
+ bool overlaps;
+ error = hfs_file_extent_overlaps(hfsmp, allocLimit, &filerec, &overlaps);
+ if (error)
+ break;
+
+ if (!overlaps)
continue;
- }
-
+
/* We want to allow open-unlinked files to be moved, so allow_deleted == 1 */
if (hfs_vget(hfsmp, filerec.fileID, &vp, 0, 1) != 0) {
if (hfs_resize_debug) {
#if CONFIG_PROTECT
if (keys_generated) {
- cp_entry_destroy(hfsmp->hfs_resize_cpentry);
- hfsmp->hfs_resize_cpentry = NULL;
+ cpx_free(hfsmp->hfs_resize_cpx);
+ hfsmp->hfs_resize_cpx = NULL;
}
#endif
return error;
}
/* Just to be safe, sync the content of the journal to the disk before we proceed */
- hfs_journal_flush(hfsmp, TRUE);
+ hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
/* First, relocate journal file blocks if they're in the way.
* Doing this first will make sure that journal relocate code
* strictly required, but shouldn't hurt.
*/
if (hfsmp->hfs_resize_blocksmoved) {
- hfs_journal_flush(hfsmp, TRUE);
+ hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
}
/* Reclaim extents from catalog file records */
printf ("hfs_reclaimspace: hfs_reclaim_xattrspace returned error=%d\n", error);
return error;
}
-
+
+ /*
+ * Make sure reserved ranges in the region we're to allocate don't
+ * overlap.
+ */
+ struct rl_entry *range;
+again:;
+ int lockf = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_SHARED_LOCK);
+ TAILQ_FOREACH(range, &hfsmp->hfs_reserved_ranges[HFS_LOCKED_BLOCKS], rl_link) {
+ if (rl_overlap(range, hfsmp->allocLimit, RL_INFINITY) != RL_NOOVERLAP) {
+ // Wait 100ms
+ hfs_systemfile_unlock(hfsmp, lockf);
+ msleep(hfs_reclaimspace, NULL, PINOD, "waiting on reserved blocks",
+ &(struct timespec){ 0, 100 * 1000000 });
+ goto again;
+ }
+ }
+ hfs_systemfile_unlock(hfsmp, lockf);
+
return error;
}
* true - One of the extents need to be relocated
* false - No overflow extents need to be relocated, or there was an error
*/
-static int
-hfs_file_extent_overlaps(struct hfsmount *hfsmp, u_int32_t allocLimit, struct HFSPlusCatalogFile *filerec)
+static errno_t
+hfs_file_extent_overlaps(struct hfsmount *hfsmp, u_int32_t allocLimit,
+ struct HFSPlusCatalogFile *filerec, bool *overlaps)
{
struct BTreeIterator * iterator = NULL;
struct FSBufferDescriptor btdata;
HFSPlusExtentRecord extrec;
HFSPlusExtentKey *extkeyptr;
FCB *fcb;
- int overlapped = false;
int i, j;
int error;
int lockflags = 0;
u_int32_t endblock;
-
+ errno_t ret = 0;
+
/* Check if data fork overlaps the target space */
for (i = 0; i < kHFSPlusExtentDensity; ++i) {
if (filerec->dataFork.extents[i].blockCount == 0) {
endblock = filerec->dataFork.extents[i].startBlock +
filerec->dataFork.extents[i].blockCount;
if (endblock > allocLimit) {
- overlapped = true;
+ *overlaps = true;
goto out;
}
}
endblock = filerec->resourceFork.extents[j].startBlock +
filerec->resourceFork.extents[j].blockCount;
if (endblock > allocLimit) {
- overlapped = true;
+ *overlaps = true;
goto out;
}
}
/* Return back if there are no overflow extents for this file */
if ((i < kHFSPlusExtentDensity) && (j < kHFSPlusExtentDensity)) {
+ *overlaps = false;
goto out;
}
- if (kmem_alloc(kernel_map, (vm_offset_t *)&iterator, sizeof(*iterator))) {
- return 0;
- }
+ MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
+
bzero(iterator, sizeof(*iterator));
extkeyptr = (HFSPlusExtentKey *)&iterator->key;
extkeyptr->keyLength = kHFSPlusExtentKeyMaximumLength;
*/
error = BTSearchRecord(fcb, iterator, &btdata, NULL, iterator);
if (error && (error != btNotFound)) {
+ ret = MacToVFSError(error);
goto out;
}
-
+
/* BTIterateRecord() might return error if the btree is empty, and
* therefore we return that the extent does not overflow to the caller
*/
}
endblock = extrec[i].startBlock + extrec[i].blockCount;
if (endblock > allocLimit) {
- overlapped = true;
+ *overlaps = true;
goto out;
}
}
/* Look for more records. */
error = BTIterateRecord(fcb, kBTreeNextRecord, iterator, &btdata, NULL);
}
-
+
+ if (error && error != btNotFound) {
+ ret = MacToVFSError(error);
+ goto out;
+ }
+
+ *overlaps = false;
+
out:
if (lockflags) {
hfs_systemfile_unlock(hfsmp, lockflags);
}
- if (iterator) {
- kmem_free(kernel_map, (vm_offset_t)iterator, sizeof(*iterator));
- }
- return overlapped;
+
+ FREE(iterator, M_TEMP);
+
+ return ret;
}