+
+
+/*
+ * 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);
+
+ return (error);
+}
+
+
+/* Find the oldest / last hardlink in the link chain */
+int
+hfs_lookup_lastlink (struct hfsmount *hfsmp, cnid_t linkfileid,
+ cnid_t *lastid, struct cat_desc *cdesc) {
+ int lockflags;
+ int error;
+
+ *lastid = 0;
+
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
+
+ error = cat_lookup_lastlink(hfsmp, linkfileid, lastid, cdesc);
+
+ hfs_systemfile_unlock(hfsmp, lockflags);
+
+ /*
+ * cat_lookup_lastlink will zero out the lastid/cdesc arguments as needed
+ * upon error cases.
+ */
+ return error;
+}
+
+
+/*
+ * Cache the origin of a directory or file hard link
+ *
+ * cnode must be lock on entry
+ */
+__private_extern__
+void
+hfs_savelinkorigin(cnode_t *cp, cnid_t parentcnid)
+{
+ linkorigin_t *origin = NULL;
+ thread_t thread = current_thread();
+ int count = 0;
+ int maxorigins = (S_ISDIR(cp->c_mode)) ? MAX_CACHED_ORIGINS : MAX_CACHED_FILE_ORIGINS;
+ /*
+ * Look for an existing origin first. If not found, create/steal one.
+ */
+ TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
+ ++count;
+ if (origin->lo_thread == thread) {
+ TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
+ break;
+ }
+ }
+ if (origin == NULL) {
+ /* Recycle the last (i.e., the oldest) if we have too many. */
+ if (count > maxorigins) {
+ origin = TAILQ_LAST(&cp->c_originlist, hfs_originhead);
+ TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
+ } else {
+ MALLOC(origin, linkorigin_t *, sizeof(linkorigin_t), M_TEMP, M_WAITOK);
+ }
+ origin->lo_thread = thread;
+ }
+ origin->lo_cnid = cp->c_cnid;
+ origin->lo_parentcnid = parentcnid;
+ TAILQ_INSERT_HEAD(&cp->c_originlist, origin, lo_link);
+}
+
+/*
+ * Release any cached origins for a directory or file hard link
+ *
+ * cnode must be lock on entry
+ */
+__private_extern__
+void
+hfs_relorigins(struct cnode *cp)
+{
+ linkorigin_t *origin, *prev;
+
+ TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev) {
+ FREE(origin, M_TEMP);
+ }
+ TAILQ_INIT(&cp->c_originlist);
+}
+
+/*
+ * Release a specific origin for a directory or file hard link
+ *
+ * cnode must be lock on entry
+ */
+__private_extern__
+void
+hfs_relorigin(struct cnode *cp, cnid_t parentcnid)
+{
+ linkorigin_t *origin, *prev;
+ thread_t thread = current_thread();
+
+ TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev) {
+ if ((origin->lo_thread == thread) ||
+ (origin->lo_parentcnid == parentcnid)) {
+ TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
+ FREE(origin, M_TEMP);
+ break;
+ }
+ }
+}
+
+/*
+ * Test if a directory or file hard link has a cached origin
+ *
+ * cnode must be lock on entry
+ */
+__private_extern__
+int
+hfs_haslinkorigin(cnode_t *cp)
+{
+ if (cp->c_flag & C_HARDLINK) {
+ linkorigin_t *origin;
+ thread_t thread = current_thread();
+
+ TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
+ if (origin->lo_thread == thread) {
+ return (1);
+ }
+ }
+ }
+ return (0);
+}
+
+/*
+ * Obtain the current parent cnid of a directory or file hard link
+ *
+ * cnode must be lock on entry
+ */
+__private_extern__
+cnid_t
+hfs_currentparent(cnode_t *cp)
+{
+ if (cp->c_flag & C_HARDLINK) {
+ linkorigin_t *origin;
+ thread_t thread = current_thread();
+
+ TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
+ if (origin->lo_thread == thread) {
+ return (origin->lo_parentcnid);
+ }
+ }
+ }
+ return (cp->c_parentcnid);
+}
+
+/*
+ * Obtain the current cnid of a directory or file hard link
+ *
+ * cnode must be lock on entry
+ */
+__private_extern__
+cnid_t
+hfs_currentcnid(cnode_t *cp)
+{
+ if (cp->c_flag & C_HARDLINK) {
+ linkorigin_t *origin;
+ thread_t thread = current_thread();
+
+ TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
+ if (origin->lo_thread == thread) {
+ return (origin->lo_cnid);
+ }
+ }
+ }
+ return (cp->c_cnid);
+}
+
+
+/*
+ * Set the first link attribute for a given file id.
+ *
+ * The attributes b-tree must already be locked.
+ * If journaling is enabled, a transaction must already be started.
+ */
+static int
+setfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t firstlink)
+{
+ FCB * btfile;
+ BTreeIterator * iterator;
+ FSBufferDescriptor btdata;
+ u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
+ HFSPlusAttrData *dataptr;
+ int result;
+ u_int16_t datasize;
+
+ if (hfsmp->hfs_attribute_cp == NULL) {
+ return (EPERM);
+ }
+ MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
+ bzero(iterator, sizeof(*iterator));
+
+ result = hfs_buildattrkey(fileid, FIRST_LINK_XATTR_NAME, (HFSPlusAttrKey *)&iterator->key);
+ if (result) {
+ goto out;
+ }
+ dataptr = (HFSPlusAttrData *)&attrdata[0];
+ dataptr->recordType = kHFSPlusAttrInlineData;
+ dataptr->reserved[0] = 0;
+ dataptr->reserved[1] = 0;
+
+ /*
+ * Since attrData is variable length, we calculate the size of
+ * attrData by subtracting the size of all other members of
+ * structure HFSPlusAttData from the size of attrdata.
+ */
+ (void)snprintf((char *)&dataptr->attrData[0],
+ sizeof(dataptr) - (4 * sizeof(uint32_t)),
+ "%lu", (unsigned long)firstlink);
+ dataptr->attrSize = 1 + strlen((char *)&dataptr->attrData[0]);
+
+ /* Calculate size of record rounded up to multiple of 2 bytes. */
+ datasize = sizeof(HFSPlusAttrData) - 2 + dataptr->attrSize + ((dataptr->attrSize & 1) ? 1 : 0);
+
+ btdata.bufferAddress = dataptr;
+ btdata.itemSize = datasize;
+ btdata.itemCount = 1;
+
+ btfile = hfsmp->hfs_attribute_cp->c_datafork;
+
+ /* Insert the attribute. */
+ result = BTInsertRecord(btfile, iterator, &btdata, datasize);
+ if (result == btExists) {
+ result = BTReplaceRecord(btfile, iterator, &btdata, datasize);
+ }
+ (void) BTFlushPath(btfile);
+out:
+ FREE(iterator, M_TEMP);
+
+ return MacToVFSError(result);
+}
+
+/*
+ * Get the first link attribute for a given file id.
+ *
+ * The attributes b-tree must already be locked.
+ */
+static int
+getfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t *firstlink)
+{
+ FCB * btfile;
+ BTreeIterator * iterator;
+ FSBufferDescriptor btdata;
+ u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
+ HFSPlusAttrData *dataptr;
+ int result;
+ u_int16_t datasize;
+
+ if (hfsmp->hfs_attribute_cp == NULL) {
+ return (EPERM);
+ }
+ MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
+ bzero(iterator, sizeof(*iterator));
+
+ result = hfs_buildattrkey(fileid, FIRST_LINK_XATTR_NAME, (HFSPlusAttrKey *)&iterator->key);
+ if (result)
+ goto out;
+
+ dataptr = (HFSPlusAttrData *)&attrdata[0];
+ datasize = sizeof(attrdata);
+
+ btdata.bufferAddress = dataptr;
+ btdata.itemSize = sizeof(attrdata);
+ btdata.itemCount = 1;
+
+ btfile = hfsmp->hfs_attribute_cp->c_datafork;
+
+ result = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL);
+ if (result)
+ goto out;
+
+ if (dataptr->attrSize < 3) {
+ result = ENOENT;
+ goto out;
+ }
+ *firstlink = strtoul((char*)&dataptr->attrData[0], NULL, 10);
+out:
+ FREE(iterator, M_TEMP);
+
+ return MacToVFSError(result);
+}
+