/*
- * Copyright (c) 2000-2012 Apple Inc. All rights reserved.
+ * Copyright (c) 2000-2013 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
#include <sys/cprotect.h>
#include <sys/xattr.h>
#include <string.h>
+#include <sys/fsevents.h>
+#include <kern/kalloc.h>
#include <miscfs/specfs/specdev.h>
#include <miscfs/fifofs/fifo.h>
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 *);
-
/*****************************************************************************
*
* Common Operations on vnodes
/* 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 */
/*
* 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) {
}
#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.
*/
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
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);
// 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;
}
}
if (tooktrunclock){
- hfs_unlock_truncate(cp, 0);
+ hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
}
hfs_unlock(cp);
*/
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);
}
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 |
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)) {
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) {
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
#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
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
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);
}
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.
/*
- * 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)
const unsigned char *to_nameptr;
char from_iname[32];
char to_iname[32];
- u_int32_t tempflag;
+ uint32_t to_flag_special;
+ uint32_t from_flag_special;
cnid_t from_parid;
cnid_t to_parid;
int lockflags;
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;
* 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;
}
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;
}
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;
to_cp = VTOC(to_vp);
hfsmp = VTOHFS(from_vp);
- /* Only normal files can be exchanged. */
- if (!vnode_isreg(from_vp) || !vnode_isreg(to_vp) ||
- VNODE_IS_RSRC(from_vp) || VNODE_IS_RSRC(to_vp)) {
+ /* Resource forks cannot be exchanged. */
+ if ( VNODE_IS_RSRC(from_vp) || VNODE_IS_RSRC(to_vp)) {
error = EINVAL;
goto exit;
}
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);
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));
- tempflag = from_cp->c_flag & (C_HARDLINK | C_HASXATTRS);
+
+ /* Save whether or not each cnode is a hardlink or has EAs */
+ from_flag_special = from_cp->c_flag & (C_HARDLINK | C_HASXATTRS);
+ to_flag_special = to_cp->c_flag & (C_HARDLINK | C_HASXATTRS);
+
+ /* Drop the special bits from each cnode */
+ from_cp->c_flag &= ~(C_HARDLINK | C_HASXATTRS);
+ 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;
- from_cp->c_fileid = from_cp->c_cnid;
+ /*
+ * If 'to' was a hardlink, then we copied over its link ID/CNID/(namespace ID)
+ * 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;
from_cp->c_bsdflags = to_cp->c_bsdflags;
from_cp->c_mode = to_cp->c_mode;
from_cp->c_linkcount = to_cp->c_linkcount;
- from_cp->c_flag = to_cp->c_flag & (C_HARDLINK | C_HASXATTRS);
+ from_cp->c_attr.ca_linkref = to_cp->c_attr.ca_linkref;
+ from_cp->c_attr.ca_firstlink = to_cp->c_attr.ca_firstlink;
+
+ /*
+ * The cnode flags need to stay with the cnode and not get transferred
+ * over along with everything else because they describe the content; they are
+ * not attributes that reflect changes specific to the file ID. In general,
+ * fields that are tied to the file ID are the ones that will move.
+ *
+ * This reflects the fact that the file may have borrowed blocks, dirty metadata,
+ * or other extents, which may not yet have been written to the catalog. If
+ * they were, they would have been transferred above in the ExchangeFileIDs call above...
+ *
+ * The flags that are special are:
+ * C_HARDLINK, C_HASXATTRS
+ *
+ * These flags move with the item and file ID in the namespace since their
+ * state is tied to that of the file ID.
+ *
+ * So to transfer the flags, we have to take the following steps
+ * 1) Store in a localvar whether or not the special bits are set.
+ * 2) Drop the special bits from the current flags
+ * 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;
- to_cp->c_fileid = to_cp->c_cnid;
+ /*
+ * Pull the file ID from the tempattr we copied above. We can't assume
+ * it is the same as the CNID.
+ */
+ to_cp->c_fileid = tempattr.ca_fileid;
to_cp->c_itime = tempattr.ca_itime;
to_cp->c_btime = tempattr.ca_btime;
to_cp->c_atime = tempattr.ca_atime;
to_cp->c_bsdflags = tempattr.ca_flags;
to_cp->c_mode = tempattr.ca_mode;
to_cp->c_linkcount = tempattr.ca_linkcount;
- to_cp->c_flag = tempflag;
+ to_cp->c_attr.ca_linkref = tempattr.ca_linkref;
+ to_cp->c_attr.ca_firstlink = tempattr.ca_firstlink;
+
+ /*
+ * Only OR in the "from" flags into our cnode flags below.
+ * Leave the rest of the flags alone.
+ */
+ to_cp->c_flag |= from_flag_special;
+
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);
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);
+ }
}
}
}
} 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
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))) {
(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;
}
/*
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);
}
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);
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!
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
* 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)) {
/*
* 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
* 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);
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
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);
/* 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);
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) {
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);
}
+ //
+ // 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...
*
* 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;
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)) {
* 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 */
}
}
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);
}
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;
}
goto retry;
}
+ if (emit_rename && is_tracked) {
+ resolve_nspace_item(fvp, NAMESPACE_HANDLER_RENAME_FAILED_OP | NAMESPACE_HANDLER_TRACK_EVENT);
+ }
+
return (error);
}
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
/* 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);
}
}
+
+
/* Ensure we didn't race src or dst parent directories with rmdir. */
if (fdcp->c_flag & (C_NOEXISTS | C_DELETED)) {
error = ENOENT;
// 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;
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;
}
* 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.
* 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:
/*
}
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++;
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;
/* 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
* 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);
}
}
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);
}
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);
/* 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;
}
*
* 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
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). */
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);
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 */
*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)
*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 */
struct hfsmount *hfsmp;
int lockflags;
int error;
+ uint32_t tstate = 0;
p = current_proc();
hfsmp = VTOHFS(vp);
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. */
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;
}
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.
hfs_end_transaction(hfsmp);
+ KERNEL_DEBUG_CONSTANT(0x3018000 | DBG_FUNC_END, vp, tstate, error, 0, 0);
+
return (error);
}
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 */
} 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)) {
*/
if ((protected_mount) && (protectable_target)) {
attr.ca_recflags |= kHFSHasAttributesMask;
+ /* delay entering in the namecache */
+ nocache = 1;
}
#endif
*/
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);
#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;
}
// 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);
#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
}
gnv_flags |= GNV_CREATE;
+ if (nocache) {
+ gnv_flags |= GNV_NOCACHE;
+ }
/*
* Create a vnode for the object just created.
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
* 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
* out the pointer if it was called already.
*/
if (entry) {
- cp_entry_destroy (&entry);
+ cp_entry_destroy (entry);
+ entry = NULL;
}
#endif
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
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 {
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
+ /*
+ * 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
+ * descriptor could have gotten swapped or replaced. If this occurred,
+ * the parent/name combo originally desired may not necessarily be provided
+ * if we use the descriptor. Even worse, if the vnode represents
+ * a hardlink, we could have removed one of the links from the namespace
+ * but left the descriptor alone, since hfs_unlink does not invalidate
+ * the descriptor in the cnode if other links still point to the inode.
+ *
+ * Consider the following (slightly contrived) scenario:
+ * /tmp/a <--> /tmp/b (hardlinks).
+ * 1. Thread A: open rsrc fork on /tmp/b.
+ * 1a. Thread A: does lookup, goes out to lunch right before calling getnamedstream.
+ * 2. Thread B does 'mv /foo/b /tmp/b'
+ * 2. Thread B succeeds.
+ * 3. Thread A comes back and wants rsrc fork info for /tmp/b.
+ *
+ * Even though the hardlink backing /tmp/b is now eliminated, the descriptor
+ * is not removed/updated during the unlink process. So, if you were to
+ * do a lookup on /tmp/b, you'd acquire an entirely different record's resource
+ * fork.
+ *
+ * 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.
+ */
+
/* Get resource fork data */
- error = cat_lookup(hfsmp, descptr, 1, (struct cat_desc *)0,
- (struct cat_attr *)0, &rsrcfork, NULL);
+ 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) {
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);
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);
#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.
*/
* 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);
}
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); }
/*
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[] = {
{ &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 =
{ &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 =