+/*
+ * Extend a file system.
+ */
+static int
+hfs_extendfs(struct mount *mp, u_int64_t newsize, struct proc *p)
+{
+ struct vnode *vp;
+ struct vnode *devvp;
+ struct buf *bp;
+ struct hfsmount *hfsmp;
+ struct filefork *fp = NULL;
+ ExtendedVCB *vcb;
+ struct cat_fork forkdata;
+ u_int64_t oldsize;
+ u_int64_t newblkcnt;
+ u_int32_t addblks;
+ u_int64_t sectorcnt;
+ u_int32_t sectorsize;
+ daddr_t prev_alt_sector;
+ daddr_t bitmapblks;
+ int error;
+
+ hfsmp = VFSTOHFS(mp);
+ devvp = hfsmp->hfs_devvp;
+ vcb = HFSTOVCB(hfsmp);
+
+ /*
+ * - HFS Plus file systems only.
+ * - Journaling must be enabled.
+ * - No embedded volumes.
+ */
+ if ((vcb->vcbSigWord == kHFSSigWord) ||
+ (hfsmp->jnl == NULL) ||
+ (vcb->hfsPlusIOPosOffset != 0)) {
+ return (EPERM);
+ }
+ /*
+ * If extending file system by non-root, then verify
+ * ownership and check permissions.
+ */
+ if (p->p_ucred->cr_uid != 0) {
+ error = hfs_root(mp, &vp);
+ if (error)
+ return (error);
+ error = hfs_owner_rights(hfsmp, VTOC(vp)->c_uid, p->p_ucred, p, 0);
+ if (error == 0) {
+ error = hfs_write_access(vp, p->p_ucred, p, false);
+ }
+ vput(vp);
+ if (error)
+ return (error);
+
+ vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY, p);
+ error = VOP_ACCESS(devvp, VREAD | VWRITE, p->p_ucred, p);
+ VOP_UNLOCK(devvp, 0, p);
+ if (error)
+ return (error);
+ }
+ if (VOP_IOCTL(devvp, DKIOCGETBLOCKSIZE, (caddr_t)§orsize, 0, FSCRED, p)) {
+ return (ENXIO);
+ }
+ if (sectorsize != hfsmp->hfs_phys_block_size) {
+ return (ENXIO);
+ }
+ if (VOP_IOCTL(devvp, DKIOCGETBLOCKCOUNT, (caddr_t)§orcnt, 0, FSCRED, p)) {
+ return (ENXIO);
+ }
+ if ((sectorsize * sectorcnt) < newsize) {
+ printf("hfs_extendfs: not enough space on device\n");
+ return (ENOSPC);
+ }
+ oldsize = (u_int64_t)hfsmp->hfs_phys_block_count *
+ (u_int64_t)hfsmp->hfs_phys_block_size;
+
+ /*
+ * Validate new size.
+ */
+ if ((newsize <= oldsize) || (newsize % vcb->blockSize)) {
+ printf("hfs_extendfs: invalid size\n");
+ return (EINVAL);
+ }
+ newblkcnt = newsize / vcb->blockSize;
+ if (newblkcnt > (u_int64_t)0xFFFFFFFF)
+ return (EOVERFLOW);
+
+ addblks = newblkcnt - vcb->totalBlocks;
+
+ printf("hfs_extendfs: growing %s by %d blocks\n", vcb->vcbVN, addblks);
+ /*
+ * Enclose changes inside a transaction.
+ */
+ hfs_global_shared_lock_acquire(hfsmp);
+ if (journal_start_transaction(hfsmp->jnl) != 0) {
+ hfs_global_shared_lock_release(hfsmp);
+ return (EINVAL);
+ }
+
+ /*
+ * Remember the location of existing alternate VH.
+ */
+ prev_alt_sector = (vcb->hfsPlusIOPosOffset / sectorsize) +
+ HFS_ALT_SECTOR(sectorsize, hfsmp->hfs_phys_block_count);
+
+ vp = vcb->allocationsRefNum;
+ error = vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ if (error) {
+ goto out2;
+ }
+ fp = VTOF(vp);
+ bcopy(&fp->ff_data, &forkdata, sizeof(forkdata));
+
+ /*
+ * Calculate additional space required (if any) by allocation bitmap.
+ */
+ bitmapblks = roundup(newblkcnt / 8, vcb->vcbVBMIOSize) / vcb->blockSize;
+ if (bitmapblks > fp->ff_blocks)
+ bitmapblks -= fp->ff_blocks;
+ else
+ bitmapblks = 0;
+
+ if (bitmapblks > 0) {
+ daddr_t blkno;
+ daddr_t blkcnt;
+
+ /*
+ * Add a new extent to the allocation bitmap file.
+ */
+ error = AddFileExtent(vcb, fp, vcb->totalBlocks, bitmapblks);
+ if (error) {
+ printf("hfs_extendfs: error %d adding extents\n", error);
+ goto out;
+ }
+ blkcnt = bitmapblks;
+ blkno = fp->ff_blocks;
+ fp->ff_blocks += bitmapblks;
+ fp->ff_size += (u_int64_t)bitmapblks * (u_int64_t)vcb->blockSize;
+ VTOC(vp)->c_blocks = fp->ff_blocks;
+ /*
+ * Zero out the new bitmap blocks.
+ */
+ {
+
+ bp = NULL;
+ while (blkcnt > 0) {
+ error = meta_bread(vp, blkno, vcb->blockSize, NOCRED, &bp);
+ if (error) {
+ if (bp) {
+ brelse(bp);
+ }
+ break;
+ }
+ bzero((char *)bp->b_data, vcb->blockSize);
+ bp->b_flags |= B_AGE;
+ error = bwrite(bp);
+ if (error)
+ break;
+ --blkcnt;
+ ++blkno;
+ }
+ }
+ if (error) {
+ printf("hfs_extendfs: error %d clearing blocks\n", error);
+ goto out;
+ }
+ /*
+ * Mark the new bitmap space as allocated.
+ */
+ error = BlockMarkAllocated(vcb, vcb->totalBlocks, bitmapblks);
+ if (error) {
+ printf("hfs_extendfs: error %d setting bitmap\n", error);
+ goto out;
+ }
+ }
+ /*
+ * Mark the new alternate VH as allocated.
+ */
+ 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;
+ }
+ /*
+ * Mark the old alternate VH as free.
+ */
+ if (vcb->blockSize == 512)
+ (void) BlockMarkFree(vcb, vcb->totalBlocks - 2, 2);
+ else
+ (void) BlockMarkFree(vcb, vcb->totalBlocks - 1, 1);
+
+ /*
+ * Adjust file system variables for new space.
+ */
+ vcb->totalBlocks += addblks;
+ vcb->freeBlocks += addblks - bitmapblks;
+ hfsmp->hfs_phys_block_count = newsize / sectorsize;
+
+ MarkVCBDirty(vcb);
+ error = hfs_flushvolumeheader(hfsmp, MNT_WAIT, HFS_ALTFLUSH);
+ if (error) {
+ printf("hfs_extendfs: couldn't flush volume headers (%d)", error);
+ /*
+ * Restore to old state.
+ */
+ fp->ff_size -= (u_int64_t)bitmapblks * (u_int64_t)vcb->blockSize;
+ vcb->totalBlocks -= addblks;
+ vcb->freeBlocks -= addblks - bitmapblks;
+ hfsmp->hfs_phys_block_count = oldsize / sectorsize;
+ MarkVCBDirty(vcb);
+ if (vcb->blockSize == 512)
+ (void) BlockMarkAllocated(vcb, vcb->totalBlocks - 2, 2);
+ else
+ (void) BlockMarkAllocated(vcb, vcb->totalBlocks - 1, 1);
+ goto out;
+ }
+ /*
+ * Invalidate the old alternate volume header.
+ */
+ bp = NULL;
+ if (meta_bread(hfsmp->hfs_devvp, prev_alt_sector, sectorsize,
+ NOCRED, &bp) == 0) {
+ journal_modify_block_start(hfsmp->jnl, bp);
+ bzero(bp->b_data + HFS_ALT_OFFSET(sectorsize), kMDBSize);
+ journal_modify_block_end(hfsmp->jnl, bp);
+ } else if (bp) {
+ brelse(bp);
+ }
+out:
+ if (error && fp) {
+ /* Restore allocation fork. */
+ bcopy(&forkdata, &fp->ff_data, sizeof(forkdata));
+ VTOC(vp)->c_blocks = fp->ff_blocks;
+
+ }
+ VOP_UNLOCK(vp, 0, p);
+out2:
+ journal_end_transaction(hfsmp->jnl);
+ hfs_global_shared_lock_release(hfsmp);
+
+ return (error);
+}
+
+