+ /*
+ * 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_updatelink(hfsmp, prevlinkid, HFS_IGNORABLE_LINK, nextlinkid);
+ }
+ /* Update next link. */
+ if (nextlinkid) {
+ (void) cat_updatelink(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));
+ /* The last link of a directory removed the inode. */
+ if (rm_priv_file) {
+ hfs_volupdate(hfsmp, VOL_RMFILE, 0);
+ }
+ /*
+ * 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);
+
+ HFS_KNOTE(dvp, NOTE_WRITE);
+ HFS_KNOTE(vp, NOTE_DELETE);
+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.
+ */
+__private_extern__
+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, 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) {
+ 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;
+
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, 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;
+ }
+
+ /* Create the private directory on disk. */
+ error = cat_create(hfsmp, 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++;
+ 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);
+