+ if (VATTR_IS_ACTIVE(vap, va_access_time)) {
+ /* Access times are lazily updated, get current time if needed */
+ if (cp->c_touch_acctime) {
+ struct timeval tv;
+
+ microtime(&tv);
+ vap->va_access_time.tv_sec = tv.tv_sec;
+ } else {
+ vap->va_access_time.tv_sec = cp->c_atime;
+ }
+ vap->va_access_time.tv_nsec = 0;
+ VATTR_SET_SUPPORTED(vap, va_access_time);
+ }
+ vap->va_create_time.tv_sec = cp->c_itime;
+ vap->va_create_time.tv_nsec = 0;
+ vap->va_modify_time.tv_sec = cp->c_mtime;
+ vap->va_modify_time.tv_nsec = 0;
+ vap->va_change_time.tv_sec = cp->c_ctime;
+ vap->va_change_time.tv_nsec = 0;
+ vap->va_backup_time.tv_sec = cp->c_btime;
+ vap->va_backup_time.tv_nsec = 0;
+
+ /* See if we need to emit the date added field to the user */
+ if (VATTR_IS_ACTIVE(vap, va_addedtime)) {
+ u_int32_t dateadded = hfs_get_dateadded (cp);
+ if (dateadded) {
+ vap->va_addedtime.tv_sec = dateadded;
+ vap->va_addedtime.tv_nsec = 0;
+ VATTR_SET_SUPPORTED (vap, va_addedtime);
+ }
+ }
+
+ /* XXX is this really a good 'optimal I/O size'? */
+ vap->va_iosize = hfsmp->hfs_logBlockSize;
+ vap->va_uid = cp->c_uid;
+ vap->va_gid = cp->c_gid;
+ vap->va_mode = cp->c_mode;
+ vap->va_flags = cp->c_bsdflags;
+
+ /*
+ * Exporting file IDs from HFS Plus:
+ *
+ * For "normal" files the c_fileid is the same value as the
+ * c_cnid. But for hard link files, they are different - the
+ * c_cnid belongs to the active directory entry (ie the link)
+ * and the c_fileid is for the actual inode (ie the data file).
+ *
+ * The stat call (getattr) uses va_fileid and the Carbon APIs,
+ * which are hardlink-ignorant, will ask for va_linkid.
+ */
+ vap->va_fileid = (u_int64_t)cp->c_fileid;
+ /*
+ * We need to use the origin cache for both hardlinked files
+ * and directories. Hardlinked directories have multiple cnids
+ * and parents (one per link). Hardlinked files also have their
+ * own parents and link IDs separate from the indirect inode number.
+ * If we don't use the cache, we could end up vending the wrong ID
+ * because the cnode will only reflect the link that was looked up most recently.
+ */
+ if (cp->c_flag & C_HARDLINK) {
+ vap->va_linkid = (u_int64_t)hfs_currentcnid(cp);
+ vap->va_parentid = (u_int64_t)hfs_currentparent(cp);
+ } else {
+ vap->va_linkid = (u_int64_t)cp->c_cnid;
+ vap->va_parentid = (u_int64_t)cp->c_parentcnid;
+ }
+ vap->va_fsid = hfsmp->hfs_raw_dev;
+ vap->va_filerev = 0;
+ vap->va_encoding = cp->c_encoding;
+ vap->va_rdev = (v_type == VBLK || v_type == VCHR) ? cp->c_rdev : 0;
+#if HFS_COMPRESSION
+ if (VATTR_IS_ACTIVE(vap, va_data_size)) {
+ if (hide_size)
+ vap->va_data_size = 0;
+ else if (compressed) {
+ if (uncompressed_size == -1) {
+ /* failed to get the uncompressed size above, so just return data_size */
+ vap->va_data_size = data_size;
+ } else {
+ /* use the uncompressed size we fetched above */
+ vap->va_data_size = uncompressed_size;
+ }
+ } else
+ vap->va_data_size = data_size;
+// vap->va_supported |= VNODE_ATTR_va_data_size;
+ VATTR_SET_SUPPORTED(vap, va_data_size);
+ }
+#else
+ vap->va_data_size = data_size;
+ vap->va_supported |= VNODE_ATTR_va_data_size;
+#endif
+
+#if CONFIG_PROTECT
+ if (VATTR_IS_ACTIVE(vap, va_dataprotect_class)) {
+ vap->va_dataprotect_class = cp->c_cpentry ? cp->c_cpentry->cp_pclass : 0;
+ VATTR_SET_SUPPORTED(vap, va_dataprotect_class);
+ }
+#endif
+ if (VATTR_IS_ACTIVE(vap, va_write_gencount)) {
+ if (ubc_is_mapped_writable(vp)) {
+ /*
+ * Return 0 to the caller to indicate the file may be
+ * changing. There is no need for us to increment the
+ * generation counter here because it gets done as part of
+ * page-out and also when the file is unmapped (to account
+ * for changes we might not have seen).
+ */
+ vap->va_write_gencount = 0;
+ } else {
+ vap->va_write_gencount = hfs_get_gencount(cp);
+ }
+
+ VATTR_SET_SUPPORTED(vap, va_write_gencount);
+ }
+
+ /* 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 |
+ VNODE_ATTR_va_iosize | VNODE_ATTR_va_uid |
+ VNODE_ATTR_va_gid | VNODE_ATTR_va_mode |
+ VNODE_ATTR_va_flags |VNODE_ATTR_va_fileid |
+ VNODE_ATTR_va_linkid | VNODE_ATTR_va_parentid |
+ VNODE_ATTR_va_fsid | VNODE_ATTR_va_filerev |
+ VNODE_ATTR_va_encoding | VNODE_ATTR_va_rdev;
+
+ /* If this is the root, let VFS to find out the mount name, which
+ * may be different from the real name. Otherwise, we need to take care
+ * for hardlinked files, which need to be looked up, if necessary
+ */
+ if (VATTR_IS_ACTIVE(vap, va_name) && (cp->c_cnid != kHFSRootFolderID)) {
+ struct cat_desc linkdesc;
+ int lockflags;
+ int uselinkdesc = 0;
+ cnid_t nextlinkid = 0;
+ cnid_t prevlinkid = 0;
+
+ /* Get the name for ATTR_CMN_NAME. We need to take special care for hardlinks
+ * here because the info. for the link ID requested by getattrlist may be
+ * different than what's currently in the cnode. This is because the cnode
+ * will be filled in with the information for the most recent link ID that went
+ * through namei/lookup(). If there are competing lookups for hardlinks that point
+ * to the same inode, one (or more) getattrlists could be vended incorrect name information.
+ * Also, we need to beware of open-unlinked files which could have a namelen of 0.
+ */
+
+ 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
+ * 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)) {
+ if ((error = hfs_lookup_siblinglinks(hfsmp, vap->va_linkid, &prevlinkid, &nextlinkid))){
+ goto out;
+ }
+ }
+ else {
+ /* just use link obtained from vap above */
+ nextlinkid = vap->va_linkid;
+ }
+
+ /* We need to probe the catalog for the descriptor corresponding to the link ID
+ * stored in nextlinkid. Note that we don't know if we have the exclusive lock
+ * for the cnode here, so we can't just update the descriptor. Instead,
+ * we should just store the descriptor's value locally and then use it to pass
+ * out the name value as needed below.
+ */
+ if (nextlinkid){
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
+ error = cat_findname(hfsmp, nextlinkid, &linkdesc);
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ if (error == 0) {
+ uselinkdesc = 1;
+ }
+ }
+ }
+
+ /* By this point, we've either patched up the name above and the c_desc
+ * points to the correct data, or it already did, in which case we just proceed
+ * by copying the name into the vap. Note that we will never set va_name to
+ * supported if nextlinkid is never initialized. This could happen in the degenerate
+ * case above involving the raw inode number, where it has no nextlinkid. In this case
+ * we will simply not mark the name bit as supported.
+ */
+ if (uselinkdesc) {
+ strlcpy(vap->va_name, (const char*) linkdesc.cd_nameptr, MAXPATHLEN);
+ VATTR_SET_SUPPORTED(vap, va_name);
+ cat_releasedesc(&linkdesc);
+ }
+ else if (cp->c_desc.cd_namelen) {
+ strlcpy(vap->va_name, (const char*) cp->c_desc.cd_nameptr, MAXPATHLEN);
+ VATTR_SET_SUPPORTED(vap, va_name);
+ }
+ }
+
+out:
+ hfs_unlock(cp);
+ /*
+ * We need to vnode_put the rsrc fork vnode only *after* we've released
+ * the cnode lock, since vnode_put can trigger an inactive call, which
+ * will go back into HFS and try to acquire a cnode lock.
+ */
+ if (rvp) {
+ vnode_put (rvp);
+ }
+
+ return (error);
+}
+
+int
+hfs_vnop_setattr(ap)
+ struct vnop_setattr_args /* {
+ struct vnode *a_vp;
+ struct vnode_attr *a_vap;
+ vfs_context_t a_context;
+ } */ *ap;
+{
+ struct vnode_attr *vap = ap->a_vap;
+ struct vnode *vp = ap->a_vp;
+ struct cnode *cp = NULL;
+ struct hfsmount *hfsmp;
+ kauth_cred_t cred = vfs_context_ucred(ap->a_context);
+ struct proc *p = vfs_context_proc(ap->a_context);
+ int error = 0;
+ uid_t nuid;
+ gid_t ngid;
+ time_t orig_ctime;
+
+ orig_ctime = VTOC(vp)->c_ctime;
+
+#if HFS_COMPRESSION
+ int decmpfs_reset_state = 0;
+ /*
+ we call decmpfs_update_attributes even if the file is not compressed
+ because we want to update the incoming flags if the xattrs are invalid
+ */
+ 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, NSPACE_REARM_NO_ARG);
+ }
+
+#if CONFIG_PROTECT
+ if ((error = cp_handle_vnop(vp, CP_WRITE_ACCESS, 0)) != 0) {
+ return (error);
+ }
+#endif /* CONFIG_PROTECT */
+
+ hfsmp = VTOHFS(vp);
+
+ /* Don't allow modification of the journal. */
+ if (hfs_is_journal_file(hfsmp, VTOC(vp))) {
+ 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
+ * the filesystem object is writeable.
+ *
+ * NOTE: HFS COMPRESSION depends on the data_size being set *before* the bsd flags are updated
+ */
+ VATTR_SET_SUPPORTED(vap, va_data_size);
+ if (VATTR_IS_ACTIVE(vap, va_data_size) && !vnode_islnk(vp)) {
+#if HFS_COMPRESSION
+ /* keep the compressed state locked until we're done truncating the file */
+ decmpfs_cnode *dp = VTOCMP(vp);
+ if (!dp) {
+ /*
+ * call hfs_lazy_init_decmpfs_cnode() to make sure that the decmpfs_cnode
+ * is filled in; we need a decmpfs_cnode to lock out decmpfs state changes
+ * on this file while it's truncating
+ */
+ dp = hfs_lazy_init_decmpfs_cnode(VTOC(vp));
+ if (!dp) {
+ /* failed to allocate a decmpfs_cnode */
+ return ENOMEM; /* what should this be? */
+ }
+ }
+
+ check_for_tracked_file(vp, orig_ctime, vap->va_data_size == 0 ? NAMESPACE_HANDLER_TRUNCATE_OP|NAMESPACE_HANDLER_DELETE_OP : NAMESPACE_HANDLER_TRUNCATE_OP, NULL);
+
+ decmpfs_lock_compressed_data(dp, 1);
+ if (hfs_file_is_compressed(VTOC(vp), 1)) {
+ error = decmpfs_decompress_file(vp, dp, -1/*vap->va_data_size*/, 0, 1);
+ if (error != 0) {
+ decmpfs_unlock_compressed_data(dp, 1);
+ return error;
+ }
+ }
+#endif
+
+ // Take truncate lock
+ hfs_lock_truncate(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
+
+ // hfs_truncate will deal with the cnode lock
+ error = hfs_truncate(vp, vap->va_data_size, vap->va_vaflags & 0xffff,
+ 0, ap->a_context);
+
+ hfs_unlock_truncate(VTOC(vp), HFS_LOCK_DEFAULT);
+#if HFS_COMPRESSION
+ decmpfs_unlock_compressed_data(dp, 1);
+#endif
+ if (error)
+ return error;
+ }
+ if (cp == NULL) {
+ if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT)))
+ return (error);
+ cp = VTOC(vp);
+ }
+
+ /*
+ * If it is just an access time update request by itself
+ * we know the request is from kernel level code, and we
+ * can delay it without being as worried about consistency.
+ * This change speeds up mmaps, in the rare case that they
+ * get caught behind a sync.
+ */
+
+ if (vap->va_active == VNODE_ATTR_va_access_time) {
+ cp->c_touch_acctime=TRUE;
+ goto out;
+ }
+
+
+
+ /*
+ * Owner/group change request.
+ * We are guaranteed that the new owner/group is valid and legal.
+ */
+ VATTR_SET_SUPPORTED(vap, va_uid);
+ VATTR_SET_SUPPORTED(vap, va_gid);
+ nuid = VATTR_IS_ACTIVE(vap, va_uid) ? vap->va_uid : (uid_t)VNOVAL;
+ ngid = VATTR_IS_ACTIVE(vap, va_gid) ? vap->va_gid : (gid_t)VNOVAL;
+ if (((nuid != (uid_t)VNOVAL) || (ngid != (gid_t)VNOVAL)) &&
+ ((error = hfs_chown(vp, nuid, ngid, cred, p)) != 0))
+ goto out;
+
+ /*
+ * Mode change request.
+ * We are guaranteed that the mode value is valid and that in
+ * conjunction with the owner and group, this change is legal.
+ */
+ VATTR_SET_SUPPORTED(vap, va_mode);
+ if (VATTR_IS_ACTIVE(vap, va_mode) &&
+ ((error = hfs_chmod(vp, (int)vap->va_mode, cred, p)) != 0))
+ goto out;
+
+ /*
+ * File flags change.
+ * We are guaranteed that only flags allowed to change given the
+ * current securelevel are being changed.
+ */
+ VATTR_SET_SUPPORTED(vap, va_flags);
+ if (VATTR_IS_ACTIVE(vap, va_flags)) {
+ u_int16_t *fdFlags;
+
+#if HFS_COMPRESSION
+ if ((cp->c_bsdflags ^ vap->va_flags) & UF_COMPRESSED) {
+ /*
+ * the UF_COMPRESSED was toggled, so reset our cached compressed state
+ * but we don't want to actually do the update until we've released the cnode lock down below
+ * NOTE: turning the flag off doesn't actually decompress the file, so that we can
+ * turn off the flag and look at the "raw" file for debugging purposes
+ */
+ 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 fdFlags for files and frFlags for folders are both 8 bytes
+ * into the userInfo (the first 16 bytes of the Finder Info). They
+ * are both 16-bit fields.
+ */
+ fdFlags = (u_int16_t *) &cp->c_finderinfo[8];
+ if (vap->va_flags & UF_HIDDEN)
+ *fdFlags |= OSSwapHostToBigConstInt16(kFinderInvisibleMask);
+ else
+ *fdFlags &= ~OSSwapHostToBigConstInt16(kFinderInvisibleMask);
+ }
+
+ /*
+ * Timestamp updates.
+ */
+ VATTR_SET_SUPPORTED(vap, va_create_time);
+ VATTR_SET_SUPPORTED(vap, va_access_time);
+ VATTR_SET_SUPPORTED(vap, va_modify_time);
+ VATTR_SET_SUPPORTED(vap, va_backup_time);
+ VATTR_SET_SUPPORTED(vap, va_change_time);
+ if (VATTR_IS_ACTIVE(vap, va_create_time) ||
+ VATTR_IS_ACTIVE(vap, va_access_time) ||
+ VATTR_IS_ACTIVE(vap, va_modify_time) ||
+ VATTR_IS_ACTIVE(vap, va_backup_time)) {
+ if (VATTR_IS_ACTIVE(vap, va_create_time))
+ cp->c_itime = vap->va_create_time.tv_sec;
+ if (VATTR_IS_ACTIVE(vap, va_access_time)) {
+ cp->c_atime = vap->va_access_time.tv_sec;
+ cp->c_touch_acctime = FALSE;
+ }
+ if (VATTR_IS_ACTIVE(vap, va_modify_time)) {
+ cp->c_mtime = vap->va_modify_time.tv_sec;
+ cp->c_touch_modtime = FALSE;
+ cp->c_touch_chgtime = TRUE;
+
+ hfs_clear_might_be_dirty_flag(cp);
+
+ /*
+ * The utimes system call can reset the modification
+ * time but it doesn't know about HFS create times.
+ * So we need to ensure that the creation time is
+ * always at least as old as the modification time.
+ */
+ if ((VTOVCB(vp)->vcbSigWord == kHFSPlusSigWord) &&
+ (cp->c_cnid != kHFSRootFolderID) &&
+ (cp->c_mtime < cp->c_itime)) {
+ cp->c_itime = cp->c_mtime;
+ }
+ }
+ if (VATTR_IS_ACTIVE(vap, va_backup_time))
+ cp->c_btime = vap->va_backup_time.tv_sec;
+ cp->c_flag |= C_MODIFIED;
+ }
+
+ /*
+ * Set name encoding.
+ */
+ VATTR_SET_SUPPORTED(vap, va_encoding);
+ if (VATTR_IS_ACTIVE(vap, va_encoding)) {
+ cp->c_encoding = vap->va_encoding;
+ hfs_setencodingbits(hfsmp, cp->c_encoding);
+ }
+
+ if ((error = hfs_update(vp, TRUE)) != 0)
+ goto out;
+out:
+ if (cp) {
+ /* Purge origin cache for cnode, since caller now has correct link ID for it
+ * We purge it here since it was acquired for us during lookup, and we no longer need it.
+ */
+ if ((cp->c_flag & C_HARDLINK) && (vp->v_type != VDIR)){
+ hfs_relorigin(cp, 0);
+ }
+
+ hfs_unlock(cp);
+#if HFS_COMPRESSION
+ if (decmpfs_reset_state) {
+ /*
+ * we've changed the UF_COMPRESSED flag, so reset the decmpfs state for this cnode
+ * but don't do it while holding the hfs cnode lock
+ */
+ decmpfs_cnode *dp = VTOCMP(vp);
+ if (!dp) {
+ /*
+ * call hfs_lazy_init_decmpfs_cnode() to make sure that the decmpfs_cnode
+ * is filled in; we need a decmpfs_cnode to prevent decmpfs state changes
+ * on this file if it's locked
+ */
+ dp = hfs_lazy_init_decmpfs_cnode(VTOC(vp));
+ if (!dp) {
+ /* failed to allocate a decmpfs_cnode */
+ return ENOMEM; /* what should this be? */
+ }
+ }
+ decmpfs_cnode_set_vnode_state(dp, FILE_TYPE_UNKNOWN, 0);
+ }
+#endif
+ }
+ return (error);
+}
+
+
+/*
+ * Change the mode on a file.
+ * cnode must be locked before calling.
+ */
+int
+hfs_chmod(struct vnode *vp, int mode, __unused kauth_cred_t cred, __unused struct proc *p)
+{
+ register struct cnode *cp = VTOC(vp);
+
+ if (VTOVCB(vp)->vcbSigWord != kHFSPlusSigWord)
+ return (0);
+
+ // Don't allow modification of the journal or journal_info_block
+ if (hfs_is_journal_file(VTOHFS(vp), cp)) {
+ return EPERM;
+ }
+
+#if OVERRIDE_UNKNOWN_PERMISSIONS
+ if (((unsigned int)vfs_flags(VTOVFS(vp))) & MNT_UNKNOWNPERMISSIONS) {
+ return (0);
+ };
+#endif
+ cp->c_mode &= ~ALLPERMS;
+ cp->c_mode |= (mode & ALLPERMS);
+ cp->c_touch_chgtime = TRUE;
+ return (0);
+}
+
+
+int
+hfs_write_access(struct vnode *vp, kauth_cred_t cred, struct proc *p, Boolean considerFlags)
+{
+ struct cnode *cp = VTOC(vp);
+ int retval = 0;
+ int is_member;
+
+ /*
+ * Disallow write attempts on read-only file systems;
+ * unless the file is a socket, fifo, or a block or
+ * character device resident on the file system.
+ */
+ switch (vnode_vtype(vp)) {
+ case VDIR:
+ case VLNK:
+ case VREG:
+ if (VTOHFS(vp)->hfs_flags & HFS_READ_ONLY)
+ return (EROFS);
+ break;
+ default:
+ break;
+ }
+
+ /* If immutable bit set, nobody gets to write it. */
+ if (considerFlags && (cp->c_bsdflags & IMMUTABLE))
+ return (EPERM);
+
+ /* Otherwise, user id 0 always gets access. */
+ if (!suser(cred, NULL))
+ return (0);
+
+ /* Otherwise, check the owner. */
+ if ((retval = hfs_owner_rights(VTOHFS(vp), cp->c_uid, cred, p, false)) == 0)
+ return ((cp->c_mode & S_IWUSR) == S_IWUSR ? 0 : EACCES);
+
+ /* Otherwise, check the groups. */
+ if (kauth_cred_ismember_gid(cred, cp->c_gid, &is_member) == 0 && is_member) {
+ return ((cp->c_mode & S_IWGRP) == S_IWGRP ? 0 : EACCES);
+ }
+
+ /* Otherwise, check everyone else. */
+ return ((cp->c_mode & S_IWOTH) == S_IWOTH ? 0 : EACCES);
+}
+
+
+/*
+ * Perform chown operation on cnode cp;
+ * code must be locked prior to call.
+ */
+int
+#if !QUOTA
+hfs_chown(struct vnode *vp, uid_t uid, gid_t gid, __unused kauth_cred_t cred,
+ __unused struct proc *p)
+#else
+hfs_chown(struct vnode *vp, uid_t uid, gid_t gid, kauth_cred_t cred,
+ __unused struct proc *p)
+#endif
+{
+ register struct cnode *cp = VTOC(vp);
+ uid_t ouid;
+ gid_t ogid;
+#if QUOTA
+ int error = 0;
+ register int i;
+ int64_t change;
+#endif /* QUOTA */
+
+ if (VTOVCB(vp)->vcbSigWord != kHFSPlusSigWord)
+ return (ENOTSUP);
+
+ if (((unsigned int)vfs_flags(VTOVFS(vp))) & MNT_UNKNOWNPERMISSIONS)
+ return (0);
+
+ if (uid == (uid_t)VNOVAL)
+ uid = cp->c_uid;
+ if (gid == (gid_t)VNOVAL)
+ gid = cp->c_gid;
+
+#if 0 /* we are guaranteed that this is already the case */
+ /*
+ * If we don't own the file, are trying to change the owner
+ * of the file, or are not a member of the target group,
+ * the caller must be superuser or the call fails.
+ */
+ if ((kauth_cred_getuid(cred) != cp->c_uid || uid != cp->c_uid ||
+ (gid != cp->c_gid &&
+ (kauth_cred_ismember_gid(cred, gid, &is_member) || !is_member))) &&
+ (error = suser(cred, 0)))
+ return (error);
+#endif
+
+ ogid = cp->c_gid;
+ ouid = cp->c_uid;
+#if QUOTA
+ if ((error = hfs_getinoquota(cp)))
+ return (error);
+ if (ouid == uid) {
+ dqrele(cp->c_dquot[USRQUOTA]);
+ cp->c_dquot[USRQUOTA] = NODQUOT;
+ }
+ if (ogid == gid) {
+ dqrele(cp->c_dquot[GRPQUOTA]);
+ cp->c_dquot[GRPQUOTA] = NODQUOT;
+ }
+
+ /*
+ * Eventually need to account for (fake) a block per directory
+ * if (vnode_isdir(vp))
+ * change = VTOHFS(vp)->blockSize;
+ * else
+ */
+
+ change = (int64_t)(cp->c_blocks) * (int64_t)VTOVCB(vp)->blockSize;
+ (void) hfs_chkdq(cp, -change, cred, CHOWN);
+ (void) hfs_chkiq(cp, -1, cred, CHOWN);
+ for (i = 0; i < MAXQUOTAS; i++) {
+ dqrele(cp->c_dquot[i]);
+ cp->c_dquot[i] = NODQUOT;
+ }
+#endif /* QUOTA */
+ cp->c_gid = gid;
+ cp->c_uid = uid;
+#if QUOTA
+ if ((error = hfs_getinoquota(cp)) == 0) {
+ if (ouid == uid) {
+ dqrele(cp->c_dquot[USRQUOTA]);
+ cp->c_dquot[USRQUOTA] = NODQUOT;
+ }
+ if (ogid == gid) {
+ dqrele(cp->c_dquot[GRPQUOTA]);
+ cp->c_dquot[GRPQUOTA] = NODQUOT;
+ }
+ if ((error = hfs_chkdq(cp, change, cred, CHOWN)) == 0) {
+ if ((error = hfs_chkiq(cp, 1, cred, CHOWN)) == 0)
+ goto good;
+ else
+ (void) hfs_chkdq(cp, -change, cred, CHOWN|FORCE);
+ }
+ for (i = 0; i < MAXQUOTAS; i++) {
+ dqrele(cp->c_dquot[i]);
+ cp->c_dquot[i] = NODQUOT;
+ }
+ }
+ cp->c_gid = ogid;
+ cp->c_uid = ouid;
+ if (hfs_getinoquota(cp) == 0) {
+ if (ouid == uid) {
+ dqrele(cp->c_dquot[USRQUOTA]);
+ cp->c_dquot[USRQUOTA] = NODQUOT;
+ }
+ if (ogid == gid) {
+ dqrele(cp->c_dquot[GRPQUOTA]);
+ cp->c_dquot[GRPQUOTA] = NODQUOT;
+ }
+ (void) hfs_chkdq(cp, change, cred, FORCE|CHOWN);
+ (void) hfs_chkiq(cp, 1, cred, FORCE|CHOWN);
+ (void) hfs_getinoquota(cp);
+ }
+ return (error);
+good:
+ if (hfs_getinoquota(cp))
+ panic("hfs_chown: lost quota");
+#endif /* QUOTA */
+
+
+ /*
+ According to the SUSv3 Standard, chown() shall mark
+ for update the st_ctime field of the file.
+ (No exceptions mentioned)
+ */
+ cp->c_touch_chgtime = TRUE;
+ return (0);
+}
+
+#if HFS_COMPRESSION
+/*
+ * Flush the resource fork if it exists. vp is the data fork and has
+ * an iocount.
+ */
+static int hfs_flush_rsrc(vnode_t vp, vfs_context_t ctx)
+{
+ cnode_t *cp = VTOC(vp);
+
+ hfs_lock(cp, HFS_SHARED_LOCK, 0);
+
+ vnode_t rvp = cp->c_rsrc_vp;
+
+ if (!rvp) {
+ hfs_unlock(cp);
+ return 0;
+ }
+
+ int vid = vnode_vid(rvp);
+
+ hfs_unlock(cp);
+
+ int error = vnode_getwithvid(rvp, vid);
+
+ if (error)
+ return error == ENOENT ? 0 : error;
+
+ hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, 0);
+ hfs_lock_always(cp, HFS_EXCLUSIVE_LOCK);
+ hfs_filedone(rvp, ctx, HFS_FILE_DONE_NO_SYNC);
+ hfs_unlock(cp);
+ hfs_unlock_truncate(cp, 0);
+
+ error = ubc_msync(rvp, 0, ubc_getsize(rvp), NULL,
+ UBC_PUSHALL | UBC_SYNC);
+
+ vnode_put(rvp);
+
+ return error;
+}
+#endif // HFS_COMPRESSION
+
+/*
+ * 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)
+ struct vnop_exchange_args /* {
+ struct vnode *a_fvp;
+ struct vnode *a_tvp;
+ int a_options;
+ vfs_context_t a_context;
+ } */ *ap;
+{
+ struct vnode *from_vp = ap->a_fvp;
+ struct vnode *to_vp = ap->a_tvp;
+ struct cnode *from_cp;
+ struct cnode *to_cp;
+ struct hfsmount *hfsmp;
+ struct cat_desc tempdesc;
+ struct cat_attr tempattr;
+ const unsigned char *from_nameptr;
+ const unsigned char *to_nameptr;
+ char from_iname[32];
+ char to_iname[32];
+ uint32_t to_flag_special;
+ uint32_t from_flag_special;
+ cnid_t from_parid;
+ cnid_t to_parid;
+ int lockflags;
+ int error = 0, started_tr = 0, got_cookie = 0;
+ cat_cookie_t cookie;
+ time_t orig_from_ctime, orig_to_ctime;
+ bool have_cnode_locks = false, have_from_trunc_lock = false, have_to_trunc_lock = false;
+
+ /*
+ * 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.
+ */
+
+ from_cp = VTOC(from_vp);
+ to_cp = VTOC(to_vp);
+ hfsmp = VTOHFS(from_vp);
+
+ orig_from_ctime = from_cp->c_ctime;
+ orig_to_ctime = to_cp->c_ctime;
+
+#if CONFIG_PROTECT
+ /*
+ * Do not allow exchangedata/F_MOVEDATAEXTENTS on data-protected filesystems
+ * because the EAs will not be swapped. As a result, the persistent keys would not
+ * match and the files will be garbage.
+ */
+ if (cp_fs_protected (vnode_mount(from_vp))) {
+ return EINVAL;
+ }
+#endif
+
+#if HFS_COMPRESSION
+ if (!ISSET(ap->a_options, FSOPT_EXCHANGE_DATA_ONLY)) {
+ if ( hfs_file_is_compressed(from_cp, 0) ) {
+ if ( 0 != ( error = decmpfs_decompress_file(from_vp, VTOCMP(from_vp), -1, 0, 1) ) ) {
+ return error;
+ }
+ }
+
+ if ( hfs_file_is_compressed(to_cp, 0) ) {
+ if ( 0 != ( error = decmpfs_decompress_file(to_vp, VTOCMP(to_vp), -1, 0, 1) ) ) {
+ return error;
+ }
+ }
+ }
+#endif // HFS_COMPRESSION
+
+ // Resource forks cannot be exchanged.
+ if (VNODE_IS_RSRC(from_vp) || VNODE_IS_RSRC(to_vp))
+ return EINVAL;
+
+ /*
+ * Normally, we want to notify the user handlers about the event,
+ * except if it's a handler driving the event.
+ */
+ if ((ap->a_options & FSOPT_EXCHANGE_DATA_ONLY) == 0) {
+ check_for_tracked_file(from_vp, orig_from_ctime, NAMESPACE_HANDLER_WRITE_OP, NULL);
+ check_for_tracked_file(to_vp, orig_to_ctime, NAMESPACE_HANDLER_WRITE_OP, NULL);
+ } else {
+ /*
+ * This is currently used by mtmd so we should tidy up the
+ * file now because the data won't be used again in the
+ * destination file.
+ */
+ hfs_lock_truncate(from_cp, HFS_EXCLUSIVE_LOCK, 0);
+ hfs_lock_always(from_cp, HFS_EXCLUSIVE_LOCK);
+ hfs_filedone(from_vp, ap->a_context, HFS_FILE_DONE_NO_SYNC);
+ hfs_unlock(from_cp);
+ hfs_unlock_truncate(from_cp, 0);
+
+ // Flush all the data from the source file
+ error = ubc_msync(from_vp, 0, ubc_getsize(from_vp), NULL,
+ UBC_PUSHALL | UBC_SYNC);
+ if (error)
+ goto exit;
+
+#if HFS_COMPRESSION
+ /*
+ * If this is a compressed file, we need to do the same for
+ * the resource fork.
+ */
+ if (ISSET(from_cp->c_bsdflags, UF_COMPRESSED)) {
+ error = hfs_flush_rsrc(from_vp, ap->a_context);
+ if (error)
+ goto exit;
+ }
+#endif
+
+ /*
+ * We're doing a data-swap so we need to take the truncate
+ * lock exclusively. We need an exclusive lock because we
+ * will be completely truncating the source file and we must
+ * make sure nobody else sneaks in and trys to issue I/O
+ * whilst we don't have the cnode lock.
+ *
+ * After taking the truncate lock we do a quick check to
+ * verify there are no other references (including mmap
+ * references), but we must remember that this does not stop
+ * anybody coming in later and taking a reference. We will
+ * have the truncate lock exclusively so that will prevent
+ * them from issuing any I/O.
+ */
+
+ if (to_cp < from_cp) {
+ hfs_lock_truncate(to_cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
+ have_to_trunc_lock = true;
+ }
+
+ hfs_lock_truncate(from_cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
+ have_from_trunc_lock = true;
+
+ /*
+ * Do an early check to verify the source is not in use by
+ * anyone. We should be called from an FD opened as F_EVTONLY
+ * so that doesn't count as a reference.
+ */
+ if (vnode_isinuse(from_vp, 0)) {
+ error = EBUSY;
+ goto exit;
+ }
+
+ if (to_cp >= from_cp) {
+ hfs_lock_truncate(to_cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
+ have_to_trunc_lock = true;
+ }
+ }
+
+ if ((error = hfs_lockpair(from_cp, to_cp, HFS_EXCLUSIVE_LOCK)))
+ goto exit;
+ have_cnode_locks = true;
+
+ // Don't allow modification of the journal or journal_info_block
+ if (hfs_is_journal_file(hfsmp, from_cp) ||
+ hfs_is_journal_file(hfsmp, to_cp)) {
+ error = EPERM;
+ goto exit;
+ }
+
+ /*
+ * Ok, now that all of the pre-flighting is done, call the underlying
+ * function if needed.
+ */
+ if (ISSET(ap->a_options, FSOPT_EXCHANGE_DATA_ONLY)) {
+#if HFS_COMPRESSION
+ if (ISSET(from_cp->c_bsdflags, UF_COMPRESSED)) {
+ error = hfs_move_compressed(from_cp, to_cp);
+ goto exit;
+ }
+#endif
+
+ error = hfs_move_data(from_cp, to_cp, 0);
+ goto exit;
+ }
+
+ if ((error = hfs_start_transaction(hfsmp)) != 0) {
+ goto exit;
+ }
+ started_tr = 1;
+
+ /*
+ * Reserve some space in the Catalog file.
+ */
+ if ((error = cat_preflight(hfsmp, CAT_EXCHANGE, &cookie, vfs_context_proc(ap->a_context)))) {
+ goto exit;
+ }
+ got_cookie = 1;
+
+ /* The backend code always tries to delete the virtual
+ * extent id for exchanging files so we need to lock
+ * the extents b-tree.
+ */
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_EXTENTS | SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
+
+ /* Account for the location of the catalog objects. */
+ if (from_cp->c_flag & C_HARDLINK) {
+ MAKE_INODE_NAME(from_iname, sizeof(from_iname),
+ from_cp->c_attr.ca_linkref);
+ from_nameptr = (unsigned char *)from_iname;
+ from_parid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
+ from_cp->c_hint = 0;
+ } else {
+ from_nameptr = from_cp->c_desc.cd_nameptr;
+ from_parid = from_cp->c_parentcnid;
+ }
+ if (to_cp->c_flag & C_HARDLINK) {
+ MAKE_INODE_NAME(to_iname, sizeof(to_iname),
+ to_cp->c_attr.ca_linkref);
+ to_nameptr = (unsigned char *)to_iname;
+ to_parid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
+ to_cp->c_hint = 0;
+ } else {
+ to_nameptr = to_cp->c_desc.cd_nameptr;
+ to_parid = to_cp->c_parentcnid;
+ }
+
+ /*
+ * ExchangeFileIDs swaps the on-disk, or in-BTree 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 ExchangeFileIDs returns successfully, "file1" will have fileID 52,
+ * and "file2" will have fileID 50. However, note that this is only
+ * approximately half of the work that exchangedata(2) will need to
+ * accomplish. In other words, we swap "too much" of the information
+ * because if we only called ExchangeFileIDs, both the fileID and extent
+ * information would be the invariants of this operation. We don't
+ * actually want that; we want to conclude with "file1" having
+ * file ID 50, and "file2" having fileID 52.
+ *
+ * The remainder of hfs_vnop_exchange will swap the file ID and other cnode
+ * data back to the proper ownership, while still allowing the cnode to remain
+ * pointing at the same set of extents that it did originally.
+ */
+ error = ExchangeFileIDs(hfsmp, from_nameptr, to_nameptr, from_parid,
+ to_parid, from_cp->c_hint, to_cp->c_hint);
+ hfs_systemfile_unlock(hfsmp, lockflags);
+
+ /*
+ * Note that we don't need to exchange any extended attributes
+ * since the attributes are keyed by file ID.
+ */
+
+ if (error != E_NONE) {
+ error = MacToVFSError(error);
+ goto exit;
+ }
+
+ /* Purge the vnodes from the name cache */
+ if (from_vp)
+ cache_purge(from_vp);
+ if (to_vp)
+ cache_purge(to_vp);
+
+ /* 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));
+
+ /* 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);
+
+ /*
+ * Now 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, 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_ctime = to_cp->c_ctime;
+ from_cp->c_gid = to_cp->c_gid;
+ from_cp->c_uid = to_cp->c_uid;
+ 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_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;
+ /*
+ * 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_ctime = tempattr.ca_ctime;
+ to_cp->c_gid = tempattr.ca_gid;
+ to_cp->c_uid = tempattr.ca_uid;
+ to_cp->c_bsdflags = tempattr.ca_flags;
+ to_cp->c_mode = tempattr.ca_mode;
+ to_cp->c_linkcount = tempattr.ca_linkcount;
+ 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);
+
+ /*
+ * When a file moves out of "Cleanup At Startup"
+ * we can drop its NODUMP status.
+ */
+ if ((from_cp->c_bsdflags & UF_NODUMP) &&
+ (from_cp->c_parentcnid != to_cp->c_parentcnid)) {
+ from_cp->c_bsdflags &= ~UF_NODUMP;
+ from_cp->c_touch_chgtime = TRUE;
+ }
+ if ((to_cp->c_bsdflags & UF_NODUMP) &&
+ (to_cp->c_parentcnid != from_cp->c_parentcnid)) {
+ to_cp->c_bsdflags &= ~UF_NODUMP;
+ to_cp->c_touch_chgtime = TRUE;
+ }
+
+exit:
+ if (got_cookie) {
+ cat_postflight(hfsmp, &cookie, vfs_context_proc(ap->a_context));
+ }
+ if (started_tr) {
+ hfs_end_transaction(hfsmp);
+ }
+
+ if (have_from_trunc_lock)
+ hfs_unlock_truncate(from_cp, 0);
+
+ if (have_to_trunc_lock)
+ hfs_unlock_truncate(to_cp, 0);
+
+ if (have_cnode_locks)
+ hfs_unlockpair(from_cp, to_cp);
+
+ return (error);
+}
+
+#if HFS_COMPRESSION
+/*
+ * This function is used specifically for the case when a namespace
+ * handler is trying to steal data before it's deleted. Note that we
+ * don't bother deleting the xattr from the source because it will get
+ * deleted a short time later anyway.
+ *
+ * cnodes must be locked
+ */
+static int hfs_move_compressed(cnode_t *from_cp, cnode_t *to_cp)
+{
+ int ret;
+ void *data = NULL;
+
+ CLR(from_cp->c_bsdflags, UF_COMPRESSED);
+ SET(from_cp->c_flag, C_MODIFIED);
+
+ ret = hfs_move_data(from_cp, to_cp, HFS_MOVE_DATA_INCLUDE_RSRC);
+ if (ret)
+ goto exit;
+
+ /*
+ * Transfer the xattr that decmpfs uses. Ideally, this code
+ * should be with the other decmpfs code but it's file system
+ * agnostic and this path is currently, and likely to remain, HFS+
+ * specific. It's easier and more performant if we implement it
+ * here.
+ */
+
+ size_t size = MAX_DECMPFS_XATTR_SIZE;
+ MALLOC(data, void *, size, M_TEMP, M_WAITOK);
+
+ ret = hfs_xattr_read(from_cp->c_vp, DECMPFS_XATTR_NAME, data, &size);
+ if (ret)
+ goto exit;
+
+ ret = hfs_xattr_write(to_cp->c_vp, DECMPFS_XATTR_NAME, data, size);
+ if (ret)
+ goto exit;
+
+ SET(to_cp->c_bsdflags, UF_COMPRESSED);
+ SET(to_cp->c_flag, C_MODIFIED);
+
+exit:
+ if (data)
+ FREE(data, M_TEMP);
+
+ return ret;
+}
+#endif // HFS_COMPRESSION
+
+int
+hfs_vnop_mmap(struct vnop_mmap_args *ap)
+{
+ struct vnode *vp = ap->a_vp;
+ cnode_t *cp = VTOC(vp);
+ int error;
+
+ if (VNODE_IS_RSRC(vp)) {
+ /* allow pageins of the resource fork */
+ } else {
+ int compressed = hfs_file_is_compressed(cp, 1); /* 1 == don't take the cnode lock */
+ time_t orig_ctime = cp->c_ctime;
+
+ if (!compressed && (cp->c_bsdflags & UF_COMPRESSED)) {
+ error = check_for_dataless_file(vp, NAMESPACE_HANDLER_READ_OP);
+ if (error != 0) {
+ return error;
+ }
+ }
+
+ if (ap->a_fflags & PROT_WRITE) {
+ check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_WRITE_OP, NULL);
+ }
+ }
+
+ //
+ // NOTE: we return ENOTSUP because we want the cluster layer
+ // to actually do all the real work.
+ //
+ return (ENOTSUP);
+}
+
+static errno_t hfs_vnop_mnomap(struct vnop_mnomap_args *ap)
+{
+ vnode_t vp = ap->a_vp;
+
+ /*
+ * Whilst the file was mapped, there may not have been any
+ * page-outs so we need to increment the generation counter now.
+ * Unfortunately this may lead to a change in the generation
+ * counter when no actual change has been made, but there is
+ * little we can do about that with our current architecture.
+ */
+ if (ubc_is_mapped_writable(vp)) {
+ cnode_t *cp = VTOC(vp);
+ hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS);
+ hfs_incr_gencount(cp);
+
+ /*
+ * We don't want to set the modification time here since a
+ * change to that is not acceptable if no changes were made.
+ * Instead we set a flag so that if we get any page-outs we
+ * know to update the modification time. It's possible that
+ * they weren't actually because of changes made whilst the
+ * file was mapped but that's not easy to fix now.
+ */
+ SET(cp->c_flag, C_MIGHT_BE_DIRTY_FROM_MAPPING);
+
+ hfs_unlock(cp);
+ }
+
+ return 0;
+}
+
+/*
+ * Mark the resource fork as needing a ubc_setsize when we drop the
+ * cnode lock later.
+ */
+static void hfs_rsrc_setsize(cnode_t *cp)
+{
+ /*
+ * We need to take an iocount if we don't have one. vnode_get
+ * will return ENOENT if the vnode is terminating which is what we
+ * want as it's not safe to call ubc_setsize in that case.
+ */
+ if (cp->c_rsrc_vp && !vnode_get(cp->c_rsrc_vp)) {
+ // Shouldn't happen, but better safe...
+ if (ISSET(cp->c_flag, C_NEED_RVNODE_PUT))
+ vnode_put(cp->c_rsrc_vp);
+ SET(cp->c_flag, C_NEED_RVNODE_PUT | C_NEED_RSRC_SETSIZE);
+ }
+}
+
+/*
+ * hfs_move_data
+ *
+ * This is a non-symmetric variant of exchangedata. In this function,
+ * the contents of the data fork (and optionally the resource fork)
+ * are moved from from_cp to to_cp.
+ *
+ * The cnodes must be locked.
+ *
+ * The cnode pointed to by 'to_cp' *must* be empty prior to invoking
+ * this function. We impose this restriction because we may not be
+ * able to fully delete the entire file's contents in a single
+ * transaction, particularly if it has a lot of extents. In the
+ * normal file deletion codepath, the file is screened for two
+ * conditions: 1) bigger than 400MB, and 2) more than 8 extents. If
+ * so, the file is relocated to the hidden directory and the deletion
+ * is broken up into multiple truncates. We can't do that here
+ * because both files need to exist in the namespace. The main reason
+ * this is imposed is that we may have to touch a whole lot of bitmap
+ * blocks if there are many extents.
+ *
+ * Any data written to 'from_cp' after this call completes is not
+ * guaranteed to be moved.
+ *
+ * Arguments:
+ * cnode_t *from_cp : source file
+ * cnode_t *to_cp : destination file; must be empty
+ *
+ * Returns:
+ *
+ * EBUSY - File has been deleted or is in use
+ * EFBIG - Destination file was not empty
+ * EIO - An I/O error
+ * 0 - success
+ * other - Other errors that can be returned from called functions
+ */
+int hfs_move_data(cnode_t *from_cp, cnode_t *to_cp,
+ hfs_move_data_options_t options)
+{
+ hfsmount_t *hfsmp = VTOHFS(from_cp->c_vp);
+ int error = 0;
+ int lockflags = 0;
+ bool return_EIO_on_error = false;
+ const bool include_rsrc = ISSET(options, HFS_MOVE_DATA_INCLUDE_RSRC);
+
+ /* Verify that neither source/dest file is open-unlinked */
+ if (ISSET(from_cp->c_flag, C_DELETED | C_NOEXISTS)
+ || ISSET(to_cp->c_flag, C_DELETED | C_NOEXISTS)) {
+ return EBUSY;
+ }
+
+ /*
+ * Verify the source file is not in use by anyone besides us.
+ *
+ * This function is typically invoked by a namespace handler
+ * process responding to a temporarily stalled system call.
+ * The FD that it is working off of is opened O_EVTONLY, so
+ * it really has no active usecounts (the kusecount from O_EVTONLY
+ * is subtracted from the total usecounts).
+ *
+ * As a result, we shouldn't have any active usecounts against
+ * this vnode when we go to check it below.
+ */
+ if (vnode_isinuse(from_cp->c_vp, 0))
+ return EBUSY;
+
+ if (include_rsrc && from_cp->c_rsrc_vp) {
+ if (vnode_isinuse(from_cp->c_rsrc_vp, 0))
+ return EBUSY;
+
+ /*
+ * In the code below, if the destination file doesn't have a
+ * c_rsrcfork then we don't create it which means we we cannot
+ * transfer the ff_invalidranges and cf_vblocks fields. These
+ * shouldn't be set because we flush the resource fork before
+ * calling this function but there is a tiny window when we
+ * did not have any locks...
+ */
+ if (!to_cp->c_rsrcfork
+ && (!TAILQ_EMPTY(&from_cp->c_rsrcfork->ff_invalidranges)
+ || from_cp->c_rsrcfork->ff_unallocblocks)) {
+ /*
+ * The file isn't really busy now but something did slip
+ * in and tinker with the file while we didn't have any
+ * locks, so this is the most meaningful return code for
+ * the caller.
+ */
+ return EBUSY;
+ }
+ }
+
+ // Check the destination file is empty
+ if (to_cp->c_datafork->ff_blocks
+ || to_cp->c_datafork->ff_size
+ || (include_rsrc
+ && (to_cp->c_blocks
+ || (to_cp->c_rsrcfork && to_cp->c_rsrcfork->ff_size)))) {
+ return EFBIG;
+ }
+
+ if ((error = hfs_start_transaction (hfsmp)))
+ return error;
+
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_EXTENTS | SFL_ATTRIBUTE,
+ HFS_EXCLUSIVE_LOCK);
+
+ // filefork_t is 128 bytes which should be OK
+ filefork_t rfork_buf, *from_rfork = NULL;
+
+ if (include_rsrc) {
+ from_rfork = from_cp->c_rsrcfork;
+
+ /*
+ * Creating resource fork vnodes is expensive, so just get get
+ * the fork data if we need it.
+ */
+ if (!from_rfork && hfs_has_rsrc(from_cp)) {
+ from_rfork = &rfork_buf;
+
+ from_rfork->ff_cp = from_cp;
+ TAILQ_INIT(&from_rfork->ff_invalidranges);
+
+ error = cat_idlookup(hfsmp, from_cp->c_fileid, 0, 1, NULL, NULL,
+ &from_rfork->ff_data);
+
+ if (error)
+ goto exit;
+ }
+ }
+
+ /*
+ * From here on, any failures mean that we might be leaving things
+ * in a weird or inconsistent state. Ideally, we should back out
+ * all the changes, but to do that properly we need to fix
+ * MoveData. We'll save fixing that for another time. For now,
+ * just return EIO in all cases to the caller so that they know.
+ */
+ return_EIO_on_error = true;
+
+ bool data_overflow_extents = overflow_extents(from_cp->c_datafork);
+
+ // Move the data fork
+ if ((error = hfs_move_fork (from_cp->c_datafork, from_cp,
+ to_cp->c_datafork, to_cp))) {
+ goto exit;
+ }
+
+ SET(from_cp->c_flag, C_NEED_DATA_SETSIZE);
+ SET(to_cp->c_flag, C_NEED_DATA_SETSIZE);
+
+ // We move the resource fork later
+
+ /*
+ * Note that because all we're doing is moving the extents around,
+ * we can probably do this in a single transaction: Each extent
+ * record (group of 8) is 64 bytes. A extent overflow B-Tree node
+ * is typically 4k. This means each node can hold roughly ~60
+ * extent records == (480 extents).
+ *
+ * If a file was massively fragmented and had 20k extents, this
+ * means we'd roughly touch 20k/480 == 41 to 42 nodes, plus the
+ * index nodes, for half of the operation. (inserting or
+ * deleting). So if we're manipulating 80-100 nodes, this is
+ * basically 320k of data to write to the journal in a bad case.
+ */
+ if (data_overflow_extents) {
+ if ((error = MoveData(hfsmp, from_cp->c_cnid, to_cp->c_cnid, 0)))
+ goto exit;
+ }