+/*
+ * Initialize vnode for attribute data I/O.
+ *
+ * On success,
+ * - returns zero
+ * - the attrdata vnode is initialized as hfsmp->hfs_attrdata_vp
+ * - an iocount is taken on the attrdata vnode which exists
+ * for the entire duration of the mount. It is only dropped
+ * during unmount
+ * - the attrdata cnode is not locked
+ *
+ * On failure,
+ * - returns non-zero value
+ * - the caller does not have to worry about any locks or references
+ */
+int init_attrdata_vnode(struct hfsmount *hfsmp)
+{
+ vnode_t vp;
+ int result = 0;
+ struct cat_desc cat_desc;
+ struct cat_attr cat_attr;
+ struct cat_fork cat_fork;
+ int newvnode_flags = 0;
+
+ bzero(&cat_desc, sizeof(cat_desc));
+ cat_desc.cd_parentcnid = kHFSRootParentID;
+ cat_desc.cd_nameptr = (const u_int8_t *)hfs_attrdatafilename;
+ cat_desc.cd_namelen = strlen(hfs_attrdatafilename);
+ cat_desc.cd_cnid = kHFSAttributeDataFileID;
+ /* Tag vnode as system file, note that we can still use cluster I/O */
+ cat_desc.cd_flags |= CD_ISMETA;
+
+ bzero(&cat_attr, sizeof(cat_attr));
+ cat_attr.ca_linkcount = 1;
+ cat_attr.ca_mode = S_IFREG;
+ cat_attr.ca_fileid = cat_desc.cd_cnid;
+ cat_attr.ca_blocks = hfsmp->totalBlocks;
+
+ /*
+ * The attribute data file is a virtual file that spans the
+ * entire file system space.
+ *
+ * Each extent-based attribute occupies a unique portion of
+ * in this virtual file. The cluster I/O is done using actual
+ * allocation block offsets so no additional mapping is needed
+ * for the VNOP_BLOCKMAP call.
+ *
+ * This approach allows the attribute data to be cached without
+ * incurring the high cost of using a separate vnode per attribute.
+ *
+ * Since we need to acquire the attribute b-tree file lock anyways,
+ * the virtual file doesn't introduce any additional serialization.
+ */
+ bzero(&cat_fork, sizeof(cat_fork));
+ cat_fork.cf_size = (u_int64_t)hfsmp->totalBlocks * (u_int64_t)hfsmp->blockSize;
+ cat_fork.cf_blocks = hfsmp->totalBlocks;
+ cat_fork.cf_extents[0].startBlock = 0;
+ cat_fork.cf_extents[0].blockCount = cat_fork.cf_blocks;
+
+ result = hfs_getnewvnode(hfsmp, NULL, NULL, &cat_desc, 0, &cat_attr,
+ &cat_fork, &vp, &newvnode_flags);
+ if (result == 0) {
+ hfsmp->hfs_attrdata_vp = vp;
+ hfs_unlock(VTOC(vp));
+ }
+ return (result);
+}
+
+/*
+ * Read an extent based attribute.
+ */
+static int
+read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents)
+{
+ vnode_t evp = hfsmp->hfs_attrdata_vp;
+ int bufsize;
+ int64_t iosize;
+ int attrsize;
+ int blksize;
+ int i;
+ int result = 0;
+
+ hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT);
+
+ bufsize = (int)uio_resid(uio);
+ attrsize = (int)datasize;
+ blksize = (int)hfsmp->blockSize;
+
+ /*
+ * Read the attribute data one extent at a time.
+ * For the typical case there is only one extent.
+ */
+ for (i = 0; (attrsize > 0) && (bufsize > 0) && (extents[i].startBlock != 0); ++i) {
+ iosize = extents[i].blockCount * blksize;
+ iosize = MIN(iosize, attrsize);
+ iosize = MIN(iosize, bufsize);
+ uio_setresid(uio, iosize);
+ uio_setoffset(uio, (u_int64_t)extents[i].startBlock * (u_int64_t)blksize);
+
+ result = cluster_read(evp, uio, VTOF(evp)->ff_size, IO_SYNC | IO_UNIT);
+
+#if HFS_XATTR_VERBOSE
+ printf("hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n",
+ iosize, extents[i].startBlock, extents[i].blockCount, result);
+#endif
+ if (result)
+ break;
+ attrsize -= iosize;
+ bufsize -= iosize;
+ }
+ uio_setresid(uio, bufsize);
+ uio_setoffset(uio, datasize);
+
+ hfs_unlock_truncate(VTOC(evp), HFS_LOCK_DEFAULT);
+ return (result);
+}
+
+/*
+ * Write an extent based attribute.
+ */
+static int
+write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents)
+{
+ vnode_t evp = hfsmp->hfs_attrdata_vp;
+ off_t filesize;
+ int bufsize;
+ int attrsize;
+ int64_t iosize;
+ int blksize;
+ int i;
+ int result = 0;
+
+ hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT);
+
+ bufsize = uio_resid(uio);
+ attrsize = (int) datasize;
+ blksize = (int) hfsmp->blockSize;
+ filesize = VTOF(evp)->ff_size;
+
+ /*
+ * Write the attribute data one extent at a time.
+ */
+ for (i = 0; (attrsize > 0) && (bufsize > 0) && (extents[i].startBlock != 0); ++i) {
+ iosize = extents[i].blockCount * blksize;
+ iosize = MIN(iosize, attrsize);
+ iosize = MIN(iosize, bufsize);
+ uio_setresid(uio, iosize);
+ uio_setoffset(uio, (u_int64_t)extents[i].startBlock * (u_int64_t)blksize);
+
+ result = cluster_write(evp, uio, filesize, filesize, filesize,
+ (off_t) 0, IO_SYNC | IO_UNIT);
+#if HFS_XATTR_VERBOSE
+ printf("hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n",
+ iosize, extents[i].startBlock, extents[i].blockCount, result);
+#endif
+ if (result)
+ break;
+ attrsize -= iosize;
+ bufsize -= iosize;
+ }
+ uio_setresid(uio, bufsize);
+ uio_setoffset(uio, datasize);
+
+ hfs_unlock_truncate(VTOC(evp), HFS_LOCK_DEFAULT);
+ return (result);
+}
+
+/*
+ * Allocate blocks for an extent based attribute.
+ */
+static int
+alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks)
+{
+ int blkcnt;
+ int startblk;
+ int lockflags;
+ int i;
+ int maxextents;
+ int result = 0;
+
+ startblk = hfsmp->hfs_metazone_end;
+ blkcnt = howmany(attrsize, hfsmp->blockSize);
+ if (blkcnt > (int)hfs_freeblks(hfsmp, 0)) {
+ return (ENOSPC);
+ }
+ *blocks = blkcnt;
+ maxextents = extentbufsize / sizeof(HFSPlusExtentDescriptor);
+
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
+
+ for (i = 0; (blkcnt > 0) && (i < maxextents); i++) {
+ /* Try allocating and see if we find something decent */
+ result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, 0,
+ &extents[i].startBlock, &extents[i].blockCount);
+ /*
+ * If we couldn't find anything, then re-try the allocation but allow
+ * journal flushes.
+ */
+ if (result == dskFulErr) {
+ result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, HFS_ALLOC_FLUSHTXN,
+ &extents[i].startBlock, &extents[i].blockCount);
+ }
+
+
+#if HFS_XATTR_VERBOSE
+ printf("hfs: alloc_attr_blks: BA blkcnt %d [%d, %d] (%d)\n",
+ blkcnt, extents[i].startBlock, extents[i].blockCount, result);
+#endif
+ if (result) {
+ extents[i].startBlock = 0;
+ extents[i].blockCount = 0;
+ break;
+ }
+ blkcnt -= extents[i].blockCount;
+ startblk = extents[i].startBlock + extents[i].blockCount;
+ }
+ /*
+ * If it didn't fit in the extents buffer then bail.
+ */
+ if (blkcnt) {
+ result = ENOSPC;
+
+#if HFS_XATTR_VERBOSE
+ printf("hfs: alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt);
+#endif
+ for (; i >= 0; i--) {
+ if ((blkcnt = extents[i].blockCount) != 0) {
+ (void) BlockDeallocate(hfsmp, extents[i].startBlock, blkcnt, 0);
+ extents[i].startBlock = 0;
+ extents[i].blockCount = 0;
+ }
+ }
+ }
+
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ return MacToVFSError(result);
+}
+
+/*
+ * Release blocks from an extent based attribute.
+ */
+static void
+free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents)
+{
+ vnode_t evp = hfsmp->hfs_attrdata_vp;
+ int remblks = blkcnt;
+ int lockflags;
+ int i;
+
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
+
+ for (i = 0; (remblks > 0) && (extents[i].blockCount != 0); i++) {
+ if (extents[i].blockCount > (u_int32_t)blkcnt) {
+#if HFS_XATTR_VERBOSE
+ printf("hfs: free_attr_blks: skipping bad extent [%d, %d]\n",
+ extents[i].startBlock, extents[i].blockCount);
+#endif
+ extents[i].blockCount = 0;
+ continue;
+ }
+ if (extents[i].startBlock == 0) {
+ break;
+ }
+ (void)BlockDeallocate(hfsmp, extents[i].startBlock, extents[i].blockCount, 0);
+ remblks -= extents[i].blockCount;
+ extents[i].startBlock = 0;
+ extents[i].blockCount = 0;
+
+#if HFS_XATTR_VERBOSE
+ printf("hfs: free_attr_blks: BlockDeallocate [%d, %d]\n",
+ extents[i].startBlock, extents[i].blockCount);
+#endif
+ /* Discard any resident pages for this block range. */
+ if (evp) {
+ off_t start, end;
+
+ start = (u_int64_t)extents[i].startBlock * (u_int64_t)hfsmp->blockSize;
+ end = start + (u_int64_t)extents[i].blockCount * (u_int64_t)hfsmp->blockSize;
+ (void) ubc_msync(hfsmp->hfs_attrdata_vp, start, end, &start, UBC_INVALIDATE);
+ }
+ }
+
+ hfs_systemfile_unlock(hfsmp, lockflags);
+}
+
+static int
+has_overflow_extents(HFSPlusForkData *forkdata)
+{
+ u_int32_t blocks;
+
+ if (forkdata->extents[7].blockCount == 0)
+ return (0);
+
+ blocks = forkdata->extents[0].blockCount +
+ forkdata->extents[1].blockCount +
+ forkdata->extents[2].blockCount +
+ forkdata->extents[3].blockCount +
+ forkdata->extents[4].blockCount +
+ forkdata->extents[5].blockCount +
+ forkdata->extents[6].blockCount +
+ forkdata->extents[7].blockCount;
+
+ return (forkdata->totalBlocks > blocks);
+}
+
+static int
+count_extent_blocks(int maxblks, HFSPlusExtentRecord extents)
+{
+ int blocks;
+ int i;
+
+ for (i = 0, blocks = 0; i < kHFSPlusExtentDensity; ++i) {
+ /* Ignore obvious bogus extents. */
+ if (extents[i].blockCount > (u_int32_t)maxblks)
+ continue;
+ if (extents[i].startBlock == 0 || extents[i].blockCount == 0)
+ break;
+ blocks += extents[i].blockCount;
+ }
+ return (blocks);
+}