X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/db6096698656d32db7df630594bd9617ee54f828..22ba694c5857e62b5a553b1505dcf2e509177f28:/bsd/hfs/hfs_vnops.c diff --git a/bsd/hfs/hfs_vnops.c b/bsd/hfs/hfs_vnops.c index 1d0497a0f..414d6de78 100644 --- a/bsd/hfs/hfs_vnops.c +++ b/bsd/hfs/hfs_vnops.c @@ -50,6 +50,8 @@ #include #include #include +#include +#include #include #include @@ -97,6 +99,8 @@ int hfs_movedata (struct vnode *, struct vnode*); static int hfs_move_fork (struct filefork *srcfork, struct cnode *src, struct filefork *dstfork, struct cnode *dst); +decmpfs_cnode* hfs_lazy_init_decmpfs_cnode (struct cnode *cp); + #if FIFO static int hfsfifo_read(struct vnop_read_args *); static int hfsfifo_write(struct vnop_write_args *); @@ -132,7 +136,6 @@ int hfsspec_close(struct vnop_close_args *); - /***************************************************************************** * * Common Operations on vnodes @@ -257,7 +260,7 @@ hfs_ref_data_vp(struct cnode *cp, struct vnode **data_vp, int skiplock) /* maybe we should take the hfs cnode lock here, and if so, use the skiplock parameter to tell us not to */ - if (!skiplock) hfs_lock(cp, HFS_SHARED_LOCK); + if (!skiplock) hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); struct vnode *c_vp = cp->c_vp; if (c_vp) { /* we already have a data vnode */ @@ -296,9 +299,10 @@ hfs_ref_data_vp(struct cnode *cp, struct vnode **data_vp, int skiplock) /* * hfs_lazy_init_decmpfs_cnode(): returns the decmpfs_cnode for a cnode, - * allocating it if necessary; returns NULL if there was an allocation error + * allocating it if necessary; returns NULL if there was an allocation error. + * function is non-static so that it can be used from the FCNTL handler. */ -static decmpfs_cnode * +decmpfs_cnode * hfs_lazy_init_decmpfs_cnode(struct cnode *cp) { if (!cp->c_decmp) { @@ -445,6 +449,195 @@ hfs_hides_xattr(vfs_context_t ctx, struct cnode *cp, const char *name, int skipl } #endif /* HFS_COMPRESSION */ + +// +// This function gets the doc_tombstone structure for the +// current thread. If the thread doesn't have one, the +// structure is allocated. +// +static struct doc_tombstone * +get_uthread_doc_tombstone(void) +{ + struct uthread *ut; + ut = get_bsdthread_info(current_thread()); + + if (ut->t_tombstone == NULL) { + ut->t_tombstone = kalloc(sizeof(struct doc_tombstone)); + if (ut->t_tombstone) { + memset(ut->t_tombstone, 0, sizeof(struct doc_tombstone)); + } + } + + return ut->t_tombstone; +} + +// +// This routine clears out the current tombstone for the +// current thread and if necessary passes the doc-id of +// the tombstone on to the dst_cnode. +// +// If the doc-id transfers to dst_cnode, we also generate +// a doc-id changed fsevent. Unlike all the other fsevents, +// doc-id changed events can only be generated here in HFS +// where we have the necessary info. +// +static void +clear_tombstone_docid(struct doc_tombstone *ut, struct hfsmount *hfsmp, struct cnode *dst_cnode) +{ + uint32_t old_id = ut->t_lastop_document_id; + + ut->t_lastop_document_id = 0; + ut->t_lastop_parent = NULL; + ut->t_lastop_parent_vid = 0; + ut->t_lastop_filename[0] = '\0'; + + // + // If the lastop item is still the same and needs to be cleared, + // clear it. + // + if (dst_cnode && old_id && ut->t_lastop_item && vnode_vid(ut->t_lastop_item) == ut->t_lastop_item_vid) { + // + // clear the document_id from the file that used to have it. + // XXXdbg - we need to lock the other vnode and make sure to + // update it on disk. + // + struct cnode *ocp = VTOC(ut->t_lastop_item); + struct FndrExtendedFileInfo *ofip = (struct FndrExtendedFileInfo *)((char *)&ocp->c_attr.ca_finderinfo + 16); + + // printf("clearing doc-id from ino %d\n", ocp->c_desc.cd_cnid); + ofip->document_id = 0; + ocp->c_bsdflags &= ~UF_TRACKED; + ocp->c_flag |= C_MODIFIED | C_FORCEUPDATE; // mark it dirty + /* cat_update(hfsmp, &ocp->c_desc, &ocp->c_attr, NULL, NULL); */ + + } + +#if CONFIG_FSE + if (dst_cnode && old_id) { + struct FndrExtendedFileInfo *fip = (struct FndrExtendedFileInfo *)((char *)&dst_cnode->c_attr.ca_finderinfo + 16); + + add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), + FSE_ARG_DEV, hfsmp->hfs_raw_dev, + FSE_ARG_INO, (ino64_t)ut->t_lastop_fileid, // src inode # + FSE_ARG_INO, (ino64_t)dst_cnode->c_fileid, // dst inode # + FSE_ARG_INT32, (uint32_t)fip->document_id, + FSE_ARG_DONE); + } +#endif + // last, clear these now that we're all done + ut->t_lastop_item = NULL; + ut->t_lastop_fileid = 0; + ut->t_lastop_item_vid = 0; +} + + +// +// This function is used to filter out operations on temp +// filenames. We have to filter out operations on certain +// temp filenames to work-around questionable application +// behavior from apps like Autocad that perform unusual +// sequences of file system operations for a "safe save". +static int +is_ignorable_temp_name(const char *nameptr, int len) +{ + if (len == 0) { + len = strlen(nameptr); + } + + if ( strncmp(nameptr, "atmp", 4) == 0 + || (len > 4 && strncmp(nameptr+len-4, ".bak", 4) == 0) + || (len > 4 && strncmp(nameptr+len-4, ".tmp", 4) == 0)) { + return 1; + } + + return 0; +} + +// +// Decide if we need to save a tombstone or not. Normally we always +// save a tombstone - but if there already is one and the name we're +// given is an ignorable name, then we will not save a tombstone. +// +static int +should_save_docid_tombstone(struct doc_tombstone *ut, struct vnode *vp, struct componentname *cnp) +{ + if (cnp->cn_nameptr == NULL) { + return 0; + } + + if (ut->t_lastop_document_id && ut->t_lastop_item == vp && is_ignorable_temp_name(cnp->cn_nameptr, cnp->cn_namelen)) { + return 0; + } + + return 1; +} + + +// +// This function saves a tombstone for the given vnode and name. The +// tombstone represents the parent directory and name where the document +// used to live and the document-id of that file. This info is recorded +// in the doc_tombstone structure hanging off the uthread (which assumes +// that all safe-save operations happen on the same thread). +// +// If later on the same parent/name combo comes back into existence then +// we'll preserve the doc-id from this vnode onto the new vnode. +// +static void +save_tombstone(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct componentname *cnp, int for_unlink) +{ + struct cnode *cp = VTOC(vp); + struct doc_tombstone *ut; + ut = get_uthread_doc_tombstone(); + + if (for_unlink && vp->v_type == VREG && cp->c_linkcount > 1) { + // + // a regular file that is being unlinked and that is also + // hardlinked should not clear the UF_TRACKED state or + // mess with the tombstone because somewhere else in the + // file system the file is still alive. + // + return; + } + + ut->t_lastop_parent = dvp; + ut->t_lastop_parent_vid = vnode_vid(dvp); + ut->t_lastop_fileid = cp->c_fileid; + if (for_unlink) { + ut->t_lastop_item = NULL; + ut->t_lastop_item_vid = 0; + } else { + ut->t_lastop_item = vp; + ut->t_lastop_item_vid = vnode_vid(vp); + } + + strlcpy((char *)&ut->t_lastop_filename[0], cnp->cn_nameptr, sizeof(ut->t_lastop_filename)); + + struct FndrExtendedFileInfo *fip = (struct FndrExtendedFileInfo *)((char *)&cp->c_attr.ca_finderinfo + 16); + ut->t_lastop_document_id = fip->document_id; + + if (for_unlink) { + // clear this so it's never returned again + fip->document_id = 0; + cp->c_bsdflags &= ~UF_TRACKED; + + if (ut->t_lastop_document_id) { + (void) cat_update(hfsmp, &cp->c_desc, &cp->c_attr, NULL, NULL); + +#if CONFIG_FSE + // this event is more of a "pending-delete" + add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), + FSE_ARG_DEV, hfsmp->hfs_raw_dev, + FSE_ARG_INO, (ino64_t)cp->c_fileid, // src inode # + FSE_ARG_INO, (ino64_t)0, // dst inode # + FSE_ARG_INT32, ut->t_lastop_document_id, // document id + FSE_ARG_DONE); +#endif + } + } +} + + /* * Open a file/directory. */ @@ -516,7 +709,7 @@ hfs_vnop_open(struct vnop_open_args *ap) return (0); } - if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) + if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) return (error); #if QUOTA @@ -586,7 +779,7 @@ hfs_vnop_close(ap) int tooktrunclock = 0; int knownrefs = 0; - if ( hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK) != 0) + if ( hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) != 0) return (0); cp = VTOC(vp); hfsmp = VTOHFS(vp); @@ -613,11 +806,11 @@ hfs_vnop_close(ap) // release cnode lock; must acquire truncate lock BEFORE cnode lock hfs_unlock(cp); - hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK); + hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); tooktrunclock = 1; - if (hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK) != 0) { - hfs_unlock_truncate(cp, 0); + if (hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) != 0) { + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); // bail out if we can't re-acquire cnode lock return 0; } @@ -655,7 +848,7 @@ hfs_vnop_close(ap) } if (tooktrunclock){ - hfs_unlock_truncate(cp, 0); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); } hfs_unlock(cp); @@ -751,12 +944,12 @@ hfs_vnop_getattr(struct vnop_getattr_args *ap) */ if ((vap->va_active & VNODE_ATTR_TIMES) && (cp->c_touch_acctime || cp->c_touch_chgtime || cp->c_touch_modtime)) { - if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) + if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) return (error); hfs_touchtimes(hfsmp, cp); } else { - if ((error = hfs_lock(cp, HFS_SHARED_LOCK))) + if ((error = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) return (error); } @@ -1009,7 +1202,29 @@ hfs_vnop_getattr(struct vnop_getattr_args *ap) vap->va_data_size = data_size; vap->va_supported |= VNODE_ATTR_va_data_size; #endif - + + if (VATTR_IS_ACTIVE(vap, va_gen)) { + if (UBCINFOEXISTS(vp) && (vp->v_ubcinfo->ui_flags & UI_ISMAPPED)) { + /* While file is mmapped the generation count is invalid. + * However, bump the value so that the write-gen counter + * will be different once the file is unmapped (since, + * when unmapped the pageouts may not yet have happened) + */ + if (vp->v_ubcinfo->ui_flags & UI_MAPPEDWRITE) { + hfs_incr_gencount (cp); + } + vap->va_gen = 0; + } else { + vap->va_gen = hfs_get_gencount(cp); + } + + VATTR_SET_SUPPORTED(vap, va_gen); + } + if (VATTR_IS_ACTIVE(vap, va_document_id)) { + vap->va_document_id = hfs_get_document_id(cp); + VATTR_SET_SUPPORTED(vap, va_document_id); + } + /* Mark them all at once instead of individual VATTR_SET_SUPPORTED calls. */ vap->va_supported |= VNODE_ATTR_va_create_time | VNODE_ATTR_va_modify_time | VNODE_ATTR_va_change_time| VNODE_ATTR_va_backup_time | @@ -1042,7 +1257,8 @@ hfs_vnop_getattr(struct vnop_getattr_args *ap) if ((cp->c_flag & C_HARDLINK) && ((cp->c_desc.cd_namelen == 0) || (vap->va_linkid != cp->c_cnid))) { - /* If we have no name and our link ID is the raw inode number, then we may + /* + * If we have no name and our link ID is the raw inode number, then we may * have an open-unlinked file. Go to the next link in this case. */ if ((cp->c_desc.cd_namelen == 0) && (vap->va_linkid == cp->c_fileid)) { @@ -1133,16 +1349,14 @@ hfs_vnop_setattr(ap) error = decmpfs_update_attributes(vp, vap); if (error) return error; - +#endif // // if this is not a size-changing setattr and it is not just // an atime update, then check for a snapshot. // if (!VATTR_IS_ACTIVE(vap, va_data_size) && !(vap->va_active == VNODE_ATTR_va_access_time)) { - check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_MOD, NULL); + check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_MOD, NSPACE_REARM_NO_ARG); } -#endif - #if CONFIG_PROTECT if ((error = cp_handle_vnop(vp, CP_WRITE_ACCESS, 0)) != 0) { @@ -1157,6 +1371,26 @@ hfs_vnop_setattr(ap) return (EPERM); } + // + // Check if we'll need a document_id and if so, get it before we lock the + // the cnode to avoid any possible deadlock with the root vnode which has + // to get locked to get the document id + // + u_int32_t document_id=0; + if (VATTR_IS_ACTIVE(vap, va_flags) && (vap->va_flags & UF_TRACKED) && !(VTOC(vp)->c_bsdflags & UF_TRACKED)) { + struct FndrExtendedDirInfo *fip = (struct FndrExtendedDirInfo *)((char *)&(VTOC(vp)->c_attr.ca_finderinfo) + 16); + // + // If the document_id is not set, get a new one. It will be set + // on the file down below once we hold the cnode lock. + // + if (fip->document_id == 0) { + if (hfs_generate_document_id(hfsmp, &document_id) != 0) { + document_id = 0; + } + } + } + + /* * File size change request. * We are guaranteed that this is not a directory, and that @@ -1195,13 +1429,13 @@ hfs_vnop_setattr(ap) #endif /* Take truncate lock before taking cnode lock. */ - hfs_lock_truncate(VTOC(vp), HFS_EXCLUSIVE_LOCK); + hfs_lock_truncate(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); /* Perform the ubc_setsize before taking the cnode lock. */ ubc_setsize(vp, vap->va_data_size); - if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { - hfs_unlock_truncate(VTOC(vp), 0); + if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { + hfs_unlock_truncate(VTOC(vp), HFS_LOCK_DEFAULT); #if HFS_COMPRESSION decmpfs_unlock_compressed_data(dp, 1); #endif @@ -1211,7 +1445,7 @@ hfs_vnop_setattr(ap) error = hfs_truncate(vp, vap->va_data_size, vap->va_vaflags & 0xffff, 1, 0, ap->a_context); - hfs_unlock_truncate(cp, 0); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); #if HFS_COMPRESSION decmpfs_unlock_compressed_data(dp, 1); #endif @@ -1219,7 +1453,7 @@ hfs_vnop_setattr(ap) goto out; } if (cp == NULL) { - if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) + if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) return (error); cp = VTOC(vp); } @@ -1281,9 +1515,53 @@ hfs_vnop_setattr(ap) decmpfs_reset_state = 1; } #endif + if ((vap->va_flags & UF_TRACKED) && !(cp->c_bsdflags & UF_TRACKED)) { + struct FndrExtendedDirInfo *fip = (struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16); + + // + // we're marking this item UF_TRACKED. if the document_id is + // not set, get a new one and put it on the file. + // + if (fip->document_id == 0) { + if (document_id != 0) { + // printf("SETATTR: assigning doc-id %d to %s (ino %d)\n", document_id, vp->v_name, cp->c_desc.cd_cnid); + fip->document_id = (uint32_t)document_id; +#if CONFIG_FSE + add_fsevent(FSE_DOCID_CHANGED, ap->a_context, + FSE_ARG_DEV, hfsmp->hfs_raw_dev, + FSE_ARG_INO, (ino64_t)0, // src inode # + FSE_ARG_INO, (ino64_t)cp->c_fileid, // dst inode # + FSE_ARG_INT32, document_id, + FSE_ARG_DONE); +#endif + } else { + // printf("hfs: could not acquire a new document_id for %s (ino %d)\n", vp->v_name, cp->c_desc.cd_cnid); + } + } + + } else if (!(vap->va_flags & UF_TRACKED) && (cp->c_bsdflags & UF_TRACKED)) { + // + // UF_TRACKED is being cleared so clear the document_id + // + struct FndrExtendedDirInfo *fip = (struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16); + if (fip->document_id) { + // printf("SETATTR: clearing doc-id %d from %s (ino %d)\n", fip->document_id, vp->v_name, cp->c_desc.cd_cnid); +#if CONFIG_FSE + add_fsevent(FSE_DOCID_CHANGED, ap->a_context, + FSE_ARG_DEV, hfsmp->hfs_raw_dev, + FSE_ARG_INO, (ino64_t)cp->c_fileid, // src inode # + FSE_ARG_INO, (ino64_t)0, // dst inode # + FSE_ARG_INT32, fip->document_id, // document id + FSE_ARG_DONE); +#endif + fip->document_id = 0; + cp->c_bsdflags &= ~UF_TRACKED; + } + } cp->c_bsdflags = vap->va_flags; cp->c_touch_chgtime = TRUE; + /* * Mirror the UF_HIDDEN flag to the invisible bit of the Finder Info. @@ -1592,11 +1870,24 @@ good: /* - * The hfs_exchange routine swaps the fork data in two files by - * exchanging some of the information in the cnode. It is used - * to preserve the file ID when updating an existing file, in - * case the file is being tracked through its file ID. Typically - * its used after creating a new file during a safe-save. + * hfs_vnop_exchange: + * + * Inputs: + * 'from' vnode/cnode + * 'to' vnode/cnode + * options flag bits + * vfs_context + * + * Discussion: + * hfs_vnop_exchange is used to service the exchangedata(2) system call. + * Per the requirements of that system call, this function "swaps" some + * of the information that lives in one catalog record for some that + * lives in another. Note that not everything is swapped; in particular, + * the extent information stored in each cnode is kept local to that + * cnode. This allows existing file descriptor references to continue + * to operate on the same content, regardless of the location in the + * namespace that the file may have moved to. See inline comments + * in the function for more information. */ int hfs_vnop_exchange(ap) @@ -1627,12 +1918,12 @@ hfs_vnop_exchange(ap) cat_cookie_t cookie; time_t orig_from_ctime, orig_to_ctime; - /* The files must be on the same volume. */ - if (vnode_mount(from_vp) != vnode_mount(to_vp)) - return (EXDEV); - - if (from_vp == to_vp) - return (EINVAL); + /* + * VFS does the following checks: + * 1. Validate that both are files. + * 2. Validate that both are on the same mount. + * 3. Validate that they're not the same vnode. + */ orig_from_ctime = VTOC(from_vp)->c_ctime; orig_to_ctime = VTOC(to_vp)->c_ctime; @@ -1678,10 +1969,10 @@ hfs_vnop_exchange(ap) * Allow the rest of the codeflow to re-acquire the cnode locks in order. */ - hfs_lock_truncate (VTOC(from_vp), HFS_SHARED_LOCK); + hfs_lock_truncate (VTOC(from_vp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); - if ((error = hfs_lock(VTOC(from_vp), HFS_EXCLUSIVE_LOCK))) { - hfs_unlock_truncate (VTOC(from_vp), 0); + if ((error = hfs_lock(VTOC(from_vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { + hfs_unlock_truncate (VTOC(from_vp), HFS_LOCK_DEFAULT); return error; } @@ -1689,7 +1980,7 @@ hfs_vnop_exchange(ap) if (vnode_isinuse(from_vp, 1)) { error = EBUSY; hfs_unlock(VTOC(from_vp)); - hfs_unlock_truncate (VTOC(from_vp), 0); + hfs_unlock_truncate (VTOC(from_vp), HFS_LOCK_DEFAULT); return error; } @@ -1698,7 +1989,7 @@ hfs_vnop_exchange(ap) error = hfs_filedone (from_vp, ap->a_context); VTOC(from_vp)->c_flag &= ~C_SWAPINPROGRESS; hfs_unlock(VTOC(from_vp)); - hfs_unlock_truncate(VTOC(from_vp), 0); + hfs_unlock_truncate(VTOC(from_vp), HFS_LOCK_DEFAULT); if (error) { return error; @@ -1713,7 +2004,7 @@ hfs_vnop_exchange(ap) hfsmp = VTOHFS(from_vp); /* Resource forks cannot be exchanged. */ - if (VNODE_IS_RSRC(from_vp) || VNODE_IS_RSRC(to_vp)) { + if ( VNODE_IS_RSRC(from_vp) || VNODE_IS_RSRC(to_vp)) { error = EINVAL; goto exit; } @@ -1776,7 +2067,23 @@ hfs_vnop_exchange(ap) to_parid = to_cp->c_parentcnid; } - /* Do the exchange */ + /* + * ExchangeFileIDs swaps the extent information attached to two + * different file IDs. It also swaps the extent information that + * may live in the extents-overflow B-Tree. + * + * We do this in a transaction as this may require a lot of B-Tree nodes + * to do completely, particularly if one of the files in question + * has a lot of extents. + * + * For example, assume "file1" has fileID 50, and "file2" has fileID 52. + * For the on-disk records, which are assumed to be synced, we will + * first swap the resident inline-8 extents as part of the catalog records. + * Then we will swap any extents overflow records for each file. + * + * When this function is done, "file1" will have fileID 52, and "file2" will + * have fileID 50. + */ error = ExchangeFileIDs(hfsmp, from_nameptr, to_nameptr, from_parid, to_parid, from_cp->c_hint, to_cp->c_hint); hfs_systemfile_unlock(hfsmp, lockflags); @@ -1797,7 +2104,14 @@ hfs_vnop_exchange(ap) if (to_vp) cache_purge(to_vp); - /* Save a copy of from attributes before swapping. */ + /* Bump both source and destination write counts before any swaps. */ + { + hfs_incr_gencount (from_cp); + hfs_incr_gencount (to_cp); + } + + + /* Save a copy of "from" attributes before swapping. */ bcopy(&from_cp->c_desc, &tempdesc, sizeof(struct cat_desc)); bcopy(&from_cp->c_attr, &tempattr, sizeof(struct cat_attr)); @@ -1810,22 +2124,46 @@ hfs_vnop_exchange(ap) to_cp->c_flag &= ~(C_HARDLINK | C_HASXATTRS); /* - * Swap the descriptors and all non-fork related attributes. - * (except the modify date) + * Complete the in-memory portion of the copy. + * + * ExchangeFileIDs swaps the on-disk records involved. We complete the + * operation by swapping the in-memory contents of the two files here. + * We swap the cnode descriptors, which contain name, BSD attributes, + * timestamps, etc, about the file. + * + * NOTE: We do *NOT* swap the fileforks of the two cnodes. We have + * already swapped the on-disk extent information. As long as we swap the + * IDs, the in-line resident 8 extents that live in the filefork data + * structure will point to the right data for the new file ID if we leave + * them alone. + * + * As a result, any file descriptor that points to a particular + * vnode (even though it should change names), will continue + * to point to the same content. */ + + /* Copy the "to" -> "from" cnode */ bcopy(&to_cp->c_desc, &from_cp->c_desc, sizeof(struct cat_desc)); from_cp->c_hint = 0; /* * If 'to' was a hardlink, then we copied over its link ID/CNID/(namespace ID) - * when we bcopy'd the descriptor above. However, we need to be careful - * when setting up the fileID below, because we cannot assume that the - * file ID is the same as the CNID if either one was a hardlink. - * The file ID is stored in the c_attr as the ca_fileid. So it needs - * to be pulled explicitly; we cannot just use the CNID. + * when we bcopy'd the descriptor above. However, the cnode attributes + * are not bcopied. As a result, make sure to swap the file IDs of each item. + * + * Further, other hardlink attributes must be moved along in this swap: + * the linkcount, the linkref, and the firstlink all need to move + * along with the file IDs. See note below regarding the flags and + * what moves vs. what does not. + * + * For Reference: + * linkcount == total # of hardlinks. + * linkref == the indirect inode pointer. + * firstlink == the first hardlink in the chain (written to the raw inode). + * These three are tied to the fileID and must move along with the rest of the data. */ from_cp->c_fileid = to_cp->c_attr.ca_fileid; - + from_cp->c_itime = to_cp->c_itime; from_cp->c_btime = to_cp->c_btime; from_cp->c_atime = to_cp->c_atime; @@ -1860,10 +2198,11 @@ hfs_vnop_exchange(ap) * 3) swap the special flag bits to their destination */ from_cp->c_flag |= to_flag_special; - from_cp->c_attr.ca_recflags = to_cp->c_attr.ca_recflags; bcopy(to_cp->c_finderinfo, from_cp->c_finderinfo, 32); + + /* Copy the "from" -> "to" cnode */ bcopy(&tempdesc, &to_cp->c_desc, sizeof(struct cat_desc)); to_cp->c_hint = 0; /* @@ -1892,6 +2231,7 @@ hfs_vnop_exchange(ap) to_cp->c_attr.ca_recflags = tempattr.ca_recflags; bcopy(tempattr.ca_finderinfo, to_cp->c_finderinfo, 32); + /* Rehash the cnodes using their new file IDs */ hfs_chash_rehash(hfsmp, from_cp, to_cp); @@ -1943,6 +2283,16 @@ hfs_vnop_mmap(struct vnop_mmap_args *ap) if (ap->a_fflags & PROT_WRITE) { check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_WRITE_OP, NULL); + + /* even though we're manipulating a cnode field here, we're only monotonically increasing + * the generation counter. The vnode can't be recycled (because we hold a FD in order to cause the + * map to happen). So it's safe to do this without holding the cnode lock. The caller's only + * requirement is that the number has been changed. + */ + struct cnode *cp = VTOC(vp); + if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { + hfs_incr_gencount(cp); + } } } @@ -2296,18 +2646,18 @@ hfs_fsync(struct vnode *vp, int waitfor, int fullsync, struct proc *p) } } else if (UBCINFOEXISTS(vp)) { hfs_unlock(cp); - hfs_lock_truncate(cp, HFS_SHARED_LOCK); + hfs_lock_truncate(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); took_trunc_lock = 1; if (fp->ff_unallocblocks != 0) { - hfs_unlock_truncate(cp, 0); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); - hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK); + hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); } /* Don't hold cnode lock when calling into cluster layer. */ (void) cluster_push(vp, waitdata ? IO_SYNC : 0); - hfs_lock(cp, HFS_FORCE_LOCK); + hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); } /* * When MNT_WAIT is requested and the zero fill timeout @@ -2332,10 +2682,10 @@ hfs_fsync(struct vnode *vp, int waitfor, int fullsync, struct proc *p) if (!took_trunc_lock || (cp->c_truncatelockowner == HFS_SHARED_OWNER)) { hfs_unlock(cp); if (took_trunc_lock) { - hfs_unlock_truncate(cp, 0); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); } - hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK); - hfs_lock(cp, HFS_FORCE_LOCK); + hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); + hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); took_trunc_lock = 1; } while ((invalid_range = TAILQ_FIRST(&fp->ff_invalidranges))) { @@ -2353,19 +2703,19 @@ hfs_fsync(struct vnode *vp, int waitfor, int fullsync, struct proc *p) (void) cluster_write(vp, (struct uio *) 0, fp->ff_size, end + 1, start, (off_t)0, IO_HEADZEROFILL | IO_NOZERODIRTY | IO_NOCACHE); - hfs_lock(cp, HFS_FORCE_LOCK); + hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); cp->c_flag |= C_MODIFIED; } hfs_unlock(cp); (void) cluster_push(vp, waitdata ? IO_SYNC : 0); - hfs_lock(cp, HFS_FORCE_LOCK); + hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); } cp->c_flag &= ~C_ZFWANTSYNC; cp->c_zftimeout = 0; } datasync: if (took_trunc_lock) { - hfs_unlock_truncate(cp, 0); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); took_trunc_lock = 0; } /* @@ -2617,6 +2967,32 @@ hfs_vnop_rmdir(ap) hfs_unlockpair (dcp, cp); return ENOENT; } + + // + // if the item is tracked but doesn't have a document_id, assign one and generate an fsevent for it + // + if ((cp->c_bsdflags & UF_TRACKED) && ((struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16))->document_id == 0) { + uint32_t newid; + + hfs_unlockpair(dcp, cp); + + if (hfs_generate_document_id(VTOHFS(vp), &newid) == 0) { + hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK); + ((struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16))->document_id = newid; +#if CONFIG_FSE + add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), + FSE_ARG_DEV, VTOHFS(vp)->hfs_raw_dev, + FSE_ARG_INO, (ino64_t)0, // src inode # + FSE_ARG_INO, (ino64_t)cp->c_fileid, // dst inode # + FSE_ARG_INT32, newid, + FSE_ARG_DONE); +#endif + } else { + // XXXdbg - couldn't get a new docid... what to do? can't really fail the rm... + hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK); + } + } + error = hfs_removedir(dvp, vp, ap->a_cnp, 0, 0); hfs_unlockpair(dcp, cp); @@ -2784,12 +3160,34 @@ hfs_removedir(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, } error = cat_delete(hfsmp, &desc, &cp->c_attr); - if (error == 0) { + + if (!error) { + // + // if skip_reserve == 1 then we're being called from hfs_vnop_rename() and thus + // we don't need to touch the document_id as it's handled by the rename code. + // otherwise it's a normal remove and we need to save the document id in the + // per thread struct and clear it from the cnode. + // + struct doc_tombstone *ut; + ut = get_uthread_doc_tombstone(); + if (!skip_reserve && (cp->c_bsdflags & UF_TRACKED) && should_save_docid_tombstone(ut, vp, cnp)) { + + if (ut->t_lastop_document_id) { + clear_tombstone_docid(ut, hfsmp, NULL); + } + save_tombstone(hfsmp, dvp, vp, cnp, 1); + + } + /* The parent lost a child */ if (dcp->c_entries > 0) dcp->c_entries--; DEC_FOLDERCOUNT(hfsmp, dcp->c_attr); dcp->c_dirchangecnt++; + { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)dcp->c_finderinfo + 16); + extinfo->write_gen_counter = OSSwapHostToBigInt32(OSSwapBigToHostInt32(extinfo->write_gen_counter) + 1); + } dcp->c_touch_chgtime = TRUE; dcp->c_touch_modtime = TRUE; hfs_touchtimes(hfsmp, cp); @@ -2842,15 +3240,16 @@ hfs_vnop_remove(ap) struct cnode *cp; struct vnode *rvp = NULL; int error=0, recycle_rsrc=0; - time_t orig_ctime; + int recycle_vnode = 0; uint32_t rsrc_vid = 0; + time_t orig_ctime; if (dvp == vp) { return (EINVAL); } orig_ctime = VTOC(vp)->c_ctime; - if ( (!vnode_isnamedstream(vp)) && ((ap->a_flags & VNODE_REMOVE_SKIP_NAMESPACE_EVENT) == 0)) { + if (!vnode_isnamedstream(vp) && ((ap->a_flags & VNODE_REMOVE_SKIP_NAMESPACE_EVENT) == 0)) { error = check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_DELETE_OP, NULL); if (error) { // XXXdbg - decide on a policy for handling namespace handler failures! @@ -2863,15 +3262,39 @@ hfs_vnop_remove(ap) relock: - hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK); + hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); if ((error = hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK))) { - hfs_unlock_truncate(cp, 0); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); if (rvp) { vnode_put (rvp); } return (error); } + // + // if the item is tracked but doesn't have a document_id, assign one and generate an fsevent for it + // + if ((cp->c_bsdflags & UF_TRACKED) && ((struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16))->document_id == 0) { + uint32_t newid; + + hfs_unlockpair(dcp, cp); + + if (hfs_generate_document_id(VTOHFS(vp), &newid) == 0) { + hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK); + ((struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16))->document_id = newid; +#if CONFIG_FSE + add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), + FSE_ARG_DEV, VTOHFS(vp)->hfs_raw_dev, + FSE_ARG_INO, (ino64_t)0, // src inode # + FSE_ARG_INO, (ino64_t)cp->c_fileid, // dst inode # + FSE_ARG_INT32, newid, + FSE_ARG_DONE); +#endif + } else { + // XXXdbg - couldn't get a new docid... what to do? can't really fail the rm... + hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK); + } + } /* * Lazily respond to determining if there is a valid resource fork @@ -2888,17 +3311,15 @@ relock: * steps if 'vp' is a directory. */ - if ((vp->v_type == VLNK) || (vp->v_type == VREG)) { if ((cp->c_rsrc_vp) && (rvp == NULL)) { /* We need to acquire the rsrc vnode */ rvp = cp->c_rsrc_vp; rsrc_vid = vnode_vid (rvp); - + /* Unlock everything to acquire iocount on the rsrc vnode */ - hfs_unlock_truncate (cp, 0); + hfs_unlock_truncate (cp, HFS_LOCK_DEFAULT); hfs_unlockpair (dcp, cp); - /* Use the vid to maintain identity on rvp */ if (vnode_getwithvid(rvp, rsrc_vid)) { /* @@ -2942,10 +3363,21 @@ relock: * If the cnode was instead marked C_NOEXISTS, then there wouldn't be any * more work. */ - if ((error == 0) && (rvp)) { - recycle_rsrc = 1; + if (error == 0) { + if (rvp) { + recycle_rsrc = 1; + } + /* + * If the target was actually removed from the catalog schedule it for + * full reclamation/inactivation. We hold an iocount on it so it should just + * get marked with MARKTERM + */ + if (cp->c_flag & C_NOEXISTS) { + recycle_vnode = 1; + } } + /* * Drop the truncate lock before unlocking the cnode * (which can potentially perform a vnode_put and @@ -2953,14 +3385,17 @@ relock: * truncate lock) */ rm_done: - hfs_unlock_truncate(cp, 0); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); hfs_unlockpair(dcp, cp); if (recycle_rsrc) { /* inactive or reclaim on rvp will clean up the blocks from the rsrc fork */ vnode_recycle(rvp); } - + if (recycle_vnode) { + vnode_recycle (vp); + } + if (rvp) { /* drop iocount on rsrc fork, was obtained at beginning of fxn */ vnode_put(rvp); @@ -3053,7 +3488,7 @@ hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, return (EPERM); } - /* + /* * If removing a symlink, then we need to ensure that the * data blocks for the symlink are not still in-flight or pending. * If so, we will unlink the symlink here, making its blocks @@ -3328,6 +3763,10 @@ hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, DEC_FOLDERCOUNT(hfsmp, dcp->c_attr); } dcp->c_dirchangecnt++; + { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)dcp->c_finderinfo + 16); + extinfo->write_gen_counter = OSSwapHostToBigInt32(OSSwapBigToHostInt32(extinfo->write_gen_counter) + 1); + } dcp->c_ctime = tv.tv_sec; dcp->c_mtime = tv.tv_sec; (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL); @@ -3378,7 +3817,7 @@ hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, /* Look up the resource fork first, if necessary */ if (temp_rsrc_fork) { - error = cat_lookup (hfsmp, &desc, 1, (struct cat_desc*) NULL, + error = cat_lookup (hfsmp, &desc, 1, 0, (struct cat_desc*) NULL, (struct cat_attr*) NULL, &temp_rsrc_fork->ff_data, NULL); if (error) { FREE_ZONE (temp_rsrc_fork, sizeof(struct filefork), M_HFSFORK); @@ -3400,8 +3839,8 @@ hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, error = cat_delete(hfsmp, &desc, &cp->c_attr); if (error && error != ENXIO && error != ENOENT) { - printf("hfs_removefile: deleting file %s (%d), err: %d\n", - cp->c_desc.cd_nameptr, cp->c_attr.ca_fileid, error); + printf("hfs_removefile: deleting file %s (id=%d) vol=%s err=%d\n", + cp->c_desc.cd_nameptr, cp->c_attr.ca_fileid, hfsmp->vcbVN, error); } if (error == 0) { @@ -3409,6 +3848,10 @@ hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, if (dcp->c_entries > 0) dcp->c_entries--; dcp->c_dirchangecnt++; + { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)dcp->c_finderinfo + 16); + extinfo->write_gen_counter = OSSwapHostToBigInt32(OSSwapBigToHostInt32(extinfo->write_gen_counter) + 1); + } dcp->c_ctime = tv.tv_sec; dcp->c_mtime = tv.tv_sec; (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL); @@ -3502,6 +3945,24 @@ hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, } + // + // if skip_reserve == 1 then we're being called from hfs_vnop_rename() and thus + // we don't need to touch the document_id as it's handled by the rename code. + // otherwise it's a normal remove and we need to save the document id in the + // per thread struct and clear it from the cnode. + // + struct doc_tombstone *ut; + ut = get_uthread_doc_tombstone(); + if (!error && !skip_reserve && (cp->c_bsdflags & UF_TRACKED) && should_save_docid_tombstone(ut, vp, cnp)) { + + if (ut->t_lastop_document_id) { + clear_tombstone_docid(ut, hfsmp, NULL); + } + save_tombstone(hfsmp, dvp, vp, cnp, 1); + + } + + /* * All done with this cnode's descriptor... * @@ -3606,7 +4067,7 @@ hfs_vnop_rename(ap) * resource fork vnode (and only if necessary). We don't care if the * source has a resource fork vnode or not. */ - struct vnode *tvp_rsrc = NULLVP; + struct vnode *tvp_rsrc = NULLVP; uint32_t tvp_rsrc_vid = 0; struct componentname *tcnp = ap->a_tcnp; struct componentname *fcnp = ap->a_fcnp; @@ -3629,6 +4090,8 @@ hfs_vnop_rename(ap) time_t orig_from_ctime, orig_to_ctime; int emit_rename = 1; int emit_delete = 1; + int is_tracked = 0; + int unlocked; orig_from_ctime = VTOC(fvp)->c_ctime; if (tvp && VTOC(tvp)) { @@ -3649,7 +4112,7 @@ hfs_vnop_rename(ap) * We may not necessarily emit a RENAME event */ emit_delete = 0; - if ((error = hfs_lock(VTOC(fvp), HFS_SHARED_LOCK))) { + if ((error = hfs_lock(VTOC(fvp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { return error; } /* Check to see if the item is a hardlink or not */ @@ -3679,6 +4142,13 @@ hfs_vnop_rename(ap) } } if (emit_rename) { + /* c_bsdflags should only be assessed while holding the cnode lock. + * This is not done consistently throughout the code and can result + * in race. This will be fixed via rdar://12181064 + */ + if (VTOC(fvp)->c_bsdflags & UF_TRACKED) { + is_tracked = 1; + } check_for_tracked_file(fvp, orig_from_ctime, NAMESPACE_HANDLER_RENAME_OP, NULL); } @@ -3687,19 +4157,20 @@ hfs_vnop_rename(ap) check_for_tracked_file(tvp, orig_to_ctime, NAMESPACE_HANDLER_DELETE_OP, NULL); } } - + retry: /* When tvp exists, take the truncate lock for hfs_removefile(). */ if (tvp && (vnode_isreg(tvp) || vnode_islnk(tvp))) { - hfs_lock_truncate(VTOC(tvp), HFS_EXCLUSIVE_LOCK); + hfs_lock_truncate(VTOC(tvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); took_trunc_lock = 1; } +relock: error = hfs_lockfour(VTOC(fdvp), VTOC(fvp), VTOC(tdvp), tvp ? VTOC(tvp) : NULL, HFS_EXCLUSIVE_LOCK, &error_cnode); if (error) { if (took_trunc_lock) { - hfs_unlock_truncate(VTOC(tvp), 0); + hfs_unlock_truncate(VTOC(tvp), HFS_LOCK_DEFAULT); took_trunc_lock = 0; } @@ -3728,6 +4199,10 @@ retry: goto retry; } + if (emit_rename && is_tracked) { + resolve_nspace_item(fvp, NAMESPACE_HANDLER_RENAME_FAILED_OP | NAMESPACE_HANDLER_TRACK_EVENT); + } + return (error); } @@ -3736,6 +4211,75 @@ retry: tdcp = VTOC(tdvp); tcp = tvp ? VTOC(tvp) : NULL; + // + // if the item is tracked but doesn't have a document_id, assign one and generate an fsevent for it + // + unlocked = 0; + if ((fcp->c_bsdflags & UF_TRACKED) && ((struct FndrExtendedDirInfo *)((char *)&fcp->c_attr.ca_finderinfo + 16))->document_id == 0) { + uint32_t newid; + + hfs_unlockfour(VTOC(fdvp), VTOC(fvp), VTOC(tdvp), tvp ? VTOC(tvp) : NULL); + unlocked = 1; + + if (hfs_generate_document_id(hfsmp, &newid) == 0) { + hfs_lock(fcp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); + ((struct FndrExtendedDirInfo *)((char *)&fcp->c_attr.ca_finderinfo + 16))->document_id = newid; +#if CONFIG_FSE + add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), + FSE_ARG_DEV, hfsmp->hfs_raw_dev, + FSE_ARG_INO, (ino64_t)0, // src inode # + FSE_ARG_INO, (ino64_t)fcp->c_fileid, // dst inode # + FSE_ARG_INT32, newid, + FSE_ARG_DONE); +#endif + hfs_unlock(fcp); + } else { + // XXXdbg - couldn't get a new docid... what to do? can't really fail the rename... + } + + // + // check if we're going to need to fix tcp as well. if we aren't, go back relock + // everything. otherwise continue on and fix up tcp as well before relocking. + // + if (tcp == NULL || !(tcp->c_bsdflags & UF_TRACKED) || ((struct FndrExtendedDirInfo *)((char *)&tcp->c_attr.ca_finderinfo + 16))->document_id != 0) { + goto relock; + } + } + + // + // same thing for tcp if it's set + // + if (tcp && (tcp->c_bsdflags & UF_TRACKED) && ((struct FndrExtendedDirInfo *)((char *)&tcp->c_attr.ca_finderinfo + 16))->document_id == 0) { + uint32_t newid; + + if (!unlocked) { + hfs_unlockfour(VTOC(fdvp), VTOC(fvp), VTOC(tdvp), tvp ? VTOC(tvp) : NULL); + unlocked = 1; + } + + if (hfs_generate_document_id(hfsmp, &newid) == 0) { + hfs_lock(tcp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); + ((struct FndrExtendedDirInfo *)((char *)&tcp->c_attr.ca_finderinfo + 16))->document_id = newid; +#if CONFIG_FSE + add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), + FSE_ARG_DEV, hfsmp->hfs_raw_dev, + FSE_ARG_INO, (ino64_t)0, // src inode # + FSE_ARG_INO, (ino64_t)tcp->c_fileid, // dst inode # + FSE_ARG_INT32, newid, + FSE_ARG_DONE); +#endif + hfs_unlock(tcp); + } else { + // XXXdbg - couldn't get a new docid... what to do? can't really fail the rename... + } + + // go back up and relock everything. next time through the if statement won't be true + // and we'll skip over this block of code. + goto relock; + } + + + /* * Acquire iocounts on the destination's resource fork vnode * if necessary. If dst/src are files and the dst has a resource @@ -3753,7 +4297,7 @@ retry: /* Unlock everything to acquire iocount on this rsrc vnode */ if (took_trunc_lock) { - hfs_unlock_truncate (VTOC(tvp), 0); + hfs_unlock_truncate (VTOC(tvp), HFS_LOCK_DEFAULT); took_trunc_lock = 0; } hfs_unlockfour(fdcp, fcp, tdcp, tcp); @@ -3767,6 +4311,8 @@ retry: } } + + /* Ensure we didn't race src or dst parent directories with rmdir. */ if (fdcp->c_flag & (C_NOEXISTS | C_DELETED)) { error = ENOENT; @@ -3795,7 +4341,7 @@ retry: // never existed in the first place. // if (took_trunc_lock) { - hfs_unlock_truncate(VTOC(tvp), 0); + hfs_unlock_truncate(VTOC(tvp), HFS_LOCK_DEFAULT); took_trunc_lock = 0; } error = 0; @@ -3984,7 +4530,7 @@ retry: lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK); - if (cat_lookup(hfsmp, &tmpdesc, 0, NULL, NULL, NULL, &real_cnid) != 0) { + if (cat_lookup(hfsmp, &tmpdesc, 0, 0, NULL, NULL, NULL, &real_cnid) != 0) { hfs_systemfile_unlock(hfsmp, lockflags); goto out; } @@ -4032,6 +4578,57 @@ retry: * capable of clearing out unused blocks for an open-unlinked file or dir. */ if (tvp) { + // + // if the destination has a document id, we need to preserve it + // + if (fvp != tvp) { + uint32_t document_id; + struct FndrExtendedDirInfo *ffip = (struct FndrExtendedDirInfo *)((char *)&fcp->c_attr.ca_finderinfo + 16); + struct FndrExtendedDirInfo *tfip = (struct FndrExtendedDirInfo *)((char *)&tcp->c_attr.ca_finderinfo + 16); + + if (ffip->document_id && tfip->document_id) { + // both documents are tracked. only save a tombstone from tcp and do nothing else. + save_tombstone(hfsmp, tdvp, tvp, tcnp, 0); + } else { + struct doc_tombstone *ut; + ut = get_uthread_doc_tombstone(); + + document_id = tfip->document_id; + tfip->document_id = 0; + + if (document_id != 0) { + // clear UF_TRACKED as well since tcp is now no longer tracked + tcp->c_bsdflags &= ~UF_TRACKED; + (void) cat_update(hfsmp, &tcp->c_desc, &tcp->c_attr, NULL, NULL); + } + + if (ffip->document_id == 0 && document_id != 0) { + // printf("RENAME: preserving doc-id %d onto %s (from ino %d, to ino %d)\n", document_id, tcp->c_desc.cd_nameptr, tcp->c_desc.cd_cnid, fcp->c_desc.cd_cnid); + fcp->c_bsdflags |= UF_TRACKED; + ffip->document_id = document_id; + + (void) cat_update(hfsmp, &fcp->c_desc, &fcp->c_attr, NULL, NULL); +#if CONFIG_FSE + add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), + FSE_ARG_DEV, hfsmp->hfs_raw_dev, + FSE_ARG_INO, (ino64_t)tcp->c_fileid, // src inode # + FSE_ARG_INO, (ino64_t)fcp->c_fileid, // dst inode # + FSE_ARG_INT32, (uint32_t)ffip->document_id, + FSE_ARG_DONE); +#endif + } else if ((fcp->c_bsdflags & UF_TRACKED) && should_save_docid_tombstone(ut, fvp, fcnp)) { + + if (ut->t_lastop_document_id) { + clear_tombstone_docid(ut, hfsmp, NULL); + } + save_tombstone(hfsmp, fdvp, fvp, fcnp, 0); + + //printf("RENAME: (dest-exists): saving tombstone doc-id %lld @ %s (ino %d)\n", + // ut->t_lastop_document_id, ut->t_lastop_filename, fcp->c_desc.cd_cnid); + } + } + } + /* * When fvp matches tvp they could be case variants * or matching hard links. @@ -4134,6 +4731,47 @@ retry: * as quickly as possible. */ vnode_recycle(tvp); + } else { + struct doc_tombstone *ut; + ut = get_uthread_doc_tombstone(); + + // + // There is nothing at the destination. If the file being renamed is + // tracked, save a "tombstone" of the document_id. If the file is + // not a tracked file, then see if it needs to inherit a tombstone. + // + // NOTE: we do not save a tombstone if the file being renamed begins + // with "atmp" which is done to work-around AutoCad's bizarre + // 5-step un-safe save behavior + // + if (fcp->c_bsdflags & UF_TRACKED) { + if (should_save_docid_tombstone(ut, fvp, fcnp)) { + save_tombstone(hfsmp, fdvp, fvp, fcnp, 0); + + //printf("RENAME: (no dest): saving tombstone doc-id %lld @ %s (ino %d)\n", + // ut->t_lastop_document_id, ut->t_lastop_filename, fcp->c_desc.cd_cnid); + } else { + // intentionally do nothing + } + } else if ( ut->t_lastop_document_id != 0 + && tdvp == ut->t_lastop_parent + && vnode_vid(tdvp) == ut->t_lastop_parent_vid + && strcmp((char *)ut->t_lastop_filename, (char *)tcnp->cn_nameptr) == 0) { + + //printf("RENAME: %s (ino %d) inheriting doc-id %lld\n", tcnp->cn_nameptr, fcp->c_desc.cd_cnid, ut->t_lastop_document_id); + struct FndrExtendedFileInfo *fip = (struct FndrExtendedFileInfo *)((char *)&fcp->c_attr.ca_finderinfo + 16); + fcp->c_bsdflags |= UF_TRACKED; + fip->document_id = ut->t_lastop_document_id; + cat_update(hfsmp, &fcp->c_desc, &fcp->c_attr, NULL, NULL); + + clear_tombstone_docid(ut, hfsmp, fcp); // will send the docid-changed fsevent + + } else if (ut->t_lastop_document_id && should_save_docid_tombstone(ut, fvp, fcnp) && should_save_docid_tombstone(ut, tvp, tcnp)) { + // no match, clear the tombstone + //printf("RENAME: clearing the tombstone %lld @ %s\n", ut->t_lastop_document_id, ut->t_lastop_filename); + clear_tombstone_docid(ut, hfsmp, NULL); + } + } skip_rm: /* @@ -4205,6 +4843,10 @@ skip_rm: } tdcp->c_entries++; tdcp->c_dirchangecnt++; + { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)tdcp->c_finderinfo + 16); + extinfo->write_gen_counter = OSSwapHostToBigInt32(OSSwapBigToHostInt32(extinfo->write_gen_counter) + 1); + } if (fdcp->c_entries > 0) fdcp->c_entries--; fdcp->c_dirchangecnt++; @@ -4214,6 +4856,11 @@ skip_rm: fdcp->c_flag |= C_FORCEUPDATE; // XXXdbg - force it out! (void) hfs_update(fdvp, 0); } + { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)fdcp->c_finderinfo + 16); + extinfo->write_gen_counter = OSSwapHostToBigInt32(OSSwapBigToHostInt32(extinfo->write_gen_counter) + 1); + } + tdcp->c_childhint = out_desc.cd_hint; /* Cache directory's location */ tdcp->c_touch_chgtime = TRUE; tdcp->c_touch_modtime = TRUE; @@ -4224,7 +4871,7 @@ skip_rm: /* Update the vnode's name now that the rename has completed. */ vnode_update_identity(fvp, tdvp, tcnp->cn_nameptr, tcnp->cn_namelen, tcnp->cn_hash, (VNODE_UPDATE_PARENT | VNODE_UPDATE_NAME)); - + /* * At this point, we may have a resource fork vnode attached to the * 'from' vnode. If it exists, we will want to update its name, because @@ -4251,7 +4898,7 @@ skip_rm: * 4) update the vnode's vid */ vnode_update_identity (fcp->c_rsrc_vp, fvp, rsrc_path, len, 0, (VNODE_UPDATE_NAME | VNODE_UPDATE_CACHE)); - + /* Free the memory associated with the resource fork's name */ FREE_ZONE (rsrc_path, MAXPATHLEN, M_NAMEI); } @@ -4271,21 +4918,29 @@ out: } if (took_trunc_lock) { - hfs_unlock_truncate(VTOC(tvp), 0); + hfs_unlock_truncate(VTOC(tvp), HFS_LOCK_DEFAULT); } hfs_unlockfour(fdcp, fcp, tdcp, tcp); - /* Now vnode_put the resource fork vnode if necessary */ + /* Now vnode_put the resource forks vnodes if necessary */ if (tvp_rsrc) { vnode_put(tvp_rsrc); - tvp_rsrc = NULL; + tvp_rsrc = NULL; } /* After tvp is removed the only acceptable error is EIO */ if (error && tvp_deleted) error = EIO; + if (emit_rename && is_tracked) { + if (error) { + resolve_nspace_item(fvp, NAMESPACE_HANDLER_RENAME_FAILED_OP | NAMESPACE_HANDLER_TRACK_EVENT); + } else { + resolve_nspace_item(fvp, NAMESPACE_HANDLER_RENAME_SUCCESS_OP | NAMESPACE_HANDLER_TRACK_EVENT); + } + } + return (error); } @@ -4342,7 +4997,7 @@ hfs_vnop_symlink(struct vnop_symlink_args *ap) goto out; } vp = *vpp; - if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { + if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { goto out; } cp = VTOC(vp); @@ -4381,17 +5036,17 @@ hfs_vnop_symlink(struct vnop_symlink_args *ap) /* hfs_removefile() requires holding the truncate lock */ hfs_unlock(cp); - hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK); - hfs_lock(cp, HFS_FORCE_LOCK); + hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); + hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); if (hfs_start_transaction(hfsmp) != 0) { started_tr = 0; - hfs_unlock_truncate(cp, TRUE); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); goto out; } (void) hfs_removefile(dvp, vp, ap->a_cnp, 0, 0, 0, NULL, 0); - hfs_unlock_truncate(cp, 0); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); goto out; } @@ -4477,7 +5132,7 @@ typedef union { * * In fact, the offset used by HFS is essentially an index (26 bits) * with a tag (6 bits). The tag is for associating the next request - * with the current request. This enables us to have multiple threads + * with the current request. This enables us to have multiple threads * reading the directory while the directory is also being modified. * * Each tag/index pair is tied to a unique directory hint. The hint @@ -4541,7 +5196,7 @@ hfs_vnop_readdir(ap) hfsmp = VTOHFS(vp); /* Note that the dirhint calls require an exclusive lock. */ - if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) + if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) return (error); /* Pick up cnid hint (if any). */ @@ -4775,7 +5430,7 @@ hfs_vnop_readlink(ap) if (!vnode_islnk(vp)) return (EINVAL); - if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) + if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) return (error); cp = VTOC(vp); fp = VTOF(vp); @@ -4852,18 +5507,28 @@ hfs_vnop_pathconf(ap) vfs_context_t a_context; } */ *ap; { + + int std_hfs = (VTOHFS(ap->a_vp)->hfs_flags & HFS_STANDARD); switch (ap->a_name) { case _PC_LINK_MAX: - if (VTOHFS(ap->a_vp)->hfs_flags & HFS_STANDARD) - *ap->a_retval = 1; - else + if (std_hfs == 0){ *ap->a_retval = HFS_LINK_MAX; + } +#if CONFIG_HFS_STD + else { + *ap->a_retval = 1; + } +#endif break; case _PC_NAME_MAX: - if (VTOHFS(ap->a_vp)->hfs_flags & HFS_STANDARD) - *ap->a_retval = kHFSMaxFileNameChars; /* 31 */ - else + if (std_hfs == 0) { *ap->a_retval = kHFSPlusMaxFileNameChars; /* 255 */ + } +#if CONFIG_HFS_STD + else { + *ap->a_retval = kHFSMaxFileNameChars; /* 31 */ + } +#endif break; case _PC_PATH_MAX: *ap->a_retval = PATH_MAX; /* 1024 */ @@ -4878,10 +5543,14 @@ hfs_vnop_pathconf(ap) *ap->a_retval = 200112; /* _POSIX_NO_TRUNC */ break; case _PC_NAME_CHARS_MAX: - if (VTOHFS(ap->a_vp)->hfs_flags & HFS_STANDARD) - *ap->a_retval = kHFSMaxFileNameChars; /* 31 */ - else + if (std_hfs == 0) { *ap->a_retval = kHFSPlusMaxFileNameChars; /* 255 */ + } +#if CONFIG_HFS_STD + else { + *ap->a_retval = kHFSMaxFileNameChars; /* 31 */ + } +#endif break; case _PC_CASE_SENSITIVE: if (VTOHFS(ap->a_vp)->hfs_flags & HFS_CASE_SENSITIVE) @@ -4893,10 +5562,15 @@ hfs_vnop_pathconf(ap) *ap->a_retval = 1; break; case _PC_FILESIZEBITS: - if (VTOHFS(ap->a_vp)->hfs_flags & HFS_STANDARD) + /* number of bits to store max file size */ + if (std_hfs == 0) { + *ap->a_retval = 64; + } +#if CONFIG_HFS_STD + else { *ap->a_retval = 32; - else - *ap->a_retval = 64; /* number of bits to store max file size */ + } +#endif break; case _PC_XATTR_SIZE_BITS: /* Number of bits to store maximum extended attribute size */ @@ -4930,6 +5604,7 @@ hfs_update(struct vnode *vp, __unused int waitfor) struct hfsmount *hfsmp; int lockflags; int error; + uint32_t tstate = 0; p = current_proc(); hfsmp = VTOHFS(vp); @@ -4945,7 +5620,21 @@ hfs_update(struct vnode *vp, __unused int waitfor) cp->c_touch_modtime = 0; return (0); } - + if (kdebug_enable) { + if (cp->c_touch_acctime) + tstate |= DBG_HFS_UPDATE_ACCTIME; + if (cp->c_touch_modtime) + tstate |= DBG_HFS_UPDATE_MODTIME; + if (cp->c_touch_chgtime) + tstate |= DBG_HFS_UPDATE_CHGTIME; + + if (cp->c_flag & C_MODIFIED) + tstate |= DBG_HFS_UPDATE_MODIFIED; + if (cp->c_flag & C_FORCEUPDATE) + tstate |= DBG_HFS_UPDATE_FORCE; + if (cp->c_flag & C_NEEDS_DATEADDED) + tstate |= DBG_HFS_UPDATE_DATEADDED; + } hfs_touchtimes(hfsmp, cp); /* Nothing to update. */ @@ -4978,7 +5667,11 @@ hfs_update(struct vnode *vp, __unused int waitfor) return (0); } + KERNEL_DEBUG_CONSTANT(0x3018000 | DBG_FUNC_START, vp, tstate, 0, 0, 0); + if ((error = hfs_start_transaction(hfsmp)) != 0) { + + KERNEL_DEBUG_CONSTANT(0x3018000 | DBG_FUNC_END, vp, tstate, error, -1, 0); return error; } @@ -5039,6 +5732,18 @@ hfs_update(struct vnode *vp, __unused int waitfor) rsrcfork.cf_size = rsrcfork.cf_blocks * HFSTOVCB(hfsmp)->blockSize; rsrcforkp = &rsrcfork; } + if (kdebug_enable) { + long dbg_parms[NUMPARMS]; + int dbg_namelen; + + dbg_namelen = NUMPARMS * sizeof(long); + vn_getpath(vp, (char *)dbg_parms, &dbg_namelen); + + if (dbg_namelen < (int)sizeof(dbg_parms)) + memset((char *)dbg_parms + dbg_namelen, 0, sizeof(dbg_parms) - dbg_namelen); + + kdebug_lookup_gen_events(dbg_parms, dbg_namelen, (void *)vp, TRUE); + } /* * Lock the Catalog b-tree file. @@ -5055,6 +5760,8 @@ hfs_update(struct vnode *vp, __unused int waitfor) hfs_end_transaction(hfsmp); + KERNEL_DEBUG_CONSTANT(0x3018000 | DBG_FUNC_END, vp, tstate, error, 0, 0); + return (error); } @@ -5080,18 +5787,19 @@ hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, int newvnode_flags = 0; u_int32_t gnv_flags = 0; int protectable_target = 0; + int nocache = 0; #if CONFIG_PROTECT struct cprotect *entry = NULL; - uint32_t cp_class = 0; + int32_t cp_class = -1; if (VATTR_IS_ACTIVE(vap, va_dataprotect_class)) { - cp_class = vap->va_dataprotect_class; + cp_class = (int32_t)vap->va_dataprotect_class; } int protected_mount = 0; #endif - if ((error = hfs_lock(VTOC(dvp), HFS_EXCLUSIVE_LOCK))) + if ((error = hfs_lock(VTOC(dvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) return (error); /* set the cnode pointer only after successfully acquiring lock */ @@ -5143,9 +5851,11 @@ hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, } else { attr.ca_itime = tv.tv_sec; } +#if CONFIG_HFS_STD if ((hfsmp->hfs_flags & HFS_STANDARD) && gTimeZone.tz_dsttime) { attr.ca_itime += 3600; /* Same as what hfs_update does */ } +#endif attr.ca_atime = attr.ca_ctime = attr.ca_mtime = attr.ca_itime; attr.ca_atimeondisk = attr.ca_atime; if (VATTR_IS_ACTIVE(vap, va_flags)) { @@ -5181,6 +5891,8 @@ hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, */ if ((protected_mount) && (protectable_target)) { attr.ca_recflags |= kHFSHasAttributesMask; + /* delay entering in the namecache */ + nocache = 1; } #endif @@ -5191,6 +5903,9 @@ hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, */ hfs_write_dateadded (&attr, attr.ca_atime); + /* Initialize the gen counter to 1 */ + hfs_write_gencount(&attr, (uint32_t)1); + attr.ca_uid = vap->va_uid; attr.ca_gid = vap->va_gid; VATTR_SET_SUPPORTED(vap, va_mode); @@ -5232,13 +5947,15 @@ hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, #if CONFIG_PROTECT /* * To preserve file creation atomicity with regards to the content protection EA, - * we must create the file in the catalog and then write out the EA in the same - * transaction. Pre-flight any operations that we can (such as allocating/preparing - * the buffer, wrapping the keys) before we start the txn and take the requisite - * b-tree locks. We pass '0' as the fileid because we do not know it yet. + * we must create the file in the catalog and then write out its EA in the same + * transaction. + * + * We only denote the target class in this EA; key generation is not completed + * until the file has been inserted into the catalog and will be done + * in a separate transaction. */ if ((protected_mount) && (protectable_target)) { - error = cp_entry_create_keys (&entry, dcp, hfsmp, cp_class, 0, attr.ca_mode); + error = cp_setup_newentry(hfsmp, dcp, cp_class, attr.ca_mode, &entry); if (error) { goto exit; } @@ -5254,21 +5971,36 @@ hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, // to check that any fileID it wants to use does not have orphaned // attributes in it. lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK); + cnid_t new_id; /* Reserve some space in the Catalog file. */ if ((error = cat_preflight(hfsmp, CAT_CREATE, NULL, 0))) { hfs_systemfile_unlock(hfsmp, lockflags); goto exit; } - error = cat_create(hfsmp, &in_desc, &attr, &out_desc); + + if ((error = cat_acquire_cnid(hfsmp, &new_id))) { + hfs_systemfile_unlock (hfsmp, lockflags); + goto exit; + } + + error = cat_create(hfsmp, new_id, &in_desc, &attr, &out_desc); if (error == 0) { /* Update the parent directory */ dcp->c_childhint = out_desc.cd_hint; /* Cache directory's location */ dcp->c_entries++; + { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)dcp->c_finderinfo + 16); + extinfo->write_gen_counter = OSSwapHostToBigInt32(OSSwapBigToHostInt32(extinfo->write_gen_counter) + 1); + } if (vnodetype == VDIR) { INC_FOLDERCOUNT(hfsmp, dcp->c_attr); } dcp->c_dirchangecnt++; + { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)dcp->c_finderinfo + 16); + extinfo->write_gen_counter = OSSwapHostToBigInt32(OSSwapBigToHostInt32(extinfo->write_gen_counter) + 1); + } dcp->c_ctime = tv.tv_sec; dcp->c_mtime = tv.tv_sec; (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL); @@ -5340,14 +6072,12 @@ hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, #if CONFIG_PROTECT /* * At this point, we must have encountered success with writing the EA. - * Update MKB with the data for the cached key, then destroy it. This may - * prevent information leakage by ensuring the cache key is only unwrapped - * to perform file I/O and it is allowed. + * Destroy our temporary cprotect (which had no keys). */ if ((attr.ca_fileid != 0) && (protected_mount) && (protectable_target)) { - cp_update_mkb (entry, attr.ca_fileid); - cp_entry_destroy (&entry); + cp_entry_destroy (entry); + entry = NULL; } #endif @@ -5357,6 +6087,9 @@ hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, } gnv_flags |= GNV_CREATE; + if (nocache) { + gnv_flags |= GNV_NOCACHE; + } /* * Create a vnode for the object just created. @@ -5380,8 +6113,115 @@ hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, goto exit; cp = VTOC(tvp); + + struct doc_tombstone *ut; + ut = get_uthread_doc_tombstone(); + if ( ut->t_lastop_document_id != 0 + && ut->t_lastop_parent == dvp + && ut->t_lastop_parent_vid == vnode_vid(dvp) + && strcmp((char *)ut->t_lastop_filename, (char *)cp->c_desc.cd_nameptr) == 0) { + struct FndrExtendedDirInfo *fip = (struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16); + + //printf("CREATE: preserving doc-id %lld on %s\n", ut->t_lastop_document_id, ut->t_lastop_filename); + fip->document_id = (uint32_t)(ut->t_lastop_document_id & 0xffffffff); + + cp->c_bsdflags |= UF_TRACKED; + // mark the cnode dirty + cp->c_flag |= C_MODIFIED | C_FORCEUPDATE; + + if ((error = hfs_start_transaction(hfsmp)) == 0) { + lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK); + + (void) cat_update(hfsmp, &cp->c_desc, &cp->c_attr, NULL, NULL); + + hfs_systemfile_unlock (hfsmp, lockflags); + (void) hfs_end_transaction(hfsmp); + } + + clear_tombstone_docid(ut, hfsmp, cp); // will send the docid-changed fsevent + } else if (ut->t_lastop_document_id != 0) { + int len = cnp->cn_namelen; + if (len == 0) { + len = strlen(cnp->cn_nameptr); + } + + if (is_ignorable_temp_name(cnp->cn_nameptr, cnp->cn_namelen)) { + // printf("CREATE: not clearing tombstone because %s is a temp name.\n", cnp->cn_nameptr); + } else { + // Clear the tombstone because the thread is not recreating the same path + // printf("CREATE: clearing tombstone because %s is NOT a temp name.\n", cnp->cn_nameptr); + clear_tombstone_docid(ut, hfsmp, NULL); + } + } + *vpp = tvp; +#if CONFIG_PROTECT + /* + * Now that we have a vnode-in-hand, generate keys for this namespace item. + * If we fail to create the keys, then attempt to delete the item from the + * namespace. If we can't delete the item, that's not desirable but also not fatal.. + * All of the places which deal with restoring/unwrapping keys must also be + * prepared to encounter an entry that does not have keys. + */ + if ((protectable_target) && (protected_mount)) { + struct cprotect *keyed_entry = NULL; + + if (cp->c_cpentry == NULL) { + panic ("hfs_makenode: no cpentry for cnode (%p)", cp); + } + + error = cp_generate_keys (hfsmp, cp, cp->c_cpentry->cp_pclass, &keyed_entry); + if (error == 0) { + /* + * Upon success, the keys were generated and written out. + * Update the cp pointer in the cnode. + */ + cp_replace_entry (cp, keyed_entry); + if (nocache) { + cache_enter (dvp, tvp, cnp); + } + } + else { + /* If key creation OR the setxattr failed, emit EPERM to userland */ + error = EPERM; + + /* + * Beware! This slightly violates the lock ordering for the + * cnode/vnode 'tvp'. Ordinarily, you must acquire the truncate lock + * which guards file size changes before acquiring the normal cnode lock + * and calling hfs_removefile on an item. + * + * However, in this case, we are still holding the directory lock so + * 'tvp' is not lookup-able and it was a newly created vnode so it + * cannot have any content yet. The only reason we are initiating + * the removefile is because we could not generate content protection keys + * for this namespace item. Note also that we pass a '1' in the allow_dirs + * argument for hfs_removefile because we may be creating a directory here. + * + * All this to say that while it is technically a violation it is + * impossible to race with another thread for this cnode so it is safe. + */ + int err = hfs_removefile (dvp, tvp, cnp, 0, 0, 1, NULL, 0); + if (err) { + printf("hfs_makenode: removefile failed (%d) for CP entry %p\n", err, tvp); + } + + /* Release the cnode lock and mark the vnode for termination */ + hfs_unlock (cp); + err = vnode_recycle (tvp); + if (err) { + printf("hfs_makenode: vnode_recycle failed (%d) for CP entry %p\n", err, tvp); + } + + /* Drop the iocount on the new vnode to force reclamation/recycling */ + vnode_put (tvp); + cp = NULL; + *vpp = NULL; + } + } +#endif + #if QUOTA /* * Once we create this vnode, we need to initialize its quota data @@ -5390,7 +6230,10 @@ hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, * function) to see if creating this cnode/vnode would cause us to go over quota. */ if (hfsmp->hfs_flags & HFS_QUOTAS) { - (void) hfs_getinoquota(cp); + if (cp) { + /* cp could have been zeroed earlier */ + (void) hfs_getinoquota(cp); + } } #endif @@ -5405,7 +6248,8 @@ exit: * out the pointer if it was called already. */ if (entry) { - cp_entry_destroy (&entry); + cp_entry_destroy (entry); + entry = NULL; } #endif @@ -5499,7 +6343,7 @@ restart: error = vnode_getwithvid(rvp, vid); if (can_drop_lock) { - (void) hfs_lock(cp, HFS_FORCE_LOCK); + (void) hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); /* * When we relinquished our cnode lock, the cnode could have raced @@ -5538,7 +6382,7 @@ restart: if (name) printf("hfs_vgetrsrc: couldn't get resource" - " fork for %s, err %d\n", name, error); + " fork for %s, vol=%s, err=%d\n", name, hfsmp->vcbVN, error); return (error); } } else { @@ -5604,9 +6448,7 @@ restart: lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK); - /* - * Get resource fork data - * + /* * We call cat_idlookup (instead of cat_lookup) below because we can't * trust the descriptor in the provided cnode for lookups at this point. * Between the time of the original lookup of this vnode and now, the @@ -5632,15 +6474,38 @@ restart: * * As a result, we use the fileid, which should be invariant for the lifetime * of the cnode (possibly barring calls to exchangedata). + * + * Addendum: We can't do the above for HFS standard since we aren't guaranteed to + * have thread records for files. They were only required for directories. So + * we need to do the lookup with the catalog name. This is OK since hardlinks were + * never allowed on HFS standard. */ - error = cat_idlookup (hfsmp, cp->c_attr.ca_fileid, 0, 1, NULL, NULL, &rsrcfork); + /* Get resource fork data */ + if ((hfsmp->hfs_flags & HFS_STANDARD) == 0) { + error = cat_idlookup (hfsmp, cp->c_fileid, 0, 1, NULL, NULL, &rsrcfork); + } +#if CONFIG_HFS_STD + else { + /* + * HFS standard only: + * + * Get the resource fork for this item with a cat_lookup call, but do not + * force a case lookup since HFS standard is case-insensitive only. We + * don't want the descriptor; just the fork data here. If we tried to + * do a ID lookup (via thread record -> catalog record), then we might fail + * prematurely since, as noted above, thread records were not strictly required + * on files in HFS. + */ + error = cat_lookup (hfsmp, descptr, 1, 0, (struct cat_desc*)NULL, + (struct cat_attr*)NULL, &rsrcfork, NULL); + } +#endif hfs_systemfile_unlock(hfsmp, lockflags); if (error) { return (error); } - /* * Supply hfs_getnewvnode with a component name. */ @@ -5730,7 +6595,7 @@ hfsspec_close(ap) struct cnode *cp; if (vnode_isinuse(ap->a_vp, 0)) { - if (hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK) == 0) { + if (hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) == 0) { cp = VTOC(vp); hfs_touchtimes(VTOHFS(vp), cp); hfs_unlock(cp); @@ -5796,7 +6661,7 @@ hfsfifo_close(ap) struct cnode *cp; if (vnode_isinuse(ap->a_vp, 1)) { - if (hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK) == 0) { + if (hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) == 0) { cp = VTOC(vp); hfs_touchtimes(VTOHFS(vp), cp); hfs_unlock(cp); @@ -5808,6 +6673,46 @@ hfsfifo_close(ap) #endif /* FIFO */ +/* + * Getter for the document_id + * the document_id is stored in FndrExtendedFileInfo/FndrExtendedDirInfo + */ +static u_int32_t +hfs_get_document_id_internal(const uint8_t *finderinfo, mode_t mode) +{ + u_int8_t *finfo = NULL; + u_int32_t doc_id = 0; + + /* overlay the FinderInfo to the correct pointer, and advance */ + finfo = ((uint8_t *)finderinfo) + 16; + + if (S_ISDIR(mode) || S_ISREG(mode)) { + struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; + doc_id = extinfo->document_id; + } else if (S_ISDIR(mode)) { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)finderinfo + 16); + doc_id = extinfo->document_id; + } + + return doc_id; +} + + +/* getter(s) for document id */ +u_int32_t +hfs_get_document_id(struct cnode *cp) +{ + return (hfs_get_document_id_internal((u_int8_t*)cp->c_finderinfo, + cp->c_attr.ca_mode)); +} + +/* If you have finderinfo and mode, you can use this */ +u_int32_t +hfs_get_document_id_from_blob(const uint8_t *finderinfo, mode_t mode) +{ + return (hfs_get_document_id_internal(finderinfo, mode)); +} + /* * Synchronize a file's in-core state with that on disk. */ @@ -5841,7 +6746,7 @@ hfs_vnop_fsync(ap) * We need to allow ENOENT lock errors since unlink * systenm call can call VNOP_FSYNC during vclean. */ - error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK); + error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); if (error) return (0); @@ -5920,10 +6825,12 @@ exit: } int (**hfs_vnodeop_p)(void *); -int (**hfs_std_vnodeop_p) (void *); #define VOPFUNC int (*)(void *) + +#if CONFIG_HFS_STD +int (**hfs_std_vnodeop_p) (void *); static int hfs_readonly_op (__unused void* ap) { return (EROFS); } /* @@ -5990,7 +6897,7 @@ struct vnodeopv_entry_desc hfs_standard_vnodeop_entries[] = { struct vnodeopv_desc hfs_std_vnodeop_opv_desc = { &hfs_std_vnodeop_p, hfs_standard_vnodeop_entries }; - +#endif /* VNOP table for HFS+ */ struct vnodeopv_entry_desc hfs_vnodeop_entries[] = { @@ -6091,6 +6998,10 @@ struct vnodeopv_entry_desc hfs_specop_entries[] = { { &vnop_copyfile_desc, (VOPFUNC)err_copyfile }, /* copyfile */ { &vnop_blktooff_desc, (VOPFUNC)hfs_vnop_blktooff }, /* blktooff */ { &vnop_offtoblk_desc, (VOPFUNC)hfs_vnop_offtoblk }, /* offtoblk */ + { &vnop_getxattr_desc, (VOPFUNC)hfs_vnop_getxattr}, + { &vnop_setxattr_desc, (VOPFUNC)hfs_vnop_setxattr}, + { &vnop_removexattr_desc, (VOPFUNC)hfs_vnop_removexattr}, + { &vnop_listxattr_desc, (VOPFUNC)hfs_vnop_listxattr}, { (struct vnodeop_desc*)NULL, (VOPFUNC)NULL } }; struct vnodeopv_desc hfs_specop_opv_desc = @@ -6135,6 +7046,10 @@ struct vnodeopv_entry_desc hfs_fifoop_entries[] = { { &vnop_blktooff_desc, (VOPFUNC)hfs_vnop_blktooff }, /* blktooff */ { &vnop_offtoblk_desc, (VOPFUNC)hfs_vnop_offtoblk }, /* offtoblk */ { &vnop_blockmap_desc, (VOPFUNC)hfs_vnop_blockmap }, /* blockmap */ + { &vnop_getxattr_desc, (VOPFUNC)hfs_vnop_getxattr}, + { &vnop_setxattr_desc, (VOPFUNC)hfs_vnop_setxattr}, + { &vnop_removexattr_desc, (VOPFUNC)hfs_vnop_removexattr}, + { &vnop_listxattr_desc, (VOPFUNC)hfs_vnop_listxattr}, { (struct vnodeop_desc*)NULL, (VOPFUNC)NULL } }; struct vnodeopv_desc hfs_fifoop_opv_desc =