+ /* Reserve some space in the catalog file. */
+ if (!skip_reserve && (error = cat_preflight(hfsmp, 2 * CAT_DELETE, NULL, 0))) {
+ goto out;
+ }
+
+ /* Purge any cached origin entries for a directory or file hard link. */
+ hfs_relorigin(cp, dcp->c_fileid);
+ if (dcp->c_fileid != dcp->c_cnid) {
+ hfs_relorigin(cp, dcp->c_cnid);
+ }
+
+ /* Delete the link record. */
+ if ((error = cat_deletelink(hfsmp, &cndesc))) {
+ goto out;
+ }
+
+ /* Update the parent directory. */
+ if (dcp->c_entries > 0) {
+ dcp->c_entries--;
+ }
+ if (cndesc.cd_flags & CD_ISDIR) {
+ DEC_FOLDERCOUNT(hfsmp, dcp->c_attr);
+ }
+ dcp->c_dirchangecnt++;
+ hfs_incr_gencount(dcp);
+ microtime(&tv);
+ dcp->c_ctime = tv.tv_sec;
+ dcp->c_mtime = tv.tv_sec;
+ (void ) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL);
+
+ /*
+ * If this is the last link then we need to process the inode.
+ * Otherwise we need to fix up the link chain.
+ */
+ --cp->c_linkcount;
+ if (cp->c_linkcount < 1) {
+ char delname[32];
+ struct cat_desc to_desc;
+ struct cat_desc from_desc;
+
+ /*
+ * If a file inode or directory inode is being deleted, rename
+ * it to an open deleted file. This ensures that deletion
+ * of inode and its corresponding extended attributes does
+ * not overflow the journal. This inode will be deleted
+ * either in hfs_vnop_inactive() or in hfs_remove_orphans().
+ * Note: a rename failure here is not fatal.
+ */
+ bzero(&from_desc, sizeof(from_desc));
+ bzero(&to_desc, sizeof(to_desc));
+ if (vnode_isdir(vp)) {
+ if (cp->c_entries != 0) {
+ panic("hfs_unlink: dir not empty (id %d, %d entries)", cp->c_fileid, cp->c_entries);
+ }
+ MAKE_DIRINODE_NAME(inodename, sizeof(inodename),
+ cp->c_attr.ca_linkref);
+ from_desc.cd_parentcnid = hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid;
+ from_desc.cd_flags = CD_ISDIR;
+ to_desc.cd_flags = CD_ISDIR;
+ } else {
+ MAKE_INODE_NAME(inodename, sizeof(inodename),
+ cp->c_attr.ca_linkref);
+ from_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
+ from_desc.cd_flags = 0;
+ to_desc.cd_flags = 0;
+ }
+ from_desc.cd_nameptr = (const u_int8_t *)inodename;
+ from_desc.cd_namelen = strlen(inodename);
+ from_desc.cd_cnid = cp->c_fileid;
+
+ MAKE_DELETED_NAME(delname, sizeof(delname), cp->c_fileid);
+ to_desc.cd_nameptr = (const u_int8_t *)delname;
+ to_desc.cd_namelen = strlen(delname);
+ to_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
+ to_desc.cd_cnid = cp->c_fileid;
+
+ error = cat_rename(hfsmp, &from_desc, &hfsmp->hfs_private_desc[FILE_HARDLINKS],
+ &to_desc, (struct cat_desc *)NULL);
+ if (error == 0) {
+ cp->c_flag |= C_DELETED;
+ cp->c_attr.ca_recflags &= ~kHFSHasLinkChainMask;
+ cp->c_attr.ca_firstlink = 0;
+ if (vnode_isdir(vp)) {
+ hfsmp->hfs_private_attr[DIR_HARDLINKS].ca_entries--;
+ DEC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[DIR_HARDLINKS]);
+
+ hfsmp->hfs_private_attr[FILE_HARDLINKS].ca_entries++;
+ INC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[FILE_HARDLINKS]);
+
+ (void)cat_update(hfsmp, &hfsmp->hfs_private_desc[DIR_HARDLINKS],
+ &hfsmp->hfs_private_attr[DIR_HARDLINKS], NULL, NULL);
+ (void)cat_update(hfsmp, &hfsmp->hfs_private_desc[FILE_HARDLINKS],
+ &hfsmp->hfs_private_attr[FILE_HARDLINKS], NULL, NULL);
+ }
+ } else {
+ error = 0; /* rename failure here is not fatal */
+ }
+ } else /* Still some links left */ {
+ cnid_t firstlink;
+
+ /*
+ * Update the start of the link chain.
+ * Note: Directory hard links store the first link in an attribute.
+ */
+ if (vnode_isdir(vp) &&
+ getfirstlink(hfsmp, cp->c_fileid, &firstlink) == 0 &&
+ firstlink == cndesc.cd_cnid) {
+ if (setfirstlink(hfsmp, cp->c_fileid, nextlinkid) == 0)
+ cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
+ } else if (vnode_isreg(vp) && cp->c_attr.ca_firstlink == cndesc.cd_cnid) {
+ cp->c_attr.ca_firstlink = nextlinkid;
+ }
+ /* Update previous link. */
+ if (prevlinkid) {
+ (void) cat_update_siblinglinks(hfsmp, prevlinkid, HFS_IGNORABLE_LINK, nextlinkid);
+ }
+ /* Update next link. */
+ if (nextlinkid) {
+ (void) cat_update_siblinglinks(hfsmp, nextlinkid, prevlinkid, HFS_IGNORABLE_LINK);
+ }
+
+ /*
+ * The call to cat_releasedesc below will only release the name buffer;
+ * it does not zero out the rest of the fields in the 'cat_desc' data structure.
+ *
+ * As a result, since there are still other links at this point, we need
+ * to make the current cnode descriptor point to the raw inode. If a path-based
+ * system call comes along first, it will replace the descriptor with a valid link
+ * ID. If a userland process already has a file descriptor open, then they will
+ * bypass that lookup, though. Replacing the descriptor CNID with the raw
+ * inode will force it to generate a new full path.
+ */
+ cp->c_cnid = cp->c_fileid;
+
+ }
+
+ /* Push new link count to disk. */
+ cp->c_ctime = tv.tv_sec;
+ (void) cat_update(hfsmp, &cp->c_desc, &cp->c_attr, NULL, NULL);
+
+ /* All done with the system files. */
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ lockflags = 0;
+
+ /* Update file system stats. */
+ hfs_volupdate(hfsmp, VOL_RMFILE, (dcp->c_cnid == kHFSRootFolderID));
+
+ /*
+ * All done with this cnode's descriptor...
+ *
+ * Note: all future catalog calls for this cnode may be
+ * by fileid only. This is OK for HFS (which doesn't have
+ * file thread records) since HFS doesn't support hard links.
+ */
+ cat_releasedesc(&cp->c_desc);
+
+out:
+ if (lockflags) {
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ }
+ if (started_tr) {
+ hfs_end_transaction(hfsmp);
+ }
+
+ dcp->c_flag &= ~C_DIR_MODIFICATION;
+ wakeup((caddr_t)&dcp->c_flag);
+
+ return (error);
+}
+
+
+/*
+ * Initialize the HFS+ private system directories.
+ *
+ * These directories are used to hold the inodes
+ * for file and directory hardlinks as well as
+ * open-unlinked files.
+ *
+ * If they don't yet exist they will get created.
+ *
+ * This call is assumed to be made during mount.
+ */
+void
+hfs_privatedir_init(struct hfsmount * hfsmp, enum privdirtype type)
+{
+ struct vnode * dvp = NULLVP;
+ struct cnode * dcp = NULL;
+ struct cat_desc *priv_descp;
+ struct cat_attr *priv_attrp;
+ struct FndrDirInfo * fndrinfo;
+ struct timeval tv;
+ int lockflags;
+ int trans = 0;
+ int error;
+
+ if (hfsmp->hfs_flags & HFS_STANDARD) {
+ return;
+ }
+
+ priv_descp = &hfsmp->hfs_private_desc[type];
+ priv_attrp = &hfsmp->hfs_private_attr[type];
+
+ /* Check if directory already exists. */
+ if (priv_descp->cd_cnid != 0) {
+ return;
+ }
+
+ priv_descp->cd_parentcnid = kRootDirID;
+ priv_descp->cd_nameptr = (const u_int8_t *)hfs_private_names[type];
+ priv_descp->cd_namelen = strlen((const char *)priv_descp->cd_nameptr);
+ priv_descp->cd_flags = CD_ISDIR | CD_DECOMPOSED;
+
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
+ error = cat_lookup(hfsmp, priv_descp, 0, 0, NULL, priv_attrp, NULL, NULL);
+ hfs_systemfile_unlock(hfsmp, lockflags);
+
+ if (error == 0) {
+ if (type == FILE_HARDLINKS) {
+ hfsmp->hfs_metadata_createdate = priv_attrp->ca_itime;
+ }
+ priv_descp->cd_cnid = priv_attrp->ca_fileid;
+ goto exit;
+ }
+
+ /* Directory is missing, if this is read-only then we're done. */
+ if (hfsmp->hfs_flags & HFS_READ_ONLY) {
+ goto exit;
+ }
+
+ /* Grab the root directory so we can update it later. */
+ if (hfs_vget(hfsmp, kRootDirID, &dvp, 0, 0) != 0) {
+ goto exit;
+ }
+ dcp = VTOC(dvp);
+
+ /* Setup the default attributes */
+ bzero(priv_attrp, sizeof(struct cat_attr));
+ priv_attrp->ca_flags = UF_IMMUTABLE | UF_HIDDEN;
+ priv_attrp->ca_mode = S_IFDIR;
+ if (type == DIR_HARDLINKS) {
+ priv_attrp->ca_mode |= S_ISVTX | S_IRUSR | S_IXUSR | S_IRGRP |
+ S_IXGRP | S_IROTH | S_IXOTH;
+ }
+ priv_attrp->ca_linkcount = 1;
+ priv_attrp->ca_itime = hfsmp->hfs_itime;
+ priv_attrp->ca_recflags = kHFSHasFolderCountMask;
+
+ fndrinfo = (struct FndrDirInfo *)&priv_attrp->ca_finderinfo;
+ fndrinfo->frLocation.v = SWAP_BE16(16384);
+ fndrinfo->frLocation.h = SWAP_BE16(16384);
+ fndrinfo->frFlags = SWAP_BE16(kIsInvisible + kNameLocked);
+
+ if (hfs_start_transaction(hfsmp) != 0) {
+ goto exit;
+ }
+ trans = 1;
+
+ /* Need the catalog and EA b-trees for CNID acquisition */
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
+
+ /* Make sure there's space in the Catalog file. */
+ if (cat_preflight(hfsmp, CAT_CREATE, NULL, 0) != 0) {
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ goto exit;
+ }
+
+ /* Get the CNID for use */
+ cnid_t new_id;
+ if ((error = cat_acquire_cnid(hfsmp, &new_id))) {
+ hfs_systemfile_unlock (hfsmp, lockflags);
+ goto exit;
+ }
+
+ /* Create the private directory on disk. */
+ error = cat_create(hfsmp, new_id, priv_descp, priv_attrp, NULL);
+ if (error == 0) {
+ priv_descp->cd_cnid = priv_attrp->ca_fileid;
+
+ /* Update the parent directory */
+ dcp->c_entries++;
+ INC_FOLDERCOUNT(hfsmp, dcp->c_attr);
+ dcp->c_dirchangecnt++;
+ hfs_incr_gencount(dcp);
+ microtime(&tv);
+ dcp->c_ctime = tv.tv_sec;
+ dcp->c_mtime = tv.tv_sec;
+ (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL);
+ }
+
+ hfs_systemfile_unlock(hfsmp, lockflags);
+
+ if (error) {
+ goto exit;
+ }
+ if (type == FILE_HARDLINKS) {
+ hfsmp->hfs_metadata_createdate = priv_attrp->ca_itime;
+ }
+ hfs_volupdate(hfsmp, VOL_MKDIR, 1);
+exit:
+ if (trans) {
+ hfs_end_transaction(hfsmp);
+ }
+ if (dvp) {
+ hfs_unlock(dcp);
+ vnode_put(dvp);
+ }
+ if ((error == 0) && (type == DIR_HARDLINKS)) {
+ hfs_xattr_init(hfsmp);
+ }
+}
+
+
+/*
+ * Lookup a hardlink link (from chain)
+ */
+int
+hfs_lookup_siblinglinks(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid, cnid_t *nextlinkid)
+{
+ int lockflags;
+ int error;
+
+ *prevlinkid = 0;
+ *nextlinkid = 0;
+
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
+
+ error = cat_lookup_siblinglinks(hfsmp, linkfileid, prevlinkid, nextlinkid);
+ if (error == ENOLINK) {
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
+
+ error = getfirstlink(hfsmp, linkfileid, nextlinkid);
+ }
+ hfs_systemfile_unlock(hfsmp, lockflags);
+