+ /* Make sure update occurs inside transaction */
+ cp->c_flag |= C_FORCEUPDATE;
+
+ if ((error == 0) && (ret = hfs_update(vp, TRUE)) != 0 && ret != EIO && ret != ENXIO) {
+ panic("hfs_vnop_link: error %d updating vp @ %p\n", ret, vp);
+ }
+
+out:
+ if (lockflags) {
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ }
+ if (intrans) {
+ hfs_end_transaction(hfsmp);
+ }
+
+ tdcp->c_flag &= ~C_DIR_MODIFICATION;
+ wakeup((caddr_t)&tdcp->c_flag);
+
+ if (fdcp) {
+ hfs_unlockfour(tdcp, cp, fdcp, NULL);
+ } else {
+ hfs_unlockpair(tdcp, cp);
+ }
+ if (fdvp) {
+ vnode_put(fdvp);
+ }
+ return (error);
+}
+
+
+/*
+ * Remove a link to a hardlink file/dir.
+ *
+ * Note: dvp and vp cnodes are already locked.
+ */
+int
+hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct componentname *cnp, int skip_reserve)
+{
+ struct cnode *cp;
+ struct cnode *dcp;
+ struct cat_desc cndesc;
+ struct timeval tv;
+ char inodename[32];
+ cnid_t prevlinkid;
+ cnid_t nextlinkid;
+ int lockflags = 0;
+ int started_tr;
+ int error;
+
+ if (hfsmp->hfs_flags & HFS_STANDARD) {
+ return (EPERM);
+ }
+ cp = VTOC(vp);
+ dcp = VTOC(dvp);
+
+ dcp->c_flag |= C_DIR_MODIFICATION;
+
+ /* Remove the entry from the namei cache: */
+ cache_purge(vp);
+
+ if ((error = hfs_start_transaction(hfsmp)) != 0) {
+ started_tr = 0;
+ goto out;
+ }
+ started_tr = 1;
+
+ /*
+ * Protect against a race with rename by using the component
+ * name passed in and parent id from dvp (instead of using
+ * the cp->c_desc which may have changed).
+ *
+ * Re-lookup the component name so we get the correct cnid
+ * for the name (as opposed to the c_cnid in the cnode which
+ * could have changed before the cnode was locked).
+ */
+ cndesc.cd_flags = vnode_isdir(vp) ? CD_ISDIR : 0;
+ cndesc.cd_encoding = cp->c_desc.cd_encoding;
+ cndesc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr;
+ cndesc.cd_namelen = cnp->cn_namelen;
+ cndesc.cd_parentcnid = dcp->c_fileid;
+ cndesc.cd_hint = dcp->c_childhint;
+
+ lockflags = SFL_CATALOG | SFL_ATTRIBUTE;
+ if (cndesc.cd_flags & CD_ISDIR) {
+ /* We'll be removing the alias resource allocation blocks. */
+ lockflags |= SFL_BITMAP;
+ }
+ lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
+
+ if ((error = cat_lookuplink(hfsmp, &cndesc, &cndesc.cd_cnid, &prevlinkid, &nextlinkid))) {
+ goto out;
+ }
+
+ /* 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++;
+ 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);
+ }
+ }
+
+ /* 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;
+ }