+ /* Update the catalog record on disk if either cnode was not
+ * found in the hash, or if a cnode was found and the cnode
+ * did not have the bit set previously.
+ */
+ retval = hfs_start_transaction(hfsmp);
+ if (retval) {
+ break;
+ }
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
+
+ /* Look up our catalog folder record */
+ retval = cat_idlookup(hfsmp, cnid, 0, 0, &desc, &attr, NULL);
+ if (retval) {
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ hfs_end_transaction(hfsmp);
+ break;
+ }
+
+ /* Update the bit in the catalog record */
+ attr.ca_recflags |= kHFSHasChildLinkMask;
+ retval = cat_update(hfsmp, &desc, &attr, NULL, NULL);
+ if (retval) {
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ hfs_end_transaction(hfsmp);
+ cat_releasedesc(&desc);
+ break;
+ }
+
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ hfs_end_transaction(hfsmp);
+
+ cnid = desc.cd_parentcnid;
+ cat_releasedesc(&desc);
+ }
+
+ return retval;
+}
+
+/* This function traverses the parent directory hierarchy from the given
+ * directory to one level below root directory and checks if any of its
+ * ancestors is -
+ * 1. A directory hard link.
+ * 2. The 'pointed at' directory.
+ * If any of these conditions fail or an internal error is encountered
+ * during look up of the catalog record, this function returns non-zero value.
+ */
+__private_extern__
+int
+cat_check_link_ancestry(struct hfsmount *hfsmp, cnid_t cnid, cnid_t pointed_at_cnid)
+{
+ HFSPlusCatalogKey *keyp;
+ BTreeIterator *ip;
+ FSBufferDescriptor btdata;
+ HFSPlusCatalogFolder folder;
+ FCB *fcb;
+ int invalid;
+ int result;
+
+ invalid = 0;
+ BDINIT(btdata, &folder);
+ MALLOC(ip, BTreeIterator *, sizeof(*ip), M_TEMP, M_WAITOK);
+ keyp = (HFSPlusCatalogKey *)&ip->key;
+ fcb = hfsmp->hfs_catalog_cp->c_datafork;
+
+ while (cnid != kHFSRootParentID) {
+ /* Check if the 'pointed at' directory is an ancestor */
+ if (pointed_at_cnid == cnid) {
+ invalid = 1;
+ break;
+ }
+ if ((result = getkey(hfsmp, cnid, (CatalogKey *)keyp))) {
+ printf("hfs: cat_check_link_ancestry: getkey failed id=%u, vol=%s\n", cnid, hfsmp->vcbVN);
+ invalid = 1; /* On errors, assume an invalid parent */
+ break;
+ }
+ if ((result = BTSearchRecord(fcb, ip, &btdata, NULL, NULL))) {
+ printf("hfs: cat_check_link_ancestry: cannot find id=%u, vol=%s\n", cnid, hfsmp->vcbVN);
+ invalid = 1; /* On errors, assume an invalid parent */
+ break;
+ }
+ /* Check if this ancestor is a directory hard link */
+ if (folder.flags & kHFSHasLinkChainMask) {
+ invalid = 1;
+ break;
+ }
+ cnid = keyp->parentID;
+ }
+ FREE(ip, M_TEMP);
+ return (invalid);
+}
+
+
+/*
+ * update_siblinglinks_callback - update a link's chain
+ */
+
+struct linkupdate_state {
+ cnid_t filelinkid;
+ cnid_t prevlinkid;
+ cnid_t nextlinkid;
+};
+
+static int
+update_siblinglinks_callback(__unused const CatalogKey *ckp, CatalogRecord *crp, struct linkupdate_state *state)
+{
+ HFSPlusCatalogFile *file;
+
+ if (crp->recordType != kHFSPlusFileRecord) {
+ printf("hfs: update_siblinglinks_callback: unexpected rec type %d\n", crp->recordType);
+ return (btNotFound);
+ }
+
+ file = (struct HFSPlusCatalogFile *)crp;
+ if (file->flags & kHFSHasLinkChainMask) {
+ if (state->prevlinkid != HFS_IGNORABLE_LINK) {
+ file->hl_prevLinkID = state->prevlinkid;
+ }
+ if (state->nextlinkid != HFS_IGNORABLE_LINK) {
+ file->hl_nextLinkID = state->nextlinkid;
+ }
+ } else {
+ printf("hfs: update_siblinglinks_callback: file %d isn't a chain\n", file->fileID);
+ }
+ return (0);
+}
+
+/*
+ * cat_update_siblinglinks - update a link's chain
+ */
+int
+cat_update_siblinglinks(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t prevlinkid, cnid_t nextlinkid)
+{
+ FCB * fcb;
+ BTreeIterator * iterator;
+ struct linkupdate_state state;
+ int result;
+
+ fcb = hfsmp->hfs_catalog_cp->c_datafork;
+ state.filelinkid = linkfileid;
+ state.prevlinkid = prevlinkid;
+ state.nextlinkid = nextlinkid;
+
+ /* Create an iterator for use by us temporarily */
+ MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
+ bzero(iterator, sizeof(*iterator));
+
+ result = getkey(hfsmp, linkfileid, (CatalogKey *)&iterator->key);
+ if (result == 0) {
+ result = BTUpdateRecord(fcb, iterator, (IterateCallBackProcPtr)update_siblinglinks_callback, &state);
+ (void) BTFlushPath(fcb);
+ } else {
+ printf("hfs: cat_update_siblinglinks: couldn't resolve cnid=%d, vol=%s\n", linkfileid, hfsmp->vcbVN);
+ }
+
+ FREE (iterator, M_TEMP);
+ return MacToVFSError(result);
+}
+
+/*
+ * cat_lookuplink - lookup a link by it's name
+ */
+int
+cat_lookuplink(struct hfsmount *hfsmp, struct cat_desc *descp, cnid_t *linkfileid, cnid_t *prevlinkid, cnid_t *nextlinkid)
+{
+ FCB * fcb;
+ BTreeIterator * iterator;
+ struct FSBufferDescriptor btdata;
+ struct HFSPlusCatalogFile file;
+ int result;
+
+ fcb = hfsmp->hfs_catalog_cp->c_datafork;
+
+ /* Create an iterator for use by us temporarily */
+ MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
+ bzero(iterator, sizeof(*iterator));
+
+ if ((result = buildkey(hfsmp, descp, (HFSPlusCatalogKey *)&iterator->key, 0))) {
+ goto exit;
+ }
+ BDINIT(btdata, &file);
+
+ if ((result = BTSearchRecord(fcb, iterator, &btdata, NULL, NULL))) {
+ goto exit;
+ }
+ if (file.recordType != kHFSPlusFileRecord) {
+ result = ENOENT;
+ goto exit;
+ }
+ *linkfileid = file.fileID;
+
+ if (file.flags & kHFSHasLinkChainMask) {
+ *prevlinkid = file.hl_prevLinkID;
+ *nextlinkid = file.hl_nextLinkID;
+ } else {
+ *prevlinkid = 0;
+ *nextlinkid = 0;
+ }
+exit:
+ FREE(iterator, M_TEMP);
+ return MacToVFSError(result);
+}
+
+
+/*
+ * cat_lookup_siblinglinks - lookup previous and next link ID for link using its cnid
+ */
+int
+cat_lookup_siblinglinks(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid, cnid_t *nextlinkid)
+{
+ FCB * fcb;
+ BTreeIterator * iterator;
+ struct FSBufferDescriptor btdata;
+ struct HFSPlusCatalogFile file;
+ int result;
+
+ fcb = hfsmp->hfs_catalog_cp->c_datafork;
+
+ /* Create an iterator for use by us temporarily */
+ MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
+ bzero(iterator, sizeof(*iterator));
+
+ if ((result = getkey(hfsmp, linkfileid, (CatalogKey *)&iterator->key))) {
+ goto exit;
+ }
+ BDINIT(btdata, &file);
+
+ if ((result = BTSearchRecord(fcb, iterator, &btdata, NULL, NULL))) {
+ goto exit;
+ }
+ /* The prev/next chain is only valid when kHFSHasLinkChainMask is set. */
+ if (file.flags & kHFSHasLinkChainMask) {
+ cnid_t parent;
+
+ parent = ((HFSPlusCatalogKey *)&iterator->key)->parentID;
+
+ /* directory inodes don't have a chain (its in an EA) */
+ if (parent == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
+ result = ENOLINK; /* signal to caller to get head of list */
+ } else if (parent == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid) {
+ *prevlinkid = 0;
+ *nextlinkid = file.hl_firstLinkID;
+ } else {
+ *prevlinkid = file.hl_prevLinkID;
+ *nextlinkid = file.hl_nextLinkID;
+ }
+ } else {
+ *prevlinkid = 0;
+ *nextlinkid = 0;
+ }
+exit:
+ FREE(iterator, M_TEMP);
+ return MacToVFSError(result);
+}
+
+
+/*
+ * cat_lookup_lastlink - find the last sibling link in the chain (no "next" ptr)
+ */
+int
+cat_lookup_lastlink(struct hfsmount *hfsmp, cnid_t linkfileid,
+ cnid_t *lastlink, struct cat_desc *cdesc)
+{
+ FCB * fcb;
+ BTreeIterator * iterator;
+ struct FSBufferDescriptor btdata;
+ struct HFSPlusCatalogFile file;
+ int result;
+ int itercount = 0;
+ int foundlast = 0;
+ cnid_t currentlink = linkfileid;
+
+ fcb = hfsmp->hfs_catalog_cp->c_datafork;
+
+ /* Create an iterator for use by us temporarily */
+ MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
+
+ while ((foundlast == 0) && (itercount < HFS_LINK_MAX )) {
+ itercount++;
+ bzero(iterator, sizeof(*iterator));
+
+ if ((result = getkey(hfsmp, currentlink, (CatalogKey *)&iterator->key))) {
+ goto exit;
+ }
+ BDINIT(btdata, &file);
+
+ if ((result = BTSearchRecord(fcb, iterator, &btdata, NULL, NULL))) {
+ goto exit;
+ }
+
+ /* The prev/next chain is only valid when kHFSHasLinkChainMask is set. */
+ if (file.flags & kHFSHasLinkChainMask) {
+ cnid_t parent;
+
+ parent = ((HFSPlusCatalogKey *)&iterator->key)->parentID;
+ /*
+ * The raw inode for a directory hardlink doesn't have a chain.
+ * Its link information lives in an EA.
+ */
+ if (parent == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
+ /* We don't iterate to find the oldest directory hardlink. */
+ result = ENOLINK;
+ goto exit;
+ }
+ else if (parent == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid) {
+ /* Raw inode for file hardlink (the base inode) */
+ currentlink = file.hl_firstLinkID;
+
+ /*
+ * One minor special-casing here is necessary.
+ * If our ID brought us to the raw hardlink inode, and it does
+ * not have any siblings, then it's an open-unlinked file, and we
+ * should not proceed any further.
+ */
+ if (currentlink == 0) {
+ result = ENOLINK;
+ goto exit;
+ }
+ }
+ else {
+ /* Otherwise, this item's parent is a legitimate directory in the namespace */
+ if (file.hl_nextLinkID == 0) {
+ /* If nextLinkID is 0, then we found the end; no more hardlinks */
+ foundlast = 1;
+ *lastlink = currentlink;
+ /*
+ * Since we had to construct a catalog key to do this lookup
+ * we still hold it in-hand. We might as well use it to build
+ * the descriptor that the caller asked for.
+ */
+ builddesc ((HFSPlusCatalogKey*)&iterator->key, currentlink, 0, 0, 0, cdesc);
+ break;
+ }
+
+ currentlink = file.hl_nextLinkID;
+ }
+ }
+ else {
+ /* Sorry, can't help you without a link chain */
+ result = ENOLINK;
+ goto exit;
+ }
+ }
+exit:
+ /* If we didn't find what we were looking for, zero out the args */
+ if (foundlast == 0) {
+ if (cdesc) {
+ bzero (cdesc, sizeof(struct cat_desc));
+ }
+ if (lastlink) {
+ *lastlink = 0;
+ }
+ }
+
+ FREE(iterator, M_TEMP);
+ return MacToVFSError(result);
+}
+
+
+/*
+ * cat_createlink - create a link in the catalog
+ *
+ * The following cat_attr fields are expected to be set:
+ * ca_linkref
+ * ca_itime
+ * ca_mode (S_IFREG)
+ * ca_recflags
+ * ca_flags
+ * ca_finderinfo (type and creator)
+ */
+int
+cat_createlink(struct hfsmount *hfsmp, struct cat_desc *descp, struct cat_attr *attrp,
+ cnid_t nextlinkid, cnid_t *linkfileid)
+{
+ FCB * fcb;
+ struct btobj * bto;
+ FSBufferDescriptor btdata;
+ HFSPlusForkData *rsrcforkp;
+ u_int32_t nextCNID;
+ u_int32_t datalen;
+ u_int32_t encoding;
+ int thread_inserted = 0;
+ int alias_allocated = 0;
+ int result = 0;
+ int std_hfs;
+
+ std_hfs = (hfsmp->hfs_flags & HFS_STANDARD);
+
+ fcb = hfsmp->hfs_catalog_cp->c_datafork;
+
+ /*
+ * Get the next CNID. We can change it since we hold the catalog lock.
+ */
+ nextCNID = hfsmp->vcbNxtCNID;
+ if (nextCNID == 0xFFFFFFFF) {
+ hfs_lock_mount (hfsmp);
+ hfsmp->vcbNxtCNID = kHFSFirstUserCatalogNodeID;
+ hfsmp->vcbAtrb |= kHFSCatalogNodeIDsReusedMask;
+ hfs_unlock_mount(hfsmp);
+ } else {
+ hfsmp->vcbNxtCNID++;
+ }
+ MarkVCBDirty(hfsmp);
+
+ /* Get space for iterator, key and data */
+ MALLOC(bto, struct btobj *, sizeof(struct btobj), M_TEMP, M_WAITOK);
+ bto->iterator.hint.nodeNum = 0;
+ rsrcforkp = &bto->data.hfsPlusFile.resourceFork;
+
+ result = buildkey(hfsmp, descp, &bto->key, 0);
+ if (result) {
+ printf("hfs: cat_createlink: err %d from buildkey\n", result);
+ goto exit;
+ }
+
+ /* This is our only chance to set the encoding (other than a rename). */
+ encoding = hfs_pickencoding(bto->key.nodeName.unicode, bto->key.nodeName.length);
+
+ /* Insert the thread record first. */
+ datalen = buildthread((void*)&bto->key, &bto->data, 0, 0);
+ btdata.bufferAddress = &bto->data;
+ btdata.itemSize = datalen;
+ btdata.itemCount = 1;
+
+ for (;;) {
+ buildthreadkey(nextCNID, 0, (CatalogKey *) &bto->iterator.key);
+
+ /*
+ * If the CNID wraparound bit is set, then we need to validate if there
+ * is a cnode in the hash already with this ID (even if it no longer exists
+ * on disk). If so, then just skip this ID and move on to the next one.
+ */
+ if (!std_hfs && (hfsmp->vcbAtrb & kHFSCatalogNodeIDsReusedMask)) {
+ /* Verify that the CNID does not already exist in the cnode hash... */
+ if (hfs_chash_snoop (hfsmp, nextCNID, 1, NULL, NULL) == 0) {
+ /* It was found in the cnode hash!*/
+ result = btExists;
+ }
+ }
+
+ if (result == 0) {
+ result = BTInsertRecord(fcb, &bto->iterator, &btdata, datalen);
+ }
+
+ if ((result == btExists) && (hfsmp->vcbAtrb & kHFSCatalogNodeIDsReusedMask)) {
+ /*
+ * Allow CNIDs on HFS Plus volumes to wrap around
+ */
+ if (++nextCNID < kHFSFirstUserCatalogNodeID) {
+ nextCNID = kHFSFirstUserCatalogNodeID;
+ }
+ continue;
+ }
+ if (result == 0) {
+ thread_inserted = 1;
+ }
+ break;
+ }
+ if (result)
+ goto exit;
+
+ /*
+ * CNID is now established. If we have wrapped then
+ * update the vcbNxtCNID.
+ */
+ if ((hfsmp->vcbAtrb & kHFSCatalogNodeIDsReusedMask)) {
+ hfsmp->vcbNxtCNID = nextCNID + 1;
+ if (hfsmp->vcbNxtCNID < kHFSFirstUserCatalogNodeID) {
+ hfsmp->vcbNxtCNID = kHFSFirstUserCatalogNodeID;
+ }
+ }
+
+ /*
+ * Now insert the link record.
+ */
+ buildrecord(attrp, nextCNID, 0, encoding, &bto->data, &datalen);
+
+ bto->data.hfsPlusFile.hl_prevLinkID = 0;
+ bto->data.hfsPlusFile.hl_nextLinkID = nextlinkid;
+ bto->data.hfsPlusFile.hl_linkReference = attrp->ca_linkref;
+
+ /* For directory hard links, create alias in resource fork */
+ if (descp->cd_flags & CD_ISDIR) {
+ if ((result = cat_makealias(hfsmp, attrp->ca_linkref, &bto->data.hfsPlusFile))) {
+ goto exit;
+ }
+ alias_allocated = 1;
+ }
+ btdata.bufferAddress = &bto->data;
+ btdata.itemSize = datalen;
+ btdata.itemCount = 1;
+
+ bcopy(&bto->key, &bto->iterator.key, sizeof(bto->key));
+
+ result = BTInsertRecord(fcb, &bto->iterator, &btdata, datalen);
+ if (result) {
+ if (result == btExists)
+ result = EEXIST;
+ goto exit;
+ }
+ if (linkfileid != NULL) {
+ *linkfileid = nextCNID;
+ }
+exit:
+ if (result) {
+ if (thread_inserted) {
+ printf("hfs: cat_createlink: BTInsertRecord err=%d, vol=%s\n", MacToVFSError(result), hfsmp->vcbVN);
+
+ buildthreadkey(nextCNID, 0, (CatalogKey *)&bto->iterator.key);
+ if (BTDeleteRecord(fcb, &bto->iterator)) {
+ printf("hfs: cat_createlink() failed to delete thread record on volume %s\n", hfsmp->vcbVN);
+ hfs_mark_volume_inconsistent(hfsmp);
+ }
+ }
+ if (alias_allocated && rsrcforkp->extents[0].startBlock != 0) {
+ (void) BlockDeallocate(hfsmp, rsrcforkp->extents[0].startBlock,
+ rsrcforkp->extents[0].blockCount, 0);
+ rsrcforkp->extents[0].startBlock = 0;
+ rsrcforkp->extents[0].blockCount = 0;
+ }
+ }
+ (void) BTFlushPath(fcb);
+ FREE(bto, M_TEMP);
+
+ return MacToVFSError(result);
+}
+
+/* Directory hard links are visible as aliases on pre-Leopard systems and
+ * as normal directories on Leopard or later. All directory hard link aliases
+ * have the same resource fork content except for the three uniquely
+ * identifying values that are updated in the resource fork data when the alias
+ * is created. The following array is the constant resource fork data used
+ * only for creating directory hard link aliases.
+ */
+static const char hfs_dirlink_alias_rsrc[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x9e, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x32,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x2b,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x9e, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x32, 0x00, 0x00, 0x61, 0x6c, 0x69, 0x73,
+ 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* Constants for directory hard link alias */
+enum {
+ /* Size of resource fork data array for directory hard link alias */
+ kHFSAliasSize = 0x1d0,
+
+ /* Volume type for ejectable devices like disk image */
+ kHFSAliasVolTypeEjectable = 0x5,
+
+ /* Offset for volume create date, in Mac OS local time */
+ kHFSAliasVolCreateDateOffset = 0x12a,
+
+ /* Offset for the type of volume */
+ kHFSAliasVolTypeOffset = 0x130,
+
+ /* Offset for folder ID of the parent directory of the directory inode */
+ kHFSAliasParentIDOffset = 0x132,
+
+ /* Offset for folder ID of the directory inode */
+ kHFSAliasTargetIDOffset = 0x176,
+};
+
+/* Create and write an alias that points at the directory represented by given
+ * inode number on the same volume. Directory hard links are visible as
+ * aliases in pre-Leopard systems and this function creates these aliases.
+ *
+ * Note: This code is very specific to creating alias for the purpose
+ * of directory hard links only, and should not be generalized.
+ */
+static int
+cat_makealias(struct hfsmount *hfsmp, u_int32_t inode_num, struct HFSPlusCatalogFile *crp)
+{
+ struct buf *bp;
+ daddr64_t blkno;
+ u_int32_t blkcount;
+ int blksize;
+ int sectorsize;
+ int result;
+ HFSPlusForkData *rsrcforkp;
+ char *alias;
+ uint32_t *valptr;
+
+ rsrcforkp = &(crp->resourceFork);
+
+ blksize = hfsmp->blockSize;
+ blkcount = howmany(kHFSAliasSize, blksize);
+ sectorsize = hfsmp->hfs_logical_block_size;
+ bzero(rsrcforkp, sizeof(HFSPlusForkData));
+
+ /* Allocate some disk space for the alias content. */
+ result = BlockAllocate(hfsmp, 0, blkcount, blkcount,
+ HFS_ALLOC_FORCECONTIG | HFS_ALLOC_METAZONE,
+ &rsrcforkp->extents[0].startBlock,
+ &rsrcforkp->extents[0].blockCount);
+ /* Did it fail with an out of space error? If so, re-try and allow journal flushing. */
+ if (result == dskFulErr ) {
+ result = BlockAllocate(hfsmp, 0, blkcount, blkcount,
+ HFS_ALLOC_FORCECONTIG | HFS_ALLOC_METAZONE | HFS_ALLOC_FLUSHTXN,
+ &rsrcforkp->extents[0].startBlock,
+ &rsrcforkp->extents[0].blockCount);
+ }
+ if (result) {
+ rsrcforkp->extents[0].startBlock = 0;
+ goto exit;
+ }
+
+ /* Acquire a buffer cache block for our block. */
+ blkno = ((u_int64_t)rsrcforkp->extents[0].startBlock * (u_int64_t)blksize) / sectorsize;
+ blkno += hfsmp->hfsPlusIOPosOffset / sectorsize;
+
+ bp = buf_getblk(hfsmp->hfs_devvp, blkno, roundup(kHFSAliasSize, hfsmp->hfs_logical_block_size), 0, 0, BLK_META);
+ if (hfsmp->jnl) {
+ journal_modify_block_start(hfsmp->jnl, bp);
+ }
+
+ /* Generate alias content */
+ alias = (char *)buf_dataptr(bp);
+ bzero(alias, buf_size(bp));
+ bcopy(hfs_dirlink_alias_rsrc, alias, kHFSAliasSize);
+
+ /* Set the volume create date, local time in Mac OS format */
+ valptr = (uint32_t *)(alias + kHFSAliasVolCreateDateOffset);
+ *valptr = OSSwapHostToBigInt32(hfsmp->localCreateDate);
+
+ /* If the file system is on a virtual device like disk image,
+ * update the volume type to be ejectable device.
+ */
+ if (hfsmp->hfs_flags & HFS_VIRTUAL_DEVICE) {
+ *(uint16_t *)(alias + kHFSAliasVolTypeOffset) =
+ OSSwapHostToBigInt16(kHFSAliasVolTypeEjectable);
+ }
+
+ /* Set id of the parent of the target directory */
+ valptr = (uint32_t *)(alias + kHFSAliasParentIDOffset);
+ *valptr = OSSwapHostToBigInt32(hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid);
+
+ /* Set id of the target directory */
+ valptr = (uint32_t *)(alias + kHFSAliasTargetIDOffset);
+ *valptr = OSSwapHostToBigInt32(inode_num);
+
+ /* Write alias content to disk. */
+ if (hfsmp->jnl) {
+ journal_modify_block_end(hfsmp->jnl, bp, NULL, NULL);
+ } else if ((result = buf_bwrite(bp))) {
+ goto exit;
+ }
+
+ /* Finish initializing the fork data. */
+ rsrcforkp->logicalSize = kHFSAliasSize;
+ rsrcforkp->totalBlocks = rsrcforkp->extents[0].blockCount;
+
+exit:
+ if (result && rsrcforkp->extents[0].startBlock != 0) {
+ (void) BlockDeallocate(hfsmp, rsrcforkp->extents[0].startBlock, rsrcforkp->extents[0].blockCount, 0);
+ rsrcforkp->extents[0].startBlock = 0;
+ rsrcforkp->extents[0].blockCount = 0;
+ rsrcforkp->logicalSize = 0;
+ rsrcforkp->totalBlocks = 0;
+ }
+ return (result);
+}
+
+/*
+ * cat_deletelink - delete a link from the catalog
+ */
+int
+cat_deletelink(struct hfsmount *hfsmp, struct cat_desc *descp)
+{
+ struct HFSPlusCatalogFile file;
+ struct cat_attr cattr;
+ uint32_t totalBlocks;
+ int i;
+ int result;
+
+ bzero(&file, sizeof (file));
+ bzero(&cattr, sizeof (cattr));
+ cattr.ca_fileid = descp->cd_cnid;
+
+ /* Directory links have alias content to remove. */
+ if (descp->cd_flags & CD_ISDIR) {
+ FCB * fcb;
+ BTreeIterator * iterator;
+ struct FSBufferDescriptor btdata;
+
+ fcb = hfsmp->hfs_catalog_cp->c_datafork;
+
+ /* Borrow the btcb iterator since we have an exclusive catalog lock. */
+ iterator = &((BTreeControlBlockPtr)(fcb->ff_sysfileinfo))->iterator;
+ iterator->hint.nodeNum = 0;
+
+ if ((result = buildkey(hfsmp, descp, (HFSPlusCatalogKey *)&iterator->key, 0))) {
+ goto exit;
+ }
+ BDINIT(btdata, &file);
+
+ if ((result = BTSearchRecord(fcb, iterator, &btdata, NULL, NULL))) {
+ goto exit;
+ }
+ }
+
+ result = cat_delete(hfsmp, descp, &cattr);
+
+ if ((result == 0) &&
+ (descp->cd_flags & CD_ISDIR) &&
+ (file.recordType == kHFSPlusFileRecord)) {
+
+ totalBlocks = file.resourceFork.totalBlocks;
+
+ for (i = 0; (i < 8) && (totalBlocks > 0); i++) {
+ if ((file.resourceFork.extents[i].blockCount == 0) &&
+ (file.resourceFork.extents[i].startBlock == 0)) {
+ break;
+ }
+
+ (void) BlockDeallocate(hfsmp,
+ file.resourceFork.extents[i].startBlock,
+ file.resourceFork.extents[i].blockCount, 0);
+
+ totalBlocks -= file.resourceFork.extents[i].blockCount;
+ file.resourceFork.extents[i].startBlock = 0;
+ file.resourceFork.extents[i].blockCount = 0;
+ }
+ }
+exit:
+ return (result);
+}
+
+
+/*
+ * Callback to collect directory entries.
+ * Called with readattr_state for each item in a directory.
+ */
+struct readattr_state {
+ struct hfsmount *hfsmp;
+ struct cat_entrylist *list;
+ cnid_t dir_cnid;
+ int stdhfs;
+ int error;
+};
+
+static int
+getentriesattr_callback(const CatalogKey *key, const CatalogRecord *rec,
+ struct readattr_state *state)
+{
+ struct cat_entrylist *list = state->list;
+ struct hfsmount *hfsmp = state->hfsmp;
+ struct cat_entry *cep;
+ cnid_t parentcnid;
+
+ if (list->realentries >= list->maxentries)
+ return (0); /* stop */
+
+ parentcnid = state->stdhfs ? key->hfs.parentID : key->hfsPlus.parentID;
+
+ switch(rec->recordType) {
+ case kHFSPlusFolderRecord:
+ case kHFSPlusFileRecord:
+#if CONFIG_HFS_STD
+ case kHFSFolderRecord:
+ case kHFSFileRecord:
+#endif
+ if (parentcnid != state->dir_cnid) {
+ state->error = ENOENT;
+ return (0); /* stop */