+/*
+ * Perform a case-insensitive compare of two UTF-8 filenames.
+ *
+ * Returns 0 if the strings match.
+ */
+__private_extern__
+int
+hfs_namecmp(const char *str1, size_t len1, const char *str2, size_t len2)
+{
+ u_int16_t *ustr1, *ustr2;
+ size_t ulen1, ulen2;
+ size_t maxbytes;
+ int cmp = -1;
+
+ if (len1 != len2)
+ return (cmp);
+
+ maxbytes = kHFSPlusMaxFileNameChars << 1;
+ MALLOC(ustr1, u_int16_t *, maxbytes << 1, M_TEMP, M_WAITOK);
+ ustr2 = ustr1 + (maxbytes >> 1);
+
+ if (utf8_decodestr(str1, len1, ustr1, &ulen1, maxbytes, ':', 0) != 0)
+ goto out;
+ if (utf8_decodestr(str2, len2, ustr2, &ulen2, maxbytes, ':', 0) != 0)
+ goto out;
+
+ cmp = FastUnicodeCompare(ustr1, ulen1>>1, ustr2, ulen2>>1);
+out:
+ FREE(ustr1, M_TEMP);
+ return (cmp);
+}
+
+
+__private_extern__
+int
+hfs_early_journal_init(struct hfsmount *hfsmp, HFSPlusVolumeHeader *vhp,
+ void *_args, int embeddedOffset, int mdb_offset,
+ HFSMasterDirectoryBlock *mdbp, struct ucred *cred)
+{
+ JournalInfoBlock *jibp;
+ struct buf *jinfo_bp, *bp;
+ int sectors_per_fsblock, arg_flags=0, arg_tbufsz=0;
+ int retval, blksize = hfsmp->hfs_phys_block_size;
+ struct vnode *devvp;
+ struct hfs_mount_args *args = _args;
+
+ devvp = hfsmp->hfs_devvp;
+
+ if (args != NULL && (args->flags & HFSFSMNT_EXTENDED_ARGS)) {
+ arg_flags = args->journal_flags;
+ arg_tbufsz = args->journal_tbuffer_size;
+ }
+
+ sectors_per_fsblock = SWAP_BE32(vhp->blockSize) / blksize;
+
+ retval = meta_bread(devvp,
+ embeddedOffset/blksize +
+ (SWAP_BE32(vhp->journalInfoBlock)*sectors_per_fsblock),
+ SWAP_BE32(vhp->blockSize), cred, &jinfo_bp);
+ if (retval)
+ return retval;
+
+ jibp = (JournalInfoBlock *)jinfo_bp->b_data;
+ jibp->flags = SWAP_BE32(jibp->flags);
+ jibp->offset = SWAP_BE64(jibp->offset);
+ jibp->size = SWAP_BE64(jibp->size);
+
+ if (jibp->flags & kJIJournalInFSMask) {
+ hfsmp->jvp = hfsmp->hfs_devvp;
+ } else {
+ printf("hfs: journal not stored in fs! don't know what to do.\n");
+ brelse(jinfo_bp);
+ return EINVAL;
+ }
+
+ // save this off for the hack-y check in hfs_remove()
+ hfsmp->jnl_start = jibp->offset / SWAP_BE32(vhp->blockSize);
+ hfsmp->jnl_size = jibp->size;
+
+ if (jibp->flags & kJIJournalNeedInitMask) {
+ printf("hfs: Initializing the journal (joffset 0x%llx sz 0x%llx)...\n",
+ jibp->offset + (off_t)embeddedOffset, jibp->size);
+ hfsmp->jnl = journal_create(hfsmp->jvp,
+ jibp->offset + (off_t)embeddedOffset,
+ jibp->size,
+ devvp,
+ blksize,
+ arg_flags,
+ arg_tbufsz,
+ hfs_sync_metadata, hfsmp->hfs_mp);
+
+ // no need to start a transaction here... if this were to fail
+ // we'd just re-init it on the next mount.
+ jibp->flags &= ~kJIJournalNeedInitMask;
+ jibp->flags = SWAP_BE32(jibp->flags);
+ jibp->offset = SWAP_BE64(jibp->offset);
+ jibp->size = SWAP_BE64(jibp->size);
+ bwrite(jinfo_bp);
+ jinfo_bp = NULL;
+ jibp = NULL;
+ } else {
+ //printf("hfs: Opening the journal (joffset 0x%llx sz 0x%llx vhp_blksize %d)...\n",
+ // jibp->offset + (off_t)embeddedOffset,
+ // jibp->size, SWAP_BE32(vhp->blockSize));
+
+ hfsmp->jnl = journal_open(hfsmp->jvp,
+ jibp->offset + (off_t)embeddedOffset,
+ jibp->size,
+ devvp,
+ blksize,
+ arg_flags,
+ arg_tbufsz,
+ hfs_sync_metadata, hfsmp->hfs_mp);
+
+ brelse(jinfo_bp);
+ jinfo_bp = NULL;
+ jibp = NULL;
+
+ if (hfsmp->jnl && mdbp) {
+ // reload the mdb because it could have changed
+ // if the journal had to be replayed.
+ if (mdb_offset == 0) {
+ mdb_offset = (embeddedOffset / blksize) + HFS_PRI_SECTOR(blksize);
+ }
+ retval = meta_bread(devvp, mdb_offset, blksize, cred, &bp);
+ if (retval) {
+ brelse(bp);
+ printf("hfs: failed to reload the mdb after opening the journal (retval %d)!\n",
+ retval);
+ return retval;
+ }
+ bcopy(bp->b_data + HFS_PRI_OFFSET(blksize), mdbp, 512);
+ brelse(bp);
+ bp = NULL;
+ }
+ }
+
+
+ //printf("journal @ 0x%x\n", hfsmp->jnl);
+
+ // if we expected the journal to be there and we couldn't
+ // create it or open it then we have to bail out.
+ if (hfsmp->jnl == NULL) {
+ printf("hfs: early jnl init: failed to open/create the journal (retval %d).\n", retval);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+//
+// This function will go and re-locate the .journal_info_block and
+// the .journal files in case they moved (which can happen if you
+// run Norton SpeedDisk). If we fail to find either file we just
+// disable journaling for this volume and return. We turn off the
+// journaling bit in the vcb and assume it will get written to disk
+// later (if it doesn't on the next mount we'd do the same thing
+// again which is harmless). If we disable journaling we don't
+// return an error so that the volume is still mountable.
+//
+// If the info we find for the .journal_info_block and .journal files
+// isn't what we had stored, we re-set our cached info and proceed
+// with opening the journal normally.
+//
+static int
+hfs_late_journal_init(struct hfsmount *hfsmp, HFSPlusVolumeHeader *vhp, void *_args)
+{
+ JournalInfoBlock *jibp;
+ struct buf *jinfo_bp, *bp;
+ int sectors_per_fsblock, arg_flags=0, arg_tbufsz=0;
+ int retval, need_flush = 0, write_jibp = 0;
+ struct vnode *devvp;
+ struct cat_attr jib_attr, jattr;
+ struct cat_fork jib_fork, jfork;
+ ExtendedVCB *vcb;
+ u_long fid;
+ struct hfs_mount_args *args = _args;
+
+ devvp = hfsmp->hfs_devvp;
+ vcb = HFSTOVCB(hfsmp);
+
+ if (args != NULL && (args->flags & HFSFSMNT_EXTENDED_ARGS)) {
+ if (args->journal_disable) {
+ return 0;
+ }
+
+ arg_flags = args->journal_flags;
+ arg_tbufsz = args->journal_tbuffer_size;
+ }
+
+ fid = GetFileInfo(vcb, kRootDirID, ".journal_info_block", &jib_attr, &jib_fork);
+ if (fid == 0 || jib_fork.cf_extents[0].startBlock == 0 || jib_fork.cf_size == 0) {
+ printf("hfs: can't find the .journal_info_block! disabling journaling (start: %d).\n",
+ jib_fork.cf_extents[0].startBlock);
+ vcb->vcbAtrb &= ~kHFSVolumeJournaledMask;
+ return 0;
+ }
+ hfsmp->hfs_jnlinfoblkid = fid;
+
+ // make sure the journal_info_block begins where we think it should.
+ if (SWAP_BE32(vhp->journalInfoBlock) != jib_fork.cf_extents[0].startBlock) {
+ printf("hfs: The journal_info_block moved (was: %d; is: %d). Fixing up\n",
+ SWAP_BE32(vhp->journalInfoBlock), jib_fork.cf_extents[0].startBlock);
+
+ vcb->vcbJinfoBlock = jib_fork.cf_extents[0].startBlock;
+ vhp->journalInfoBlock = SWAP_BE32(jib_fork.cf_extents[0].startBlock);
+ }
+
+
+ sectors_per_fsblock = SWAP_BE32(vhp->blockSize) / hfsmp->hfs_phys_block_size;
+ retval = meta_bread(devvp,
+ vcb->hfsPlusIOPosOffset / hfsmp->hfs_phys_block_size +
+ (SWAP_BE32(vhp->journalInfoBlock)*sectors_per_fsblock),
+ SWAP_BE32(vhp->blockSize), NOCRED, &jinfo_bp);
+ if (retval) {
+ printf("hfs: can't read journal info block. disabling journaling.\n");
+ vcb->vcbAtrb &= ~kHFSVolumeJournaledMask;
+ return 0;
+ }
+
+ jibp = (JournalInfoBlock *)jinfo_bp->b_data;
+ jibp->flags = SWAP_BE32(jibp->flags);
+ jibp->offset = SWAP_BE64(jibp->offset);
+ jibp->size = SWAP_BE64(jibp->size);
+
+ fid = GetFileInfo(vcb, kRootDirID, ".journal", &jattr, &jfork);
+ if (fid == 0 || jfork.cf_extents[0].startBlock == 0 || jfork.cf_size == 0) {
+ printf("hfs: can't find the journal file! disabling journaling (start: %d)\n",
+ jfork.cf_extents[0].startBlock);
+ brelse(jinfo_bp);
+ vcb->vcbAtrb &= ~kHFSVolumeJournaledMask;
+ return 0;
+ }
+ hfsmp->hfs_jnlfileid = fid;
+
+ // make sure the journal file begins where we think it should.
+ if ((jibp->offset / (u_int64_t)vcb->blockSize) != jfork.cf_extents[0].startBlock) {
+ printf("hfs: The journal file moved (was: %lld; is: %d). Fixing up\n",
+ (jibp->offset / (u_int64_t)vcb->blockSize), jfork.cf_extents[0].startBlock);
+
+ jibp->offset = (u_int64_t)jfork.cf_extents[0].startBlock * (u_int64_t)vcb->blockSize;
+ write_jibp = 1;
+ }
+
+ // check the size of the journal file.
+ if (jibp->size != (u_int64_t)jfork.cf_extents[0].blockCount*vcb->blockSize) {
+ printf("hfs: The journal file changed size! (was %lld; is %lld). Fixing up.\n",
+ jibp->size, (u_int64_t)jfork.cf_extents[0].blockCount*vcb->blockSize);
+
+ jibp->size = (u_int64_t)jfork.cf_extents[0].blockCount * vcb->blockSize;
+ write_jibp = 1;
+ }
+
+ if (jibp->flags & kJIJournalInFSMask) {
+ hfsmp->jvp = hfsmp->hfs_devvp;
+ } else {
+ printf("hfs: journal not stored in fs! don't know what to do.\n");
+ brelse(jinfo_bp);
+ return EINVAL;
+ }
+
+ // save this off for the hack-y check in hfs_remove()
+ hfsmp->jnl_start = jibp->offset / SWAP_BE32(vhp->blockSize);
+ hfsmp->jnl_size = jibp->size;
+
+ if (jibp->flags & kJIJournalNeedInitMask) {
+ printf("hfs: Initializing the journal (joffset 0x%llx sz 0x%llx)...\n",
+ jibp->offset + (off_t)vcb->hfsPlusIOPosOffset, jibp->size);
+ hfsmp->jnl = journal_create(hfsmp->jvp,
+ jibp->offset + (off_t)vcb->hfsPlusIOPosOffset,
+ jibp->size,
+ devvp,
+ hfsmp->hfs_phys_block_size,
+ arg_flags,
+ arg_tbufsz,
+ hfs_sync_metadata, hfsmp->hfs_mp);
+
+ // no need to start a transaction here... if this were to fail
+ // we'd just re-init it on the next mount.
+ jibp->flags &= ~kJIJournalNeedInitMask;
+ write_jibp = 1;
+
+ } else {
+ //
+ // if we weren't the last person to mount this volume
+ // then we need to throw away the journal because it
+ // is likely that someone else mucked with the disk.
+ // if the journal is empty this is no big deal. if the
+ // disk is dirty this prevents us from replaying the
+ // journal over top of changes that someone else made.
+ //
+ arg_flags |= JOURNAL_RESET;
+
+ //printf("hfs: Opening the journal (joffset 0x%llx sz 0x%llx vhp_blksize %d)...\n",
+ // jibp->offset + (off_t)vcb->hfsPlusIOPosOffset,
+ // jibp->size, SWAP_BE32(vhp->blockSize));
+
+ hfsmp->jnl = journal_open(hfsmp->jvp,
+ jibp->offset + (off_t)vcb->hfsPlusIOPosOffset,
+ jibp->size,
+ devvp,
+ hfsmp->hfs_phys_block_size,
+ arg_flags,
+ arg_tbufsz,
+ hfs_sync_metadata, hfsmp->hfs_mp);
+ }
+
+
+ if (write_jibp) {
+ jibp->flags = SWAP_BE32(jibp->flags);
+ jibp->offset = SWAP_BE64(jibp->offset);
+ jibp->size = SWAP_BE64(jibp->size);
+
+ bwrite(jinfo_bp);
+ } else {
+ brelse(jinfo_bp);
+ }
+ jinfo_bp = NULL;
+ jibp = NULL;
+
+ //printf("journal @ 0x%x\n", hfsmp->jnl);
+
+ // if we expected the journal to be there and we couldn't
+ // create it or open it then we have to bail out.
+ if (hfsmp->jnl == NULL) {
+ printf("hfs: late jnl init: failed to open/create the journal (retval %d).\n", retval);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Calculate the allocation zone for metadata.
+ *
+ * This zone includes the following:
+ * Allocation Bitmap file
+ * Overflow Extents file
+ * Journal file
+ * Quota files
+ * Clustered Hot files
+ * Catalog file
+ *
+ * METADATA ALLOCATION ZONE
+ * ____________________________________________________________________________
+ * | | | | | | |
+ * | BM | JF | OEF | CATALOG |---> | HOT FILES |
+ * |____|____|_____|_______________|______________________________|___________|
+ *
+ * <------------------------------- N * 128 MB ------------------------------->
+ *
+ */
+#define GIGABYTE (u_int64_t)(1024*1024*1024)
+
+#define OVERFLOW_DEFAULT_SIZE (4*1024*1024)
+#define OVERFLOW_MAXIMUM_SIZE (128*1024*1024)
+#define JOURNAL_DEFAULT_SIZE (8*1024*1024)
+#define JOURNAL_MAXIMUM_SIZE (512*1024*1024)
+#define HOTBAND_MINIMUM_SIZE (10*1024*1024)
+#define HOTBAND_MAXIMUM_SIZE (512*1024*1024)
+
+static void
+hfs_metadatazone_init(struct hfsmount *hfsmp)
+{
+ ExtendedVCB *vcb;
+ struct BTreeInfoRec btinfo;
+ u_int64_t fs_size;
+ u_int64_t zonesize;
+ u_int64_t temp;
+ u_int64_t filesize;
+ u_int32_t blk;
+ int items;
+
+ vcb = HFSTOVCB(hfsmp);
+ fs_size = (u_int64_t)vcb->blockSize * (u_int64_t)vcb->totalBlocks;
+
+ /*
+ * For volumes less than 10 GB, don't bother.
+ */
+ if (fs_size < ((u_int64_t)10 * GIGABYTE))
+ return;
+ /*
+ * Skip non-journaled volumes as well.
+ */
+ if (hfsmp->jnl == NULL)
+ return;
+
+ /*
+ * Start with allocation bitmap (a fixed size).
+ */
+ zonesize = roundup(vcb->totalBlocks / 8, vcb->vcbVBMIOSize);
+
+ /*
+ * Overflow Extents file gets 4 MB per 100 GB.
+ */
+ items = fs_size / ((u_int64_t)100 * GIGABYTE);
+ filesize = (u_int64_t)(items + 1) * OVERFLOW_DEFAULT_SIZE;
+ if (filesize > OVERFLOW_MAXIMUM_SIZE)
+ filesize = OVERFLOW_MAXIMUM_SIZE;
+ zonesize += filesize;
+ hfsmp->hfs_overflow_maxblks = filesize / vcb->blockSize;
+
+ /*
+ * Plan for at least 8 MB of journal for each
+ * 100 GB of disk space (up to a 512 MB).
+ */
+ items = fs_size / ((u_int64_t)100 * GIGABYTE);
+ filesize = (u_int64_t)(items + 1) * JOURNAL_DEFAULT_SIZE;
+ if (filesize > JOURNAL_MAXIMUM_SIZE)
+ filesize = JOURNAL_MAXIMUM_SIZE;
+ zonesize += filesize;
+
+ /*
+ * Catalog file gets 10 MB per 1 GB.
+ *
+ * How about considering the current catalog size (used nodes * node size)
+ * and the current file data size to help estimate the required
+ * catalog size.
+ */
+ filesize = MIN((fs_size / 1024) * 10, GIGABYTE);
+ hfsmp->hfs_catalog_maxblks = filesize / vcb->blockSize;
+ zonesize += filesize;
+
+ /*
+ * Add space for hot file region.
+ *
+ * ...for now, use 5 MB per 1 GB (0.5 %)
+ */
+ filesize = (fs_size / 1024) * 5;
+ if (filesize > HOTBAND_MAXIMUM_SIZE)
+ filesize = HOTBAND_MAXIMUM_SIZE;
+ else if (filesize < HOTBAND_MINIMUM_SIZE)
+ filesize = HOTBAND_MINIMUM_SIZE;
+ /*
+ * Calculate user quota file requirements.
+ */
+ items = QF_USERS_PER_GB * (fs_size / GIGABYTE);
+ if (items < QF_MIN_USERS)
+ items = QF_MIN_USERS;
+ else if (items > QF_MAX_USERS)
+ items = QF_MAX_USERS;
+ if (!powerof2(items)) {
+ int x = items;
+ items = 4;
+ while (x>>1 != 1) {
+ x = x >> 1;
+ items = items << 1;
+ }
+ }
+ filesize += (items + 1) * sizeof(struct dqblk);
+ /*
+ * Calculate group quota file requirements.
+ *
+ */
+ items = QF_GROUPS_PER_GB * (fs_size / GIGABYTE);
+ if (items < QF_MIN_GROUPS)
+ items = QF_MIN_GROUPS;
+ else if (items > QF_MAX_GROUPS)
+ items = QF_MAX_GROUPS;
+ if (!powerof2(items)) {
+ int x = items;
+ items = 4;
+ while (x>>1 != 1) {
+ x = x >> 1;
+ items = items << 1;
+ }
+ }
+ filesize += (items + 1) * sizeof(struct dqblk);
+ hfsmp->hfs_hotfile_maxblks = filesize / vcb->blockSize;
+ zonesize += filesize;
+
+ /*
+ * Round up entire zone to a bitmap block's worth.
+ * The extra space goes to the catalog file and hot file area.
+ */
+ temp = zonesize;
+ zonesize = roundup(zonesize, vcb->vcbVBMIOSize * 8 * vcb->blockSize);
+ temp = zonesize - temp; /* temp has extra space */
+ filesize += temp / 3;
+ hfsmp->hfs_catalog_maxblks += (temp - (temp / 3)) / vcb->blockSize;
+
+ /* Convert to allocation blocks. */
+ blk = zonesize / vcb->blockSize;
+
+ /* The default metadata zone location is at the start of volume. */
+ hfsmp->hfs_metazone_start = 1;
+ hfsmp->hfs_metazone_end = blk - 1;
+
+ /* The default hotfile area is at the end of the zone. */
+ hfsmp->hfs_hotfile_start = blk - (filesize / vcb->blockSize);
+ hfsmp->hfs_hotfile_end = hfsmp->hfs_metazone_end;
+ hfsmp->hfs_hotfile_freeblks = hfs_hotfile_freeblocks(hfsmp);
+#if 0
+ printf("HFS: metadata zone is %d to %d\n", hfsmp->hfs_metazone_start, hfsmp->hfs_metazone_end);
+ printf("HFS: hot file band is %d to %d\n", hfsmp->hfs_hotfile_start, hfsmp->hfs_hotfile_end);
+ printf("HFS: hot file band free blocks = %d\n", hfsmp->hfs_hotfile_freeblks);
+#endif
+ hfsmp->hfs_flags |= HFS_METADATA_ZONE;
+}
+
+
+static u_int32_t
+hfs_hotfile_freeblocks(struct hfsmount *hfsmp)
+{
+ ExtendedVCB *vcb = HFSTOVCB(hfsmp);
+ int freeblocks;
+
+ freeblocks = MetaZoneFreeBlocks(vcb);
+ /* Minus Extents overflow file reserve. */
+ freeblocks -=
+ hfsmp->hfs_overflow_maxblks - VTOF(vcb->extentsRefNum)->ff_blocks;
+ /* Minus catalog file reserve. */
+ freeblocks -=
+ hfsmp->hfs_catalog_maxblks - VTOF(vcb->catalogRefNum)->ff_blocks;
+ if (freeblocks < 0)
+ freeblocks = 0;
+
+ return MIN(freeblocks, hfsmp->hfs_hotfile_maxblks);
+}
+
+/*
+ * Determine if a file is a "virtual" metadata file.
+ * This includes journal and quota files.
+ */
+__private_extern__
+int
+hfs_virtualmetafile(struct cnode *cp)
+{
+ char * filename;
+
+
+ if (cp->c_parentcnid != kHFSRootFolderID)
+ return (0);
+
+ filename = cp->c_desc.cd_nameptr;
+ if (filename == NULL)
+ return (0);
+
+ if ((strcmp(filename, ".journal") == 0) ||
+ (strcmp(filename, ".journal_info_block") == 0) ||
+ (strcmp(filename, ".quota.user") == 0) ||
+ (strcmp(filename, ".quota.group") == 0) ||
+ (strcmp(filename, ".hotfiles.btree") == 0))
+ return (1);
+
+ return (0);
+}