#include <sys/systm.h>
#include <sys/kauth.h>
#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.h"
#include "hfs_catalog.h"
#include "hfs_cnode.h"
#include "hfs_endian.h"
#include "hfs_btreeio.h"
#include "hfs_cprotect.h"
int hfs_resize_debug = 0;
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);
int
hfs_extendfs(struct hfsmount *hfsmp, u_int64_t newsize, vfs_context_t context)
{
struct proc *p = vfs_context_proc(context);
kauth_cred_t cred = vfs_context_ucred(context);
struct vnode *vp;
struct vnode *devvp;
struct buf *bp;
struct filefork *fp = NULL;
ExtendedVCB *vcb;
struct cat_fork forkdata;
u_int64_t oldsize;
u_int64_t newblkcnt;
u_int64_t prev_phys_block_count;
u_int32_t addblks;
u_int64_t sector_count;
u_int32_t sector_size;
u_int32_t phys_sector_size;
u_int32_t overage_blocks;
daddr64_t prev_fs_alt_sector;
daddr_t bitmapblks;
int lockflags = 0;
int error;
int64_t oldBitmapSize;
Boolean usedExtendFileC = false;
int transaction_begun = 0;
devvp = hfsmp->hfs_devvp;
vcb = HFSTOVCB(hfsmp);
if ((vcb->vcbSigWord == kHFSSigWord) ||
(hfsmp->jnl == NULL) ||
(vcb->hfsPlusIOPosOffset != 0)) {
return (EPERM);
}
if (suser(cred, NULL)) {
error = hfs_vget(hfsmp, kHFSRootFolderID, &vp, 0, 0);
if (error)
return (error);
error = hfs_owner_rights(hfsmp, VTOC(vp)->c_uid, cred, p, 0);
if (error == 0) {
error = hfs_write_access(vp, cred, p, false);
}
hfs_unlock(VTOC(vp));
vnode_put(vp);
if (error)
return (error);
error = vnode_authorize(devvp, NULL, KAUTH_VNODE_READ_DATA | KAUTH_VNODE_WRITE_DATA, context);
if (error)
return (error);
}
if (VNOP_IOCTL(devvp, DKIOCGETBLOCKSIZE, (caddr_t)§or_size, 0, context)) {
return (ENXIO);
}
if (sector_size != hfsmp->hfs_logical_block_size) {
return (ENXIO);
}
if (VNOP_IOCTL(devvp, DKIOCGETBLOCKCOUNT, (caddr_t)§or_count, 0, context)) {
return (ENXIO);
}
if ((sector_size * sector_count) < newsize) {
printf("hfs_extendfs: not enough space on device (vol=%s)\n", hfsmp->vcbVN);
return (ENOSPC);
}
error = VNOP_IOCTL(devvp, DKIOCGETPHYSICALBLOCKSIZE, (caddr_t)&phys_sector_size, 0, context);
if (error) {
if ((error != ENOTSUP) && (error != ENOTTY)) {
return (ENXIO);
}
phys_sector_size = sector_size;
}
oldsize = (u_int64_t)hfsmp->totalBlocks * (u_int64_t)hfsmp->blockSize;
if ((newsize <= oldsize) || (newsize % sector_size) || (newsize % phys_sector_size)) {
printf("hfs_extendfs: invalid size (newsize=%qu, oldsize=%qu)\n", newsize, oldsize);
return (EINVAL);
}
newblkcnt = newsize / vcb->blockSize;
if (newblkcnt > (u_int64_t)0xFFFFFFFF) {
printf ("hfs_extendfs: current blockSize=%u too small for newsize=%qu\n", hfsmp->blockSize, newsize);
return (EOVERFLOW);
}
addblks = newblkcnt - vcb->totalBlocks;
if (hfs_resize_debug) {
printf ("hfs_extendfs: old: size=%qu, blkcnt=%u\n", oldsize, hfsmp->totalBlocks);
printf ("hfs_extendfs: new: size=%qu, blkcnt=%u, addblks=%u\n", newsize, (u_int32_t)newblkcnt, addblks);
}
printf("hfs_extendfs: will extend \"%s\" by %d blocks\n", vcb->vcbVN, addblks);
hfs_lock_mount (hfsmp);
if (hfsmp->hfs_flags & HFS_RESIZE_IN_PROGRESS) {
hfs_unlock_mount(hfsmp);
error = EALREADY;
goto out;
}
hfsmp->hfs_flags |= HFS_RESIZE_IN_PROGRESS;
hfs_unlock_mount (hfsmp);
hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
if (hfs_start_transaction(hfsmp) != 0) {
error = EINVAL;
goto out;
}
transaction_begun = 1;
prev_phys_block_count = hfsmp->hfs_logical_block_count;
prev_fs_alt_sector = hfsmp->hfs_fs_avh_sector;
hfsmp->hfs_logical_block_count = sector_count;
hfsmp->hfs_logical_bytes = (uint64_t) sector_count * (uint64_t) sector_size;
if (hfs_resize_debug) {
printf ("hfs_extendfs: old: partition_avh_sector=%qu, fs_avh_sector=%qu\n",
hfsmp->hfs_partition_avh_sector, hfsmp->hfs_fs_avh_sector);
}
hfsmp->hfs_partition_avh_sector = (hfsmp->hfsPlusIOPosOffset / sector_size) +
HFS_ALT_SECTOR(sector_size, hfsmp->hfs_logical_block_count);
hfsmp->hfs_fs_avh_sector = (hfsmp->hfsPlusIOPosOffset / sector_size) +
HFS_ALT_SECTOR(sector_size, (newsize/hfsmp->hfs_logical_block_size));
if (hfs_resize_debug) {
printf ("hfs_extendfs: new: partition_avh_sector=%qu, fs_avh_sector=%qu\n",
hfsmp->hfs_partition_avh_sector, hfsmp->hfs_fs_avh_sector);
}
lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_EXTENTS | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
vp = vcb->allocationsRefNum;
fp = VTOF(vp);
bcopy(&fp->ff_data, &forkdata, sizeof(forkdata));
oldBitmapSize = fp->ff_size;
bitmapblks = roundup((newblkcnt+7) / 8, vcb->vcbVBMIOSize) / vcb->blockSize;
if (bitmapblks > (daddr_t)fp->ff_blocks)
bitmapblks -= fp->ff_blocks;
else
bitmapblks = 0;
overage_blocks = fp->ff_blocks * vcb->blockSize * 8;
overage_blocks = MIN (overage_blocks, newblkcnt);
overage_blocks -= vcb->totalBlocks;
BlockMarkFreeUnused(vcb, vcb->totalBlocks, overage_blocks);
if (bitmapblks > 0) {
daddr64_t blkno;
daddr_t blkcnt;
off_t bytesAdded;
blkno = (daddr64_t)fp->ff_blocks;
error = ExtendFileC(vcb, fp, bitmapblks * vcb->blockSize, 0,
kEFAllMask | kEFNoClumpMask | kEFReserveMask
| kEFMetadataMask | kEFContigMask, &bytesAdded);
if (error == 0) {
usedExtendFileC = true;
} else {
bytesAdded = 0;
error = AddFileExtent(vcb, fp, vcb->totalBlocks, bitmapblks);
if (error) {
printf("hfs_extendfs: error %d adding extents\n", error);
goto out;
}
fp->ff_blocks += bitmapblks;
VTOC(vp)->c_blocks = fp->ff_blocks;
VTOC(vp)->c_flag |= C_MODIFIED;
}
fp->ff_size += (u_int64_t)bitmapblks * (u_int64_t)vcb->blockSize;
{
bp = NULL;
blkcnt = bitmapblks;
while (blkcnt > 0) {
error = (int)buf_meta_bread(vp, blkno, vcb->blockSize, NOCRED, &bp);
if (error) {
if (bp) {
buf_brelse(bp);
}
break;
}
bzero((char *)buf_dataptr(bp), vcb->blockSize);
buf_markaged(bp);
error = (int)buf_bwrite(bp);
if (error)
break;
--blkcnt;
++blkno;
}
}
if (error) {
printf("hfs_extendfs: error %d clearing blocks\n", error);
goto out;
}
if (!usedExtendFileC) {
error = BlockMarkAllocated(vcb, vcb->totalBlocks, bitmapblks);
if (error) {
printf("hfs_extendfs: error %d setting bitmap\n", error);
goto out;
}
vcb->freeBlocks -= bitmapblks;
}
}
if (vcb->blockSize == 512)
error = BlockMarkAllocated(vcb, vcb->totalBlocks + addblks - 2, 2);
else
error = BlockMarkAllocated(vcb, vcb->totalBlocks + addblks - 1, 1);
if (error) {
printf("hfs_extendfs: error %d setting bitmap (VH)\n", error);
goto out;
}
if (vcb->blockSize == 512)
(void) BlockMarkFree(vcb, vcb->totalBlocks - 2, 2);
else
(void) BlockMarkFree(vcb, vcb->totalBlocks - 1, 1);
vcb->totalBlocks += addblks;
vcb->freeBlocks += addblks;
MarkVCBDirty(vcb);
error = hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT | HFS_FVH_WRITE_ALT);
if (error) {
printf("hfs_extendfs: couldn't flush volume headers (%d)", error);
if (usedExtendFileC) {
(void) TruncateFileC(vcb, fp, oldBitmapSize, 0, FORK_IS_RSRC(fp),
FTOC(fp)->c_fileid, false);
} else {
fp->ff_blocks -= bitmapblks;
fp->ff_size -= (u_int64_t)bitmapblks * (u_int64_t)vcb->blockSize;
vcb->freeBlocks += bitmapblks;
}
vcb->totalBlocks -= addblks;
vcb->freeBlocks -= addblks;
hfsmp->hfs_logical_block_count = prev_phys_block_count;
hfsmp->hfs_fs_avh_sector = prev_fs_alt_sector;
MarkVCBDirty(vcb);
if (vcb->blockSize == 512) {
if (BlockMarkAllocated(vcb, vcb->totalBlocks - 2, 2)) {
hfs_mark_inconsistent(hfsmp, HFS_ROLLBACK_FAILED);
}
} else {
if (BlockMarkAllocated(vcb, vcb->totalBlocks - 1, 1)) {
hfs_mark_inconsistent(hfsmp, HFS_ROLLBACK_FAILED);
}
}
goto out;
}
bp = NULL;
if (prev_fs_alt_sector) {
if (buf_meta_bread(hfsmp->hfs_devvp,
HFS_PHYSBLK_ROUNDDOWN(prev_fs_alt_sector, hfsmp->hfs_log_per_phys),
hfsmp->hfs_physical_block_size, NOCRED, &bp) == 0) {
journal_modify_block_start(hfsmp->jnl, bp);
bzero((char *)buf_dataptr(bp) + HFS_ALT_OFFSET(hfsmp->hfs_physical_block_size), kMDBSize);
journal_modify_block_end(hfsmp->jnl, bp, NULL, NULL);
} else if (bp) {
buf_brelse(bp);
}
}
hfs_metadatazone_init(hfsmp, false);
if (hfsmp->hfs_attrdata_vp) {
struct cnode *attr_cp;
struct filefork *attr_fp;
if (vnode_get(hfsmp->hfs_attrdata_vp) == 0) {
attr_cp = VTOC(hfsmp->hfs_attrdata_vp);
attr_fp = VTOF(hfsmp->hfs_attrdata_vp);
attr_cp->c_blocks = newblkcnt;
attr_fp->ff_blocks = newblkcnt;
attr_fp->ff_extents[0].blockCount = newblkcnt;
attr_fp->ff_size = (off_t) newblkcnt * hfsmp->blockSize;
ubc_setsize(hfsmp->hfs_attrdata_vp, attr_fp->ff_size);
vnode_put(hfsmp->hfs_attrdata_vp);
}
}
if (error == 0) {
UpdateAllocLimit(hfsmp, hfsmp->totalBlocks);
}
if (lockflags) {
hfs_systemfile_unlock(hfsmp, lockflags);
lockflags = 0;
}
if (transaction_begun) {
hfs_end_transaction(hfsmp);
hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
transaction_begun = 0;
}
error = hfs_extend_journal(hfsmp, sector_size, sector_count, context);
if (error) {
printf ("hfs_extendfs: Could not extend journal size\n");
goto out_noalloc;
}
printf("hfs_extendfs: extended \"%s\" to %d blocks (was %d blocks)\n",
hfsmp->vcbVN, hfsmp->totalBlocks, (u_int32_t)(oldsize/hfsmp->blockSize));
out:
if (error && fp) {
bcopy(&forkdata, &fp->ff_data, sizeof(forkdata));
VTOC(vp)->c_blocks = fp->ff_blocks;
}
out_noalloc:
hfs_lock_mount (hfsmp);
hfsmp->hfs_flags &= ~HFS_RESIZE_IN_PROGRESS;
hfs_unlock_mount (hfsmp);
if (lockflags) {
hfs_systemfile_unlock(hfsmp, lockflags);
}
if (transaction_begun) {
hfs_end_transaction(hfsmp);
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);
}
return MacToVFSError(error);
}
#define HFS_MIN_SIZE (32LL * 1024LL * 1024LL)
int
hfs_truncatefs(struct hfsmount *hfsmp, u_int64_t newsize, vfs_context_t context)
{
u_int64_t oldsize;
u_int32_t newblkcnt;
u_int32_t reclaimblks = 0;
int lockflags = 0;
int transaction_begun = 0;
Boolean updateFreeBlocks = false;
Boolean disable_sparse = false;
int error = 0;
hfs_lock_mount (hfsmp);
if (hfsmp->hfs_flags & HFS_RESIZE_IN_PROGRESS) {
hfs_unlock_mount (hfsmp);
return (EALREADY);
}
hfsmp->hfs_flags |= HFS_RESIZE_IN_PROGRESS;
hfsmp->hfs_resize_blocksmoved = 0;
hfsmp->hfs_resize_totalblocks = 0;
hfsmp->hfs_resize_progress = 0;
hfs_unlock_mount (hfsmp);
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;
if (hfs_resize_debug) {
printf ("hfs_truncatefs: old: size=%qu, blkcnt=%u, freeblks=%u\n", oldsize, hfsmp->totalBlocks, hfs_freeblks(hfsmp, 1));
printf ("hfs_truncatefs: new: size=%qu, blkcnt=%u, reclaimblks=%u\n", newsize, newblkcnt, reclaimblks);
}
if ((newsize < HFS_MIN_SIZE) ||
(newsize >= oldsize) ||
(newsize % hfsmp->hfs_logical_block_size) ||
(newsize % hfsmp->hfs_physical_block_size)) {
printf ("hfs_truncatefs: invalid size (newsize=%qu, oldsize=%qu)\n", newsize, oldsize);
error = EINVAL;
goto out;
}
if (reclaimblks >= hfs_freeblks(hfsmp, 1)) {
printf("hfs_truncatefs: insufficient space (need %u blocks; have %u free blocks)\n", reclaimblks, hfs_freeblks(hfsmp, 1));
error = ENOSPC;
goto out;
}
hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
if (hfs_start_transaction(hfsmp) != 0) {
error = EINVAL;
goto out;
}
transaction_begun = 1;
lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
if (hfsmp->blockSize == 512) {
error = UpdateAllocLimit (hfsmp, newblkcnt - 2);
}
else {
error = UpdateAllocLimit (hfsmp, newblkcnt - 1);
}
hfs_lock_mount (hfsmp);
if (hfsmp->hfs_flags & HFS_HAS_SPARSE_DEVICE) {
hfsmp->hfs_flags &= ~HFS_HAS_SPARSE_DEVICE;
ResetVCBFreeExtCache(hfsmp);
disable_sparse = true;
}
hfsmp->reclaimBlocks = reclaimblks;
hfsmp->freeBlocks -= reclaimblks;
updateFreeBlocks = true;
hfs_unlock_mount(hfsmp);
if (lockflags) {
hfs_systemfile_unlock(hfsmp, lockflags);
lockflags = 0;
}
hfs_metadatazone_init(hfsmp, false);
if (hfs_isallocated(hfsmp, hfsmp->allocLimit, reclaimblks)) {
hfs_end_transaction(hfsmp);
transaction_begun = 0;
error = hfs_reclaimspace(hfsmp, hfsmp->allocLimit, reclaimblks, context);
if (error != 0) {
printf("hfs_truncatefs: couldn't reclaim space on %s (error=%d)\n", hfsmp->vcbVN, error);
error = ENOSPC;
goto out;
}
if (hfs_start_transaction(hfsmp) != 0) {
error = EINVAL;
goto out;
}
transaction_begun = 1;
error = hfs_isallocated(hfsmp, hfsmp->allocLimit, reclaimblks);
if (error != 0) {
printf("hfs_truncatefs: didn't reclaim enough space on %s (error=%d)\n", hfsmp->vcbVN, error);
error = EAGAIN;
goto out;
}
}
lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_EXTENTS | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
error = BlockMarkAllocated(hfsmp, hfsmp->allocLimit, (hfsmp->blockSize == 512) ? 2 : 1);
if (error) {
printf("hfs_truncatefs: Error %d allocating new alternate volume header\n", error);
goto out;
}
if (hfsmp->blockSize == 512)
(void) BlockMarkFree(hfsmp, hfsmp->totalBlocks - 2, 2);
else
(void) BlockMarkFree(hfsmp, hfsmp->totalBlocks - 1, 1);
printf("hfs_truncatefs: shrank \"%s\" to %d blocks (was %d blocks)\n",
hfsmp->vcbVN, newblkcnt, hfsmp->totalBlocks);
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;
if (hfs_resize_debug) {
printf ("hfs_truncatefs: old: partition_avh_sector=%qu, fs_avh_sector=%qu\n",
hfsmp->hfs_partition_avh_sector, hfsmp->hfs_fs_avh_sector);
}
hfsmp->hfs_fs_avh_sector = HFS_ALT_SECTOR(hfsmp->hfs_logical_block_size, hfsmp->hfs_logical_block_count);
if (hfs_resize_debug) {
printf ("hfs_truncatefs: new: partition_avh_sector=%qu, fs_avh_sector=%qu\n",
hfsmp->hfs_partition_avh_sector, hfsmp->hfs_fs_avh_sector);
}
MarkVCBDirty(hfsmp);
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 (hfsmp->hfs_attrdata_vp) {
struct cnode *cp;
struct filefork *fp;
if (vnode_get(hfsmp->hfs_attrdata_vp) == 0) {
cp = VTOC(hfsmp->hfs_attrdata_vp);
fp = VTOF(hfsmp->hfs_attrdata_vp);
cp->c_blocks = newblkcnt;
fp->ff_blocks = newblkcnt;
fp->ff_extents[0].blockCount = newblkcnt;
fp->ff_size = (off_t) newblkcnt * hfsmp->blockSize;
ubc_setsize(hfsmp->hfs_attrdata_vp, fp->ff_size);
vnode_put(hfsmp->hfs_attrdata_vp);
}
}
out:
UpdateAllocLimit (hfsmp, hfsmp->totalBlocks);
hfs_lock_mount (hfsmp);
if (disable_sparse == true) {
hfsmp->hfs_flags |= HFS_HAS_SPARSE_DEVICE;
ResetVCBFreeExtCache(hfsmp);
}
if (error && (updateFreeBlocks == true)) {
hfsmp->freeBlocks += reclaimblks;
}
hfsmp->reclaimBlocks = 0;
if (hfsmp->nextAllocation >= hfsmp->allocLimit) {
hfsmp->nextAllocation = hfsmp->hfs_metazone_end + 1;
}
hfsmp->hfs_flags &= ~HFS_RESIZE_IN_PROGRESS;
hfs_unlock_mount (hfsmp);
if (error && (updateFreeBlocks == true)) {
hfs_metadatazone_init(hfsmp, false);
}
if (lockflags) {
hfs_systemfile_unlock(hfsmp, lockflags);
}
if (transaction_begun) {
hfs_end_transaction(hfsmp);
int flush_error = hfs_flush(hfsmp, HFS_FLUSH_FULL);
if (flush_error && !error)
error = flush_error;
}
if (error) {
printf ("hfs_truncatefs: failed error=%d on vol=%s\n", MacToVFSError(error), hfsmp->vcbVN);
}
return MacToVFSError(error);
}
struct hfs_inval_blk_no {
daddr64_t sectorStart;
daddr64_t sectorCount;
};
static int
hfs_invalidate_block_numbers_callback(buf_t bp, void *args_in)
{
daddr64_t blkno;
struct hfs_inval_blk_no *args;
blkno = buf_blkno(bp);
args = args_in;
if (blkno >= args->sectorStart && blkno < args->sectorStart+args->sectorCount)
buf_setblkno(bp, buf_lblkno(bp));
return BUF_RETURNED;
}
static void
hfs_invalidate_sectors(struct vnode *vp, daddr64_t sectorStart, daddr64_t sectorCount)
{
struct hfs_inval_blk_no args;
args.sectorStart = sectorStart;
args.sectorCount = sectorCount;
buf_iterate(vp, hfs_invalidate_block_numbers_callback, BUF_SCAN_DIRTY|BUF_SCAN_CLEAN, &args);
}
static int
hfs_copy_extent(
struct hfsmount *hfsmp,
struct vnode *vp,
u_int32_t oldStart,
u_int32_t newStart,
u_int32_t blockCount,
__unused vfs_context_t context)
{
int err = 0;
size_t bufferSize;
void *buffer = NULL;
struct vfsioattr ioattr;
buf_t bp = NULL;
off_t resid;
size_t ioSize;
u_int32_t ioSizeSectors;
daddr64_t srcSector, destSector;
u_int32_t sectorsPerBlock = hfsmp->blockSize / hfsmp->hfs_logical_block_size;
#if CONFIG_PROTECT
int cpenabled = 0;
#endif
struct cnode *cp = VTOC(vp);
if (cp != hfsmp->hfs_allocation_cp && cp->c_lockowner != current_thread())
panic("hfs_copy_extent: vp=%p (cp=%p) not owned?\n", vp, cp);
#if CONFIG_PROTECT
if (!vnode_issystem (vp) && vnode_isreg(vp) && cp_fs_protected (hfsmp->hfs_mp)) {
cpenabled = 1;
}
#endif
vfs_ioattr(hfsmp->hfs_mp, &ioattr);
bufferSize = MIN(ioattr.io_maxreadcnt, ioattr.io_maxwritecnt);
if (kmem_alloc(kernel_map, (vm_offset_t*) &buffer, bufferSize, VM_KERN_MEMORY_FILE))
return ENOMEM;
bp = buf_alloc(hfsmp->hfs_devvp);
buf_setdataptr(bp, (uintptr_t)buffer);
resid = (off_t) blockCount * (off_t) hfsmp->blockSize;
srcSector = (daddr64_t) oldStart * hfsmp->blockSize / hfsmp->hfs_logical_block_size;
destSector = (daddr64_t) newStart * hfsmp->blockSize / hfsmp->hfs_logical_block_size;
while (resid > 0) {
ioSize = MIN(bufferSize, (size_t) resid);
ioSizeSectors = ioSize / hfsmp->hfs_logical_block_size;
buf_reset(bp, B_READ);
buf_setsize(bp, ioSize);
buf_setcount(bp, ioSize);
buf_setblkno(bp, srcSector);
buf_setlblkno(bp, srcSector);
#if CONFIG_PROTECT
if (cpenabled) {
cp->c_cpentry->cp_flags |= CP_RELOCATION_INFLIGHT;
bufattr_setcpx(buf_attr(bp), hfsmp->hfs_resize_cpx);
buf_setcpoff (bp, 0);
}
#endif
err = VNOP_STRATEGY(bp);
if (!err)
err = buf_biowait(bp);
if (err) {
#if CONFIG_PROTECT
if (cpenabled) {
cp->c_cpentry->cp_flags &= ~CP_RELOCATION_INFLIGHT;
}
#endif
printf("hfs_copy_extent: Error %d from VNOP_STRATEGY (read)\n", err);
break;
}
buf_reset(bp, B_WRITE);
buf_setsize(bp, ioSize);
buf_setcount(bp, ioSize);
buf_setblkno(bp, destSector);
buf_setlblkno(bp, destSector);
if (vnode_issystem(vp) && journal_uses_fua(hfsmp->jnl))
buf_markfua(bp);
#if CONFIG_PROTECT
if (cpenabled) {
bufattr_setcpx(buf_attr(bp), hfsmp->hfs_resize_cpx);
buf_setcpoff (bp, 0);
}
#endif
vnode_startwrite(hfsmp->hfs_devvp);
err = VNOP_STRATEGY(bp);
if (!err) {
err = buf_biowait(bp);
}
#if CONFIG_PROTECT
if (cpenabled) {
cp->c_cpentry->cp_flags &= ~CP_RELOCATION_INFLIGHT;
}
#endif
if (err) {
printf("hfs_copy_extent: Error %d from VNOP_STRATEGY (write)\n", err);
break;
}
resid -= ioSize;
srcSector += ioSizeSectors;
destSector += ioSizeSectors;
}
if (bp)
buf_free(bp);
if (buffer)
kmem_free(kernel_map, (vm_offset_t)buffer, bufferSize);
if (vnode_issystem(vp) && !journal_uses_fua(hfsmp->jnl)) {
err = hfs_flush(hfsmp, HFS_FLUSH_CACHE);
if (err) {
printf("hfs_copy_extent: hfs_flush failed (%d)\n", err);
err = 0;
}
}
if (!err)
hfs_invalidate_sectors(vp, (daddr64_t)oldStart*sectorsPerBlock, (daddr64_t)blockCount*sectorsPerBlock);
return err;
}
struct hfs_reclaim_extent_info {
struct vnode *vp;
u_int32_t fileID;
u_int8_t forkType;
u_int8_t is_dirlink;
u_int8_t is_sysfile;
u_int8_t is_xattr;
u_int8_t extent_index;
int lockflags;
u_int32_t blocks_relocated;
u_int32_t recStartBlock;
u_int32_t cur_blockCount;
struct filefork *catalog_fp;
union record {
HFSPlusExtentRecord overflow;
HFSPlusAttrRecord xattr;
} record;
HFSPlusExtentDescriptor *extents;
struct cat_desc *dirlink_desc;
struct cat_attr *dirlink_attr;
struct filefork *dirlink_fork;
struct BTreeIterator *iterator;
struct FSBufferDescriptor btdata;
u_int16_t recordlen;
int overflow_count;
FCB *fcb;
};
static int
hfs_split_extent(struct hfs_reclaim_extent_info *extent_info, uint32_t newBlockCount)
{
int error = 0;
int index = extent_info->extent_index;
int i;
HFSPlusExtentDescriptor shift_extent;
HFSPlusExtentDescriptor last_extent;
HFSPlusExtentDescriptor *extents;
HFSPlusExtentRecord *extents_rec = NULL;
HFSPlusExtentKey *extents_key = NULL;
HFSPlusAttrRecord *xattr_rec = NULL;
HFSPlusAttrKey *xattr_key = NULL;
struct BTreeIterator iterator;
struct FSBufferDescriptor btdata;
uint16_t reclen;
uint32_t read_recStartBlock;
uint32_t write_recStartBlock;
Boolean create_record = false;
Boolean is_xattr;
struct cnode *cp;
is_xattr = extent_info->is_xattr;
extents = extent_info->extents;
cp = VTOC(extent_info->vp);
if (newBlockCount == 0) {
if (hfs_resize_debug) {
printf ("hfs_split_extent: No splitting required for newBlockCount=0\n");
}
return error;
}
if (hfs_resize_debug) {
printf ("hfs_split_extent: Split record:%u recStartBlock=%u %u:(%u,%u) for %u blocks\n", extent_info->overflow_count, extent_info->recStartBlock, index, extents[index].startBlock, extents[index].blockCount, newBlockCount);
}
if ((extent_info->fileID == kHFSExtentsFileID) && (extents[kHFSPlusExtentDensity - 1].blockCount != 0)) {
printf ("hfs_split_extent: Maximum 8 extents allowed for extents overflow btree, cannot split further.\n");
error = ENOSPC;
goto out;
}
read_recStartBlock = extent_info->recStartBlock;
for (i = 0; i < kHFSPlusExtentDensity; i++) {
if (extents[i].blockCount == 0) {
break;
}
read_recStartBlock += extents[i].blockCount;
}
if (index == kHFSPlusExtentDensity-1) {
shift_extent.startBlock = extents[index].startBlock + newBlockCount;
shift_extent.blockCount = extents[index].blockCount - newBlockCount;
} else {
shift_extent = extents[kHFSPlusExtentDensity-1];
if ((hfs_resize_debug) && (shift_extent.blockCount != 0)) {
printf ("hfs_split_extent: Save 7:(%u,%u) to shift into overflow record\n", shift_extent.startBlock, shift_extent.blockCount);
}
for (i = kHFSPlusExtentDensity-2; i > index; i--) {
if (hfs_resize_debug) {
if (extents[i].blockCount) {
printf ("hfs_split_extent: Shift %u:(%u,%u) to %u:(%u,%u)\n", i, extents[i].startBlock, extents[i].blockCount, i+1, extents[i].startBlock, extents[i].blockCount);
}
}
extents[i+1] = extents[i];
}
}
if (index == kHFSPlusExtentDensity-1) {
} else {
extents[index+1].startBlock = extents[index].startBlock + newBlockCount;
extents[index+1].blockCount = extents[index].blockCount - newBlockCount;
}
extents[index].blockCount = newBlockCount;
if (hfs_resize_debug) {
printf ("hfs_split_extent: Split %u:(%u,%u) and ", index, extents[index].startBlock, extents[index].blockCount);
if (index != kHFSPlusExtentDensity-1) {
printf ("%u:(%u,%u)\n", index+1, extents[index+1].startBlock, extents[index+1].blockCount);
} else {
printf ("overflow:(%u,%u)\n", shift_extent.startBlock, shift_extent.blockCount);
}
}
if (extent_info->catalog_fp) {
cp->c_flag |= C_MODIFIED;
} else {
error = BTReplaceRecord(extent_info->fcb, extent_info->iterator,
&(extent_info->btdata), extent_info->recordlen);
if (error) {
printf ("hfs_split_extent: fileID=%u BTReplaceRecord returned error=%d\n", extent_info->fileID, error);
goto out;
}
}
if (shift_extent.blockCount == 0) {
if (hfs_resize_debug) {
printf ("hfs_split_extent: No extent entry to be shifted into overflow records\n");
}
error = 0;
goto out;
}
bzero(&iterator, sizeof(iterator));
if (is_xattr) {
xattr_key = (HFSPlusAttrKey *)&(iterator.key);
bcopy((HFSPlusAttrKey *)&(extent_info->iterator->key), xattr_key, sizeof(HFSPlusAttrKey));
MALLOC(xattr_rec, HFSPlusAttrRecord *,
sizeof(HFSPlusAttrRecord), M_TEMP, M_WAITOK);
if (xattr_rec == NULL) {
error = ENOMEM;
goto out;
}
btdata.bufferAddress = xattr_rec;
btdata.itemSize = sizeof(HFSPlusAttrRecord);
btdata.itemCount = 1;
extents = xattr_rec->overflowExtents.extents;
} else {
extents_key = (HFSPlusExtentKey *) &(iterator.key);
extents_key->keyLength = kHFSPlusExtentKeyMaximumLength;
extents_key->forkType = extent_info->forkType;
extents_key->fileID = extent_info->fileID;
MALLOC(extents_rec, HFSPlusExtentRecord *,
sizeof(HFSPlusExtentRecord), M_TEMP, M_WAITOK);
if (extents_rec == NULL) {
error = ENOMEM;
goto out;
}
btdata.bufferAddress = extents_rec;
btdata.itemSize = sizeof(HFSPlusExtentRecord);
btdata.itemCount = 1;
extents = extents_rec[0];
}
while (shift_extent.blockCount) {
if (hfs_resize_debug) {
printf ("hfs_split_extent: Will shift (%u,%u) into overflow record with startBlock=%u\n", shift_extent.startBlock, shift_extent.blockCount, read_recStartBlock);
}
if (is_xattr) {
xattr_key->startBlock = read_recStartBlock;
} else {
extents_key->startBlock = read_recStartBlock;
}
error = BTSearchRecord(extent_info->fcb, &iterator, &btdata, &reclen, &iterator);
if (error) {
if (error != btNotFound) {
printf ("hfs_split_extent: fileID=%u startBlock=%u BTSearchRecord error=%d\n", extent_info->fileID, read_recStartBlock, error);
goto out;
}
create_record = true;
}
write_recStartBlock = read_recStartBlock - shift_extent.blockCount;
if (hfs_resize_debug) {
if (create_record) {
printf ("hfs_split_extent: No records found for startBlock=%u, will create new with startBlock=%u\n", read_recStartBlock, write_recStartBlock);
}
}
for (i = 0; i < kHFSPlusExtentDensity; i++) {
if (extents[i].blockCount == 0) {
break;
}
read_recStartBlock += extents[i].blockCount;
}
if (create_record == true) {
bzero(extents, sizeof(HFSPlusExtentRecord));
extents[0] = shift_extent;
shift_extent.startBlock = shift_extent.blockCount = 0;
if (is_xattr) {
xattr_rec->recordType = kHFSPlusAttrExtents;
xattr_rec->overflowExtents.reserved = 0;
reclen = sizeof(HFSPlusAttrExtents);
} else {
extents_key->keyLength = kHFSPlusExtentKeyMaximumLength;
extents_key->forkType = extent_info->forkType;
extents_key->fileID = extent_info->fileID;
reclen = sizeof(HFSPlusExtentRecord);
}
} else {
last_extent = extents[kHFSPlusExtentDensity-1];
for (i = kHFSPlusExtentDensity-2; i >= 0; i--) {
extents[i+1] = extents[i];
}
extents[0] = shift_extent;
if (hfs_resize_debug) {
printf ("hfs_split_extent: Shift overflow=(%u,%u) to record with updated startBlock=%u\n", shift_extent.startBlock, shift_extent.blockCount, write_recStartBlock);
}
shift_extent = last_extent;
error = BTDeleteRecord(extent_info->fcb, &iterator);
if (error) {
printf ("hfs_split_extent: fileID=%u startBlock=%u BTDeleteRecord error=%d\n", extent_info->fileID, read_recStartBlock, error);
goto out;
}
if (hfs_resize_debug) {
printf ("hfs_split_extent: Deleted extent record with startBlock=%u\n", (is_xattr ? xattr_key->startBlock : extents_key->startBlock));
}
}
bzero(&iterator.hint, sizeof(iterator.hint));
if (is_xattr) {
xattr_key->startBlock = write_recStartBlock;
} else {
extents_key->startBlock = write_recStartBlock;
}
error = BTInsertRecord(extent_info->fcb, &iterator, &btdata, reclen);
if (error) {
printf ("hfs_split_extent: fileID=%u, startBlock=%u BTInsertRecord error=%d\n", extent_info->fileID, write_recStartBlock, error);
goto out;
}
if (hfs_resize_debug) {
printf ("hfs_split_extent: Inserted extent record with startBlock=%u\n", write_recStartBlock);
}
}
out:
BTFlushPath(extent_info->fcb);
if (extents_rec) {
FREE (extents_rec, M_TEMP);
}
if (xattr_rec) {
FREE (xattr_rec, M_TEMP);
}
return error;
}
static int
hfs_reclaim_extent(struct hfsmount *hfsmp, const u_long allocLimit, struct hfs_reclaim_extent_info *extent_info, vfs_context_t context)
{
int error = 0;
int index;
struct cnode *cp;
u_int32_t oldStartBlock;
u_int32_t oldBlockCount;
u_int32_t newStartBlock;
u_int32_t newBlockCount;
u_int32_t roundedBlockCount;
uint16_t node_size;
uint32_t remainder_blocks;
u_int32_t alloc_flags;
int blocks_allocated = false;
index = extent_info->extent_index;
cp = VTOC(extent_info->vp);
oldStartBlock = extent_info->extents[index].startBlock;
oldBlockCount = extent_info->extents[index].blockCount;
if (0 && hfs_resize_debug) {
printf ("hfs_reclaim_extent: Examine record:%u recStartBlock=%u, %u:(%u,%u)\n", extent_info->overflow_count, extent_info->recStartBlock, index, oldStartBlock, oldBlockCount);
}
if ((oldStartBlock + oldBlockCount) <= allocLimit) {
extent_info->cur_blockCount += oldBlockCount;
return error;
}
error = hfs_start_transaction(hfsmp);
if (error) {
return error;
}
extent_info->lockflags = hfs_systemfile_lock(hfsmp, extent_info->lockflags, HFS_EXCLUSIVE_LOCK);
if (oldStartBlock < allocLimit) {
newBlockCount = allocLimit - oldStartBlock;
if (hfs_resize_debug) {
int idx = extent_info->extent_index;
printf ("hfs_reclaim_extent: Split straddling extent %u:(%u,%u) for %u blocks\n", idx, extent_info->extents[idx].startBlock, extent_info->extents[idx].blockCount, newBlockCount);
}
if (extent_info->is_sysfile) {
node_size = get_btree_nodesize(extent_info->vp);
if (node_size > hfsmp->blockSize) {
remainder_blocks = newBlockCount % (node_size / hfsmp->blockSize);
if (remainder_blocks) {
newBlockCount -= remainder_blocks;
if (hfs_resize_debug) {
printf ("hfs_reclaim_extent: Round-down newBlockCount to be multiple of nodeSize, node_allocblks=%u, old=%u, new=%u\n", node_size/hfsmp->blockSize, newBlockCount + remainder_blocks, newBlockCount);
}
}
}
if (newBlockCount == 0) {
if (hfs_resize_debug) {
printf ("hfs_reclaim_extent: After round-down newBlockCount=0, skip split, relocate full extent\n");
}
goto relocate_full_extent;
}
}
error = hfs_split_extent(extent_info, newBlockCount);
if (error == 0) {
goto out;
}
if (hfs_resize_debug) {
int idx = extent_info->extent_index;
printf ("hfs_reclaim_extent: Split straddling extent %u:(%u,%u) for %u blocks failed, relocate full extent\n", idx, extent_info->extents[idx].startBlock, extent_info->extents[idx].blockCount, newBlockCount);
}
}
relocate_full_extent:
alloc_flags = HFS_ALLOC_FORCECONTIG | HFS_ALLOC_SKIPFREEBLKS;
if (extent_info->is_sysfile) {
alloc_flags |= HFS_ALLOC_METAZONE;
}
error = BlockAllocate(hfsmp, 1, oldBlockCount, oldBlockCount, alloc_flags,
&newStartBlock, &newBlockCount);
if ((extent_info->is_sysfile == false) &&
((error == dskFulErr) || (error == ENOSPC))) {
alloc_flags |= HFS_ALLOC_METAZONE;
error = BlockAllocate(hfsmp, 1, oldBlockCount, oldBlockCount,
alloc_flags, &newStartBlock, &newBlockCount);
}
if ((error == dskFulErr) || (error == ENOSPC)) {
alloc_flags &= ~HFS_ALLOC_FORCECONTIG;
alloc_flags |= HFS_ALLOC_FLUSHTXN;
error = BlockAllocate(hfsmp, 1, oldBlockCount, oldBlockCount,
alloc_flags, &newStartBlock, &newBlockCount);
if (error) {
printf ("hfs_reclaim_extent: fileID=%u start=%u, %u:(%u,%u) BlockAllocate error=%d\n", extent_info->fileID, extent_info->recStartBlock, index, oldStartBlock, oldBlockCount, error);
goto out;
}
if (newBlockCount != oldBlockCount) {
blocks_allocated = true;
if (extent_info->is_sysfile) {
node_size = get_btree_nodesize(extent_info->vp);
if (node_size > hfsmp->blockSize) {
remainder_blocks = newBlockCount % (node_size / hfsmp->blockSize);
if (remainder_blocks) {
roundedBlockCount = newBlockCount - remainder_blocks;
BlockDeallocate(hfsmp, newStartBlock + roundedBlockCount,
newBlockCount - roundedBlockCount,
HFS_ALLOC_SKIPFREEBLKS);
newBlockCount = roundedBlockCount;
if (hfs_resize_debug) {
printf ("hfs_reclaim_extent: Fixing extent block count, node_blks=%u, old=%u, new=%u\n", node_size/hfsmp->blockSize, newBlockCount + remainder_blocks, newBlockCount);
}
if (newBlockCount == 0) {
printf ("hfs_reclaim_extent: Not enough contiguous blocks available to relocate fileID=%d\n", extent_info->fileID);
error = ENOSPC;
goto out;
}
}
}
}
error = hfs_split_extent(extent_info, newBlockCount);
if (error) {
printf ("hfs_reclaim_extent: fileID=%u start=%u, %u:(%u,%u) split error=%d\n", extent_info->fileID, extent_info->recStartBlock, index, oldStartBlock, oldBlockCount, error);
goto out;
}
oldBlockCount = newBlockCount;
}
}
if (error) {
printf ("hfs_reclaim_extent: fileID=%u start=%u, %u:(%u,%u) contig BlockAllocate error=%d\n", extent_info->fileID, extent_info->recStartBlock, index, oldStartBlock, oldBlockCount, error);
goto out;
}
blocks_allocated = true;
error = hfs_copy_extent(hfsmp, extent_info->vp, oldStartBlock,
newStartBlock, newBlockCount, context);
if (error) {
printf ("hfs_reclaim_extent: fileID=%u start=%u, %u:(%u,%u)=>(%u,%u) hfs_copy_extent error=%d\n", extent_info->fileID, extent_info->recStartBlock, index, oldStartBlock, oldBlockCount, newStartBlock, newBlockCount, error);
goto out;
}
extent_info->extents[index].startBlock = newStartBlock;
if (extent_info->catalog_fp) {
if (extent_info->is_dirlink) {
error = cat_update_dirlink(hfsmp, extent_info->forkType,
extent_info->dirlink_desc, extent_info->dirlink_attr,
&(extent_info->dirlink_fork->ff_data));
} else {
cp->c_flag |= C_MODIFIED;
if (extent_info->is_sysfile) {
error = hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT | HFS_FVH_WRITE_ALT);
}
}
} else {
error = BTReplaceRecord(extent_info->fcb, extent_info->iterator,
&(extent_info->btdata), extent_info->recordlen);
}
if (error) {
printf ("hfs_reclaim_extent: fileID=%u, update record error=%u\n", extent_info->fileID, error);
goto out;
}
error = BlockDeallocate(hfsmp, oldStartBlock, oldBlockCount, HFS_ALLOC_SKIPFREEBLKS);
if (error) {
printf ("hfs_reclaim_extent: fileID=%u start=%u, %u:(%u,%u) BlockDeallocate error=%d\n", extent_info->fileID, extent_info->recStartBlock, index, oldStartBlock, oldBlockCount, error);
goto out;
}
extent_info->blocks_relocated += newBlockCount;
if (hfs_resize_debug) {
printf ("hfs_reclaim_extent: Relocated record:%u %u:(%u,%u) to (%u,%u)\n", extent_info->overflow_count, index, oldStartBlock, oldBlockCount, newStartBlock, newBlockCount);
}
out:
if (error != 0) {
if (blocks_allocated == true) {
BlockDeallocate(hfsmp, newStartBlock, newBlockCount, HFS_ALLOC_SKIPFREEBLKS);
}
} else {
extent_info->cur_blockCount += newBlockCount;
}
hfs_systemfile_unlock(hfsmp, extent_info->lockflags);
if ((extent_info->catalog_fp) &&
(extent_info->is_sysfile == false)) {
hfs_update(extent_info->vp, 0);
}
hfs_end_transaction(hfsmp);
return error;
}
static void
hfs_truncatefs_progress(struct hfsmount *hfsmp)
{
u_int32_t cur_progress = 0;
hfs_resize_progress(hfsmp, &cur_progress);
if (cur_progress > (hfsmp->hfs_resize_progress + 9)) {
printf("hfs_truncatefs: %d%% done...\n", cur_progress);
hfsmp->hfs_resize_progress = cur_progress;
}
return;
}
static int
hfs_reclaim_file(struct hfsmount *hfsmp, struct vnode *vp, u_int32_t fileID,
u_int8_t forktype, u_long allocLimit, vfs_context_t context)
{
int error = 0;
struct hfs_reclaim_extent_info *extent_info;
int i;
int lockflags = 0;
struct cnode *cp;
struct filefork *fp;
int took_truncate_lock = false;
int release_desc = false;
HFSPlusExtentKey *key;
if (vp == NULL) {
return 0;
}
cp = VTOC(vp);
if (hfs_resize_debug) {
const char *filename = (const char *) cp->c_desc.cd_nameptr;
int namelen = cp->c_desc.cd_namelen;
if (filename == NULL) {
filename = "";
namelen = 0;
}
printf("hfs_reclaim_file: reclaiming '%.*s'\n", namelen, filename);
}
MALLOC(extent_info, struct hfs_reclaim_extent_info *,
sizeof(struct hfs_reclaim_extent_info), M_TEMP, M_WAITOK);
if (extent_info == NULL) {
return ENOMEM;
}
bzero(extent_info, sizeof(struct hfs_reclaim_extent_info));
extent_info->vp = vp;
extent_info->fileID = fileID;
extent_info->forkType = forktype;
extent_info->is_sysfile = vnode_issystem(vp);
if (vnode_isdir(vp) && (cp->c_flag & C_HARDLINK)) {
extent_info->is_dirlink = true;
}
lockflags = SFL_BITMAP | SFL_EXTENTS;
if ((fileID == kHFSCatalogFileID) || (extent_info->is_dirlink == true)) {
lockflags |= SFL_CATALOG;
} else if (fileID == kHFSAttributesFileID) {
lockflags |= SFL_ATTRIBUTE;
} else if (fileID == kHFSStartupFileID) {
lockflags |= SFL_STARTUP;
}
extent_info->lockflags = lockflags;
extent_info->fcb = VTOF(hfsmp->hfs_extents_vp);
if (extent_info->is_sysfile) {
error = hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
if (error) {
printf ("hfs_reclaim_file: journal_flush returned %d\n", error);
goto out;
}
} else if (extent_info->is_dirlink == false) {
buf_flushdirtyblks(vp, 0, BUF_SKIP_LOCKED, "hfs_reclaim_file");
hfs_unlock(cp);
hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
took_truncate_lock = true;
(void) cluster_push(vp, 0);
error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS);
if (error) {
goto out;
}
if (cp->c_flag & C_NOEXISTS) {
error = 0;
goto out;
}
error = vnode_waitforwrites(vp, 0, 0, 0, "hfs_reclaim_file");
if (error) {
goto out;
}
}
if (hfs_resize_debug) {
printf("hfs_reclaim_file: === Start reclaiming %sfork for %sid=%u ===\n", (forktype ? "rsrc" : "data"), (extent_info->is_dirlink ? "dirlink" : "file"), fileID);
}
if (extent_info->is_dirlink) {
MALLOC(extent_info->dirlink_desc, struct cat_desc *,
sizeof(struct cat_desc), M_TEMP, M_WAITOK);
MALLOC(extent_info->dirlink_attr, struct cat_attr *,
sizeof(struct cat_attr), M_TEMP, M_WAITOK);
MALLOC(extent_info->dirlink_fork, struct filefork *,
sizeof(struct filefork), M_TEMP, M_WAITOK);
if ((extent_info->dirlink_desc == NULL) ||
(extent_info->dirlink_attr == NULL) ||
(extent_info->dirlink_fork == NULL)) {
error = ENOMEM;
goto out;
}
fp = extent_info->dirlink_fork;
bzero(extent_info->dirlink_fork, sizeof(struct filefork));
extent_info->dirlink_fork->ff_cp = cp;
lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
error = cat_lookup_dirlink(hfsmp, fileID, forktype,
extent_info->dirlink_desc, extent_info->dirlink_attr,
&(extent_info->dirlink_fork->ff_data));
hfs_systemfile_unlock(hfsmp, lockflags);
if (error) {
printf ("hfs_reclaim_file: cat_lookup_dirlink for fileID=%u returned error=%u\n", fileID, error);
goto out;
}
release_desc = true;
} else {
fp = VTOF(vp);
}
extent_info->catalog_fp = fp;
extent_info->recStartBlock = 0;
extent_info->extents = extent_info->catalog_fp->ff_extents;
for (i = 0; i < kHFSPlusExtentDensity; ++i) {
if (fp->ff_extents[i].blockCount == 0) {
break;
}
extent_info->extent_index = i;
error = hfs_reclaim_extent(hfsmp, allocLimit, extent_info, context);
if (error) {
printf ("hfs_reclaim_file: fileID=%u #%d %u:(%u,%u) hfs_reclaim_extent error=%d\n", fileID, extent_info->overflow_count, i, fp->ff_extents[i].startBlock, fp->ff_extents[i].blockCount, error);
goto out;
}
}
if (fp->ff_blocks <= extent_info->cur_blockCount) {
if (0 && hfs_resize_debug) {
printf ("hfs_reclaim_file: Nothing more to relocate, offset=%d, ff_blocks=%u, cur_blockCount=%u\n", i, fp->ff_blocks, extent_info->cur_blockCount);
}
goto out;
}
if (hfs_resize_debug) {
printf ("hfs_reclaim_file: Will check overflow records, offset=%d, ff_blocks=%u, cur_blockCount=%u\n", i, fp->ff_blocks, extent_info->cur_blockCount);
}
MALLOC(extent_info->iterator, struct BTreeIterator *, sizeof(struct BTreeIterator), M_TEMP, M_WAITOK);
if (extent_info->iterator == NULL) {
error = ENOMEM;
goto out;
}
bzero(extent_info->iterator, sizeof(struct BTreeIterator));
key = (HFSPlusExtentKey *) &(extent_info->iterator->key);
key->keyLength = kHFSPlusExtentKeyMaximumLength;
key->forkType = forktype;
key->fileID = fileID;
key->startBlock = extent_info->cur_blockCount;
extent_info->btdata.bufferAddress = extent_info->record.overflow;
extent_info->btdata.itemSize = sizeof(HFSPlusExtentRecord);
extent_info->btdata.itemCount = 1;
extent_info->catalog_fp = NULL;
lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
error = BTSearchRecord(extent_info->fcb, extent_info->iterator,
&(extent_info->btdata), &(extent_info->recordlen),
extent_info->iterator);
hfs_systemfile_unlock(hfsmp, lockflags);
while (error == 0) {
extent_info->overflow_count++;
extent_info->recStartBlock = key->startBlock;
extent_info->extents = extent_info->record.overflow;
for (i = 0; i < kHFSPlusExtentDensity; i++) {
if (extent_info->record.overflow[i].blockCount == 0) {
goto out;
}
extent_info->extent_index = i;
error = hfs_reclaim_extent(hfsmp, allocLimit, extent_info, context);
if (error) {
printf ("hfs_reclaim_file: fileID=%u #%d %u:(%u,%u) hfs_reclaim_extent error=%d\n", fileID, extent_info->overflow_count, i, extent_info->record.overflow[i].startBlock, extent_info->record.overflow[i].blockCount, error);
goto out;
}
}
lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
error = BTIterateRecord(extent_info->fcb, kBTreeNextRecord,
extent_info->iterator, &(extent_info->btdata),
&(extent_info->recordlen));
hfs_systemfile_unlock(hfsmp, lockflags);
if (error) {
break;
}
if ((key->fileID != fileID) || (key->forkType != forktype)) {
break;
}
}
if (error == fsBTRecordNotFoundErr || error == fsBTEndOfIterationErr) {
error = 0;
}
out:
if (extent_info->blocks_relocated) {
hfsmp->hfs_resize_blocksmoved += extent_info->blocks_relocated;
hfs_truncatefs_progress(hfsmp);
if (fileID < kHFSFirstUserCatalogNodeID) {
printf ("hfs_reclaim_file: Relocated %u blocks from fileID=%u on \"%s\"\n",
extent_info->blocks_relocated, fileID, hfsmp->vcbVN);
}
}
if (extent_info->iterator) {
FREE(extent_info->iterator, M_TEMP);
}
if (release_desc == true) {
cat_releasedesc(extent_info->dirlink_desc);
}
if (extent_info->dirlink_desc) {
FREE(extent_info->dirlink_desc, M_TEMP);
}
if (extent_info->dirlink_attr) {
FREE(extent_info->dirlink_attr, M_TEMP);
}
if (extent_info->dirlink_fork) {
FREE(extent_info->dirlink_fork, M_TEMP);
}
if ((extent_info->blocks_relocated != 0) && (extent_info->is_sysfile == false)) {
hfs_update(vp, 0);
}
if (took_truncate_lock) {
hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
}
if (extent_info) {
FREE(extent_info, M_TEMP);
}
if (hfs_resize_debug) {
printf("hfs_reclaim_file: === Finished relocating %sfork for fileid=%u (error=%d) ===\n", (forktype ? "rsrc" : "data"), fileID, error);
}
return error;
}
struct hfs_journal_relocate_args {
struct hfsmount *hfsmp;
vfs_context_t context;
u_int32_t newStartBlock;
u_int32_t newBlockCount;
};
static errno_t
hfs_journal_relocate_callback(void *_args)
{
int error;
struct hfs_journal_relocate_args *args = _args;
struct hfsmount *hfsmp = args->hfsmp;
buf_t bp;
JournalInfoBlock *jibp;
error = buf_meta_bread(hfsmp->hfs_devvp,
(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);
if (bp) {
buf_brelse(bp);
}
return error;
}
jibp = (JournalInfoBlock*) buf_dataptr(bp);
jibp->offset = SWAP_BE64((u_int64_t)args->newStartBlock * hfsmp->blockSize);
jibp->size = SWAP_BE64((u_int64_t)args->newBlockCount * hfsmp->blockSize);
if (journal_uses_fua(hfsmp->jnl))
buf_markfua(bp);
error = buf_bwrite(bp);
if (error) {
printf("hfs_journal_relocate_callback: failed to write JIB (%d)\n", error);
return error;
}
if (!journal_uses_fua(hfsmp->jnl)) {
error = hfs_flush(hfsmp, HFS_FLUSH_CACHE);
if (error) {
printf("hfs_journal_relocate_callback: hfs_flush failed (%d)\n", error);
error = 0;
}
}
return error;
}
#define HFS_RESIZE_TRUNCATE 1
#define HFS_RESIZE_EXTEND 2
static int
hfs_relocate_journal_file(struct hfsmount *hfsmp, u_int32_t jnl_size, int resize_type, vfs_context_t context)
{
int error;
int journal_err;
int lockflags;
u_int32_t oldStartBlock;
u_int32_t newStartBlock;
u_int32_t oldBlockCount;
u_int32_t newBlockCount;
u_int32_t jnlBlockCount;
u_int32_t alloc_skipfreeblks;
struct cat_desc journal_desc;
struct cat_attr journal_attr;
struct cat_fork journal_fork;
struct hfs_journal_relocate_args callback_args;
jnlBlockCount = howmany(jnl_size, hfsmp->blockSize);
if (resize_type == HFS_RESIZE_TRUNCATE) {
alloc_skipfreeblks = HFS_ALLOC_SKIPFREEBLKS;
} else {
alloc_skipfreeblks = 0;
}
error = hfs_start_transaction(hfsmp);
if (error) {
printf("hfs_relocate_journal_file: hfs_start_transaction returned %d\n", error);
return error;
}
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
error = BlockAllocate(hfsmp, 1, jnlBlockCount, jnlBlockCount,
HFS_ALLOC_METAZONE | HFS_ALLOC_FORCECONTIG | HFS_ALLOC_FLUSHTXN | alloc_skipfreeblks,
&newStartBlock, &newBlockCount);
if (error) {
printf("hfs_relocate_journal_file: BlockAllocate returned %d\n", error);
goto fail;
}
if (newBlockCount != jnlBlockCount) {
printf("hfs_relocate_journal_file: newBlockCount != jnlBlockCount (%u, %u)\n", newBlockCount, jnlBlockCount);
goto free_fail;
}
error = cat_idlookup(hfsmp, hfsmp->hfs_jnlfileid, 1, 0, &journal_desc, &journal_attr, &journal_fork);
if (error) {
printf("hfs_relocate_journal_file: cat_idlookup returned %d\n", error);
goto free_fail;
}
oldStartBlock = journal_fork.cf_extents[0].startBlock;
oldBlockCount = journal_fork.cf_extents[0].blockCount;
error = BlockDeallocate(hfsmp, oldStartBlock, oldBlockCount, alloc_skipfreeblks);
if (error) {
printf("hfs_relocate_journal_file: BlockDeallocate returned %d\n", error);
goto free_fail;
}
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;
error = cat_update(hfsmp, &journal_desc, &journal_attr, &journal_fork, NULL);
cat_releasedesc(&journal_desc);
if (error) {
printf("hfs_relocate_journal_file: cat_update returned %d\n", error);
goto free_fail;
}
if (hfsmp->jvp == hfsmp->hfs_devvp) {
callback_args.hfsmp = hfsmp;
callback_args.context = context;
callback_args.newStartBlock = newStartBlock;
callback_args.newBlockCount = newBlockCount;
error = journal_relocate(hfsmp->jnl, (off_t)newStartBlock*hfsmp->blockSize,
(off_t)newBlockCount*hfsmp->blockSize, 0,
hfs_journal_relocate_callback, &callback_args);
if (error) {
printf("hfs_relocate_journal_file: journal_relocate returned %d\n", error);
goto fail;
}
if (hfs_resize_debug) {
printf ("hfs_relocate_journal_file: Successfully relocated journal from (%u,%u) to (%u,%u)\n", oldStartBlock, oldBlockCount, newStartBlock, newBlockCount);
}
hfsmp->jnl_start = newStartBlock;
hfsmp->jnl_size = (off_t)newBlockCount * hfsmp->blockSize;
}
hfs_systemfile_unlock(hfsmp, lockflags);
error = hfs_end_transaction(hfsmp);
if (error) {
printf("hfs_relocate_journal_file: hfs_end_transaction returned %d\n", error);
}
return error;
free_fail:
journal_err = BlockDeallocate(hfsmp, newStartBlock, newBlockCount, HFS_ALLOC_SKIPFREEBLKS);
if (journal_err) {
printf("hfs_relocate_journal_file: BlockDeallocate returned %d\n", error);
hfs_mark_inconsistent(hfsmp, HFS_ROLLBACK_FAILED);
}
fail:
hfs_systemfile_unlock(hfsmp, lockflags);
(void) hfs_end_transaction(hfsmp);
if (hfs_resize_debug) {
printf ("hfs_relocate_journal_file: Error relocating journal file (error=%d)\n", error);
}
return error;
}
static int
hfs_reclaim_journal_file(struct hfsmount *hfsmp, u_int32_t allocLimit, vfs_context_t context)
{
int error = 0;
u_int32_t startBlock;
u_int32_t blockCount = hfsmp->jnl_size / hfsmp->blockSize;
if (hfsmp->jvp == hfsmp->hfs_devvp) {
startBlock = hfsmp->jnl_start;
blockCount = hfsmp->jnl_size / hfsmp->blockSize;
} else {
u_int32_t fileid;
u_int32_t old_jnlfileid;
struct cat_attr attr;
struct cat_fork fork;
old_jnlfileid = hfsmp->hfs_jnlfileid;
hfsmp->hfs_jnlfileid = 0;
fileid = GetFileInfo(hfsmp, kHFSRootFolderID, ".journal", &attr, &fork);
hfsmp->hfs_jnlfileid = old_jnlfileid;
if (fileid != old_jnlfileid) {
printf("hfs_reclaim_journal_file: cannot find .journal file!\n");
return EIO;
}
startBlock = fork.cf_extents[0].startBlock;
blockCount = fork.cf_extents[0].blockCount;
}
if (startBlock + blockCount <= allocLimit) {
return 0;
}
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);
printf ("hfs_reclaim_journal_file: Relocated %u blocks from journal on \"%s\"\n",
blockCount, hfsmp->vcbVN);
}
return error;
}
static int
hfs_reclaim_journal_info_block(struct hfsmount *hfsmp, u_int32_t allocLimit, vfs_context_t context)
{
int error;
int journal_err;
int lockflags;
u_int32_t oldBlock;
u_int32_t newBlock;
u_int32_t blockCount;
struct cat_desc jib_desc;
struct cat_attr jib_attr;
struct cat_fork jib_fork;
buf_t old_bp, new_bp;
if (hfsmp->vcbJinfoBlock <= allocLimit) {
return 0;
}
error = hfs_start_transaction(hfsmp);
if (error) {
printf("hfs_reclaim_journal_info_block: hfs_start_transaction returned %d\n", error);
return error;
}
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
error = BlockAllocate(hfsmp, 1, 1, 1,
HFS_ALLOC_METAZONE | HFS_ALLOC_FORCECONTIG | HFS_ALLOC_SKIPFREEBLKS | HFS_ALLOC_FLUSHTXN,
&newBlock, &blockCount);
if (error) {
printf("hfs_reclaim_journal_info_block: BlockAllocate returned %d\n", error);
goto fail;
}
if (blockCount != 1) {
printf("hfs_reclaim_journal_info_block: blockCount != 1 (%u)\n", blockCount);
goto free_fail;
}
error = buf_meta_bread(hfsmp->hfs_devvp,
(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);
if (old_bp) {
buf_brelse(old_bp);
}
goto free_fail;
}
new_bp = buf_getblk(hfsmp->hfs_devvp,
(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);
if (journal_uses_fua(hfsmp->jnl))
buf_markfua(new_bp);
error = buf_bwrite(new_bp);
if (error) {
printf("hfs_reclaim_journal_info_block: failed to write new JIB (%d)\n", error);
goto free_fail;
}
if (!journal_uses_fua(hfsmp->jnl)) {
error = hfs_flush(hfsmp, HFS_FLUSH_CACHE);
if (error) {
printf("hfs_reclaim_journal_info_block: hfs_flush failed (%d)\n", error);
}
}
error = BlockDeallocate(hfsmp, hfsmp->vcbJinfoBlock, 1, HFS_ALLOC_SKIPFREEBLKS);
if (error) {
printf("hfs_reclaim_journal_info_block: BlockDeallocate returned %d\n", error);
goto free_fail;
}
error = cat_idlookup(hfsmp, hfsmp->hfs_jnlinfoblkid, 1, 0, &jib_desc, &jib_attr, &jib_fork);
if (error) {
printf("hfs_reclaim_journal_info_block: cat_idlookup returned %d\n", error);
goto fail;
}
oldBlock = jib_fork.cf_extents[0].startBlock;
jib_fork.cf_size = hfsmp->blockSize;
jib_fork.cf_extents[0].startBlock = newBlock;
jib_fork.cf_extents[0].blockCount = 1;
jib_fork.cf_blocks = 1;
error = cat_update(hfsmp, &jib_desc, &jib_attr, &jib_fork, NULL);
cat_releasedesc(&jib_desc);
if (error) {
printf("hfs_reclaim_journal_info_block: cat_update returned %d\n", error);
goto fail;
}
hfsmp->vcbJinfoBlock = newBlock;
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;
}
hfs_systemfile_unlock(hfsmp, lockflags);
error = hfs_end_transaction(hfsmp);
if (error) {
printf("hfs_reclaim_journal_info_block: hfs_end_transaction returned %d\n", error);
}
error = hfs_flush(hfsmp, HFS_FLUSH_JOURNAL);
if (error) {
printf("hfs_reclaim_journal_info_block: journal_flush returned %d\n", error);
}
hfsmp->hfs_resize_blocksmoved += 1;
hfs_truncatefs_progress(hfsmp);
if (!error) {
printf ("hfs_reclaim_journal_info: Relocated 1 block from journal info on \"%s\"\n",
hfsmp->vcbVN);
if (hfs_resize_debug) {
printf ("hfs_reclaim_journal_info_block: Successfully relocated journal info block from (%u,%u) to (%u,%u)\n", oldBlock, blockCount, newBlock, blockCount);
}
}
return error;
free_fail:
journal_err = BlockDeallocate(hfsmp, newBlock, blockCount, HFS_ALLOC_SKIPFREEBLKS);
if (journal_err) {
printf("hfs_reclaim_journal_info_block: BlockDeallocate returned %d\n", error);
hfs_mark_inconsistent(hfsmp, HFS_ROLLBACK_FAILED);
}
fail:
hfs_systemfile_unlock(hfsmp, lockflags);
(void) hfs_end_transaction(hfsmp);
if (hfs_resize_debug) {
printf ("hfs_reclaim_journal_info_block: Error relocating journal info block (error=%d)\n", error);
}
return error;
}
static u_int64_t
calculate_journal_size(struct hfsmount *hfsmp, u_int32_t sector_size, u_int64_t sector_count)
{
u_int64_t journal_size;
u_int32_t journal_scale;
#define DEFAULT_JOURNAL_SIZE (8*1024*1024)
#define MAX_JOURNAL_SIZE (512*1024*1024)
journal_scale = (sector_size * sector_count) / ((u_int64_t)100 * 1024 * 1024 * 1024);
journal_size = DEFAULT_JOURNAL_SIZE * (journal_scale + 1);
if (journal_size > MAX_JOURNAL_SIZE) {
journal_size = MAX_JOURNAL_SIZE;
}
if (journal_size < hfsmp->blockSize) {
journal_size = hfsmp->blockSize;
}
return journal_size;
}
static int
hfs_extend_journal(struct hfsmount *hfsmp, u_int32_t sector_size, u_int64_t sector_count, vfs_context_t context)
{
int error = 0;
u_int64_t calc_journal_size;
if (hfsmp->jvp != hfsmp->hfs_devvp) {
if (hfs_resize_debug) {
printf("hfs_extend_journal: not resizing the journal because it is on an external device.\n");
}
return 0;
}
calc_journal_size = calculate_journal_size(hfsmp, sector_size, sector_count);
if (calc_journal_size <= hfsmp->jnl_size) {
goto out;
}
if (hfs_resize_debug) {
printf ("hfs_extend_journal: journal old=%u, new=%qd\n", hfsmp->jnl_size, calc_journal_size);
}
error = hfs_relocate_journal_file(hfsmp, calc_journal_size, HFS_RESIZE_EXTEND, context);
if (error == 0) {
printf ("hfs_extend_journal: Extended journal size to %u bytes on \"%s\"\n",
hfsmp->jnl_size, hfsmp->vcbVN);
}
out:
return error;
}
static int
hfs_reclaim_xattr(struct hfsmount *hfsmp, struct vnode *vp, u_int32_t fileID, u_int32_t allocLimit, vfs_context_t context)
{
int error = 0;
struct hfs_reclaim_extent_info *extent_info;
int i;
HFSPlusAttrKey *key;
int *lockflags;
if (hfs_resize_debug) {
printf("hfs_reclaim_xattr: === Start reclaiming xattr for id=%u ===\n", fileID);
}
MALLOC(extent_info, struct hfs_reclaim_extent_info *,
sizeof(struct hfs_reclaim_extent_info), M_TEMP, M_WAITOK);
if (extent_info == NULL) {
return ENOMEM;
}
bzero(extent_info, sizeof(struct hfs_reclaim_extent_info));
extent_info->vp = vp;
extent_info->fileID = fileID;
extent_info->is_xattr = true;
extent_info->is_sysfile = vnode_issystem(vp);
extent_info->fcb = VTOF(hfsmp->hfs_attribute_vp);
lockflags = &(extent_info->lockflags);
*lockflags = SFL_ATTRIBUTE | SFL_BITMAP;
MALLOC(extent_info->iterator, struct BTreeIterator *,
sizeof(struct BTreeIterator), M_TEMP, M_WAITOK);
if (extent_info->iterator == NULL) {
error = ENOMEM;
goto out;
}
bzero(extent_info->iterator, sizeof(struct BTreeIterator));
key = (HFSPlusAttrKey *)&(extent_info->iterator->key);
error = hfs_buildattrkey(fileID, NULL, key);
if (error) {
goto out;
}
extent_info->btdata.bufferAddress = &(extent_info->record.xattr);
extent_info->btdata.itemSize = sizeof(HFSPlusAttrRecord);
extent_info->btdata.itemCount = 1;
hfs_lock_truncate(VTOC(hfsmp->hfs_attrdata_vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
(void)cluster_push(hfsmp->hfs_attrdata_vp, 0);
error = vnode_waitforwrites(hfsmp->hfs_attrdata_vp, 0, 0, 0, "hfs_reclaim_xattr");
hfs_unlock_truncate(VTOC(hfsmp->hfs_attrdata_vp), HFS_LOCK_DEFAULT);
if (error) {
goto out;
}
*lockflags = hfs_systemfile_lock(hfsmp, *lockflags, HFS_EXCLUSIVE_LOCK);
error = BTSearchRecord(extent_info->fcb, extent_info->iterator,
&(extent_info->btdata), &(extent_info->recordlen),
extent_info->iterator);
hfs_systemfile_unlock(hfsmp, *lockflags);
if (error) {
if (error != btNotFound) {
goto out;
}
error = 0;
}
while (1) {
*lockflags = hfs_systemfile_lock(hfsmp, *lockflags, HFS_EXCLUSIVE_LOCK);
error = BTIterateRecord(extent_info->fcb, kBTreeNextRecord,
extent_info->iterator, &(extent_info->btdata),
&(extent_info->recordlen));
hfs_systemfile_unlock(hfsmp, *lockflags);
if (error || key->fileID != fileID) {
if (error == fsBTRecordNotFoundErr || error == fsBTEndOfIterationErr) {
error = 0;
}
break;
}
if ((extent_info->record.xattr.recordType != kHFSPlusAttrForkData) &&
(extent_info->record.xattr.recordType != kHFSPlusAttrExtents)) {
continue;
}
if (extent_info->record.xattr.recordType == kHFSPlusAttrForkData) {
extent_info->overflow_count = 0;
extent_info->extents = extent_info->record.xattr.forkData.theFork.extents;
} else if (extent_info->record.xattr.recordType == kHFSPlusAttrExtents) {
extent_info->overflow_count++;
extent_info->extents = extent_info->record.xattr.overflowExtents.extents;
}
extent_info->recStartBlock = key->startBlock;
for (i = 0; i < kHFSPlusExtentDensity; i++) {
if (extent_info->extents[i].blockCount == 0) {
break;
}
extent_info->extent_index = i;
error = hfs_reclaim_extent(hfsmp, allocLimit, extent_info, context);
if (error) {
printf ("hfs_reclaim_xattr: fileID=%u hfs_reclaim_extent error=%d\n", fileID, error);
goto out;
}
}
}
out:
if (extent_info->blocks_relocated) {
hfsmp->hfs_resize_blocksmoved += extent_info->blocks_relocated;
hfs_truncatefs_progress(hfsmp);
}
if (extent_info->iterator) {
FREE(extent_info->iterator, M_TEMP);
}
if (extent_info) {
FREE(extent_info, M_TEMP);
}
if (hfs_resize_debug) {
printf("hfs_reclaim_xattr: === Finished relocating xattr for fileid=%u (error=%d) ===\n", fileID, error);
}
return error;
}
static int
hfs_reclaim_xattrspace(struct hfsmount *hfsmp, u_int32_t allocLimit, vfs_context_t context)
{
int error = 0;
FCB *fcb;
struct BTreeIterator *iterator = NULL;
struct FSBufferDescriptor btdata;
HFSPlusAttrKey *key;
HFSPlusAttrRecord rec;
int lockflags = 0;
cnid_t prev_fileid = 0;
struct vnode *vp;
int need_relocate;
int btree_operation;
u_int32_t files_moved = 0;
u_int32_t prev_blocksmoved;
int i;
fcb = VTOF(hfsmp->hfs_attribute_vp);
prev_blocksmoved = hfsmp->hfs_resize_blocksmoved;
if (kmem_alloc(kernel_map, (vm_offset_t *)&iterator, sizeof(*iterator), VM_KERN_MEMORY_FILE)) {
return ENOMEM;
}
bzero(iterator, sizeof(*iterator));
key = (HFSPlusAttrKey *)&iterator->key;
btdata.bufferAddress = &rec;
btdata.itemSize = sizeof(rec);
btdata.itemCount = 1;
need_relocate = false;
btree_operation = kBTreeFirstRecord;
while (1) {
lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
error = BTIterateRecord(fcb, btree_operation, iterator, &btdata, NULL);
hfs_systemfile_unlock(hfsmp, lockflags);
if (error) {
if (error == fsBTRecordNotFoundErr || error == fsBTEndOfIterationErr) {
error = 0;
}
break;
}
btree_operation = kBTreeNextRecord;
if (prev_fileid == key->fileID) {
continue;
}
need_relocate = false;
switch(rec.recordType) {
case kHFSPlusAttrForkData:
for (i = 0; i < kHFSPlusExtentDensity; i++) {
if (rec.forkData.theFork.extents[i].blockCount == 0) {
break;
}
if ((rec.forkData.theFork.extents[i].startBlock +
rec.forkData.theFork.extents[i].blockCount) > allocLimit) {
need_relocate = true;
break;
}
}
break;
case kHFSPlusAttrExtents:
for (i = 0; i < kHFSPlusExtentDensity; i++) {
if (rec.overflowExtents.extents[i].blockCount == 0) {
break;
}
if ((rec.overflowExtents.extents[i].startBlock +
rec.overflowExtents.extents[i].blockCount) > allocLimit) {
need_relocate = true;
break;
}
}
break;
};
if (need_relocate == false) {
continue;
}
if (hfs_vget(hfsmp, key->fileID, &vp, 0, 1) != 0) {
continue;
}
error = hfs_reclaim_xattr(hfsmp, vp, key->fileID, allocLimit, context);
hfs_unlock(VTOC(vp));
vnode_put(vp);
if (error) {
printf ("hfs_reclaim_xattrspace: Error relocating xattrs for fileid=%u (error=%d)\n", key->fileID, error);
break;
}
prev_fileid = key->fileID;
files_moved++;
}
if (files_moved) {
printf("hfs_reclaim_xattrspace: Relocated %u xattr blocks from %u files on \"%s\"\n",
(hfsmp->hfs_resize_blocksmoved - prev_blocksmoved),
files_moved, hfsmp->vcbVN);
}
kmem_free(kernel_map, (vm_offset_t)iterator, sizeof(*iterator));
return error;
}
static int
hfs_reclaim_filespace(struct hfsmount *hfsmp, u_int32_t allocLimit, vfs_context_t context)
{
int error;
FCB *fcb;
struct BTreeIterator *iterator = NULL;
struct FSBufferDescriptor btdata;
int btree_operation;
int lockflags;
struct HFSPlusCatalogFile filerec;
struct vnode *vp;
struct vnode *rvp;
struct filefork *datafork;
u_int32_t files_moved = 0;
u_int32_t prev_blocksmoved;
#if CONFIG_PROTECT
int keys_generated = 0;
#endif
fcb = VTOF(hfsmp->hfs_catalog_vp);
prev_blocksmoved = hfsmp->hfs_resize_blocksmoved;
if (kmem_alloc(kernel_map, (vm_offset_t *)&iterator, sizeof(*iterator), VM_KERN_MEMORY_FILE)) {
error = ENOMEM;
goto reclaim_filespace_done;
}
#if CONFIG_PROTECT
if (cp_fs_protected (hfsmp->hfs_mp)) {
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;
}
}
#endif
bzero(iterator, sizeof(*iterator));
btdata.bufferAddress = &filerec;
btdata.itemSize = sizeof(filerec);
btdata.itemCount = 1;
btree_operation = kBTreeFirstRecord;
while (1) {
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
error = BTIterateRecord(fcb, btree_operation, iterator, &btdata, NULL);
hfs_systemfile_unlock(hfsmp, lockflags);
if (error) {
if (error == fsBTRecordNotFoundErr || error == fsBTEndOfIterationErr) {
error = 0;
}
break;
}
btree_operation = kBTreeNextRecord;
if (filerec.recordType != kHFSPlusFileRecord) {
continue;
}
bool overlaps;
error = hfs_file_extent_overlaps(hfsmp, allocLimit, &filerec, &overlaps);
if (error)
break;
if (!overlaps)
continue;
if (hfs_vget(hfsmp, filerec.fileID, &vp, 0, 1) != 0) {
if (hfs_resize_debug) {
printf("hfs_reclaim_filespace: hfs_vget(%u) failed.\n", filerec.fileID);
}
continue;
}
datafork = VTOF(vp);
if ((datafork && datafork->ff_blocks > 0) || vnode_isdir(vp)) {
error = hfs_reclaim_file(hfsmp, vp, filerec.fileID,
kHFSDataForkType, allocLimit, context);
if (error) {
printf ("hfs_reclaimspace: Error reclaiming datafork blocks of fileid=%u (error=%d)\n", filerec.fileID, error);
hfs_unlock(VTOC(vp));
vnode_put(vp);
break;
}
}
if (((VTOC(vp)->c_blocks - (datafork ? datafork->ff_blocks : 0)) > 0) || vnode_isdir(vp)) {
if (vnode_isdir(vp)) {
rvp = vp;
} else {
error = hfs_vgetrsrc(hfsmp, vp, &rvp);
if (error) {
printf ("hfs_reclaimspace: Error looking up rvp for fileid=%u (error=%d)\n", filerec.fileID, error);
hfs_unlock(VTOC(vp));
vnode_put(vp);
break;
}
VTOC(rvp)->c_flag |= C_NEED_RVNODE_PUT;
}
error = hfs_reclaim_file(hfsmp, rvp, filerec.fileID,
kHFSResourceForkType, allocLimit, context);
if (error) {
printf ("hfs_reclaimspace: Error reclaiming rsrcfork blocks of fileid=%u (error=%d)\n", filerec.fileID, error);
hfs_unlock(VTOC(vp));
vnode_put(vp);
break;
}
}
hfs_unlock(VTOC(vp));
vnode_put(vp);
files_moved++;
}
if (files_moved) {
printf("hfs_reclaim_filespace: Relocated %u blocks from %u files on \"%s\"\n",
(hfsmp->hfs_resize_blocksmoved - prev_blocksmoved),
files_moved, hfsmp->vcbVN);
}
reclaim_filespace_done:
if (iterator) {
kmem_free(kernel_map, (vm_offset_t)iterator, sizeof(*iterator));
}
#if CONFIG_PROTECT
if (keys_generated) {
cpx_free(hfsmp->hfs_resize_cpx);
hfsmp->hfs_resize_cpx = NULL;
}
#endif
return error;
}
static int
hfs_reclaimspace(struct hfsmount *hfsmp, u_int32_t allocLimit, u_int32_t reclaimblks, vfs_context_t context)
{
int error = 0;
error = hfs_count_allocated(hfsmp, allocLimit, reclaimblks, &(hfsmp->hfs_resize_totalblocks));
if (error) {
printf ("hfs_reclaimspace: Unable to determine total blocks to reclaim error=%d\n", error);
return error;
}
if (hfs_resize_debug) {
printf ("hfs_reclaimspace: Total number of blocks to reclaim = %u\n", hfsmp->hfs_resize_totalblocks);
}
hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
error = hfs_reclaim_journal_file(hfsmp, allocLimit, context);
if (error) {
printf("hfs_reclaimspace: hfs_reclaim_journal_file failed (%d)\n", error);
return error;
}
error = hfs_reclaim_journal_info_block(hfsmp, allocLimit, context);
if (error) {
printf("hfs_reclaimspace: hfs_reclaim_journal_info_block failed (%d)\n", error);
return error;
}
error = hfs_reclaim_file(hfsmp, hfsmp->hfs_extents_vp, kHFSExtentsFileID,
kHFSDataForkType, allocLimit, context);
if (error) {
printf("hfs_reclaimspace: reclaim extents b-tree returned %d\n", error);
return error;
}
error = hfs_reclaim_file(hfsmp, hfsmp->hfs_allocation_vp, kHFSAllocationFileID,
kHFSDataForkType, allocLimit, context);
if (error) {
printf("hfs_reclaimspace: reclaim allocation file returned %d\n", error);
return error;
}
error = hfs_reclaim_file(hfsmp, hfsmp->hfs_catalog_vp, kHFSCatalogFileID,
kHFSDataForkType, allocLimit, context);
if (error) {
printf("hfs_reclaimspace: reclaim catalog b-tree returned %d\n", error);
return error;
}
error = hfs_reclaim_file(hfsmp, hfsmp->hfs_attribute_vp, kHFSAttributesFileID,
kHFSDataForkType, allocLimit, context);
if (error) {
printf("hfs_reclaimspace: reclaim attribute b-tree returned %d\n", error);
return error;
}
error = hfs_reclaim_file(hfsmp, hfsmp->hfs_startup_vp, kHFSStartupFileID,
kHFSDataForkType, allocLimit, context);
if (error) {
printf("hfs_reclaimspace: reclaim startup file returned %d\n", error);
return error;
}
if (hfsmp->hfs_resize_blocksmoved) {
hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
}
error = hfs_reclaim_filespace(hfsmp, allocLimit, context);
if (error) {
printf ("hfs_reclaimspace: hfs_reclaim_filespace returned error=%d\n", error);
return error;
}
error = hfs_reclaim_xattrspace(hfsmp, allocLimit, context);
if (error) {
printf ("hfs_reclaimspace: hfs_reclaim_xattrspace returned error=%d\n", error);
return error;
}
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) {
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;
}
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 i, j;
int error;
int lockflags = 0;
u_int32_t endblock;
errno_t ret = 0;
for (i = 0; i < kHFSPlusExtentDensity; ++i) {
if (filerec->dataFork.extents[i].blockCount == 0) {
break;
}
endblock = filerec->dataFork.extents[i].startBlock +
filerec->dataFork.extents[i].blockCount;
if (endblock > allocLimit) {
*overlaps = true;
goto out;
}
}
for (j = 0; j < kHFSPlusExtentDensity; ++j) {
if (filerec->resourceFork.extents[j].blockCount == 0) {
break;
}
endblock = filerec->resourceFork.extents[j].startBlock +
filerec->resourceFork.extents[j].blockCount;
if (endblock > allocLimit) {
*overlaps = true;
goto out;
}
}
if ((i < kHFSPlusExtentDensity) && (j < kHFSPlusExtentDensity)) {
*overlaps = false;
goto out;
}
MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
bzero(iterator, sizeof(*iterator));
extkeyptr = (HFSPlusExtentKey *)&iterator->key;
extkeyptr->keyLength = kHFSPlusExtentKeyMaximumLength;
extkeyptr->forkType = 0;
extkeyptr->fileID = filerec->fileID;
extkeyptr->startBlock = 0;
btdata.bufferAddress = &extrec;
btdata.itemSize = sizeof(extrec);
btdata.itemCount = 1;
fcb = VTOF(hfsmp->hfs_extents_vp);
lockflags = hfs_systemfile_lock(hfsmp, SFL_EXTENTS, HFS_SHARED_LOCK);
error = BTSearchRecord(fcb, iterator, &btdata, NULL, iterator);
if (error && (error != btNotFound)) {
ret = MacToVFSError(error);
goto out;
}
error = BTIterateRecord(fcb, kBTreeNextRecord, iterator, &btdata, NULL);
while (error == 0) {
if (extkeyptr->fileID != filerec->fileID) {
break;
}
for (i = 0; i < kHFSPlusExtentDensity; ++i) {
if (extrec[i].blockCount == 0) {
break;
}
endblock = extrec[i].startBlock + extrec[i].blockCount;
if (endblock > allocLimit) {
*overlaps = true;
goto out;
}
}
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);
}
FREE(iterator, M_TEMP);
return ret;
}
__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_totalblocks > 0) {
*progress = (u_int32_t)((hfsmp->hfs_resize_blocksmoved * 100ULL) / hfsmp->hfs_resize_totalblocks);
} else {
*progress = 0;
}
return (0);
}