+errno_t
+vn_rename(struct vnode *fdvp, struct vnode **fvpp, struct componentname *fcnp, struct vnode_attr *fvap,
+ struct vnode *tdvp, struct vnode **tvpp, struct componentname *tcnp, struct vnode_attr *tvap,
+ uint32_t flags, vfs_context_t ctx)
+{
+ int _err;
+ vnode_t src_attr_vp = NULLVP;
+ vnode_t dst_attr_vp = NULLVP;
+ struct nameidata *fromnd = NULL;
+ struct nameidata *tond = NULL;
+ char smallname1[48];
+ char smallname2[48];
+ char *xfromname = NULL;
+ char *xtoname = NULL;
+ int batched;
+
+ batched = vnode_compound_rename_available(fdvp);
+
+#if CONFIG_VFS_FUNNEL
+ vnode_t fdvp_unsafe = (THREAD_SAFE_FS(fdvp) ? NULLVP : fdvp);
+#endif /* CONFIG_VFS_FUNNEL */
+
+ if (!batched) {
+ if (*fvpp == NULLVP)
+ panic("Not batched, and no fvp?");
+ }
+
+ /*
+ * We need to preflight any potential AppleDouble file for the source file
+ * before doing the rename operation, since we could potentially be doing
+ * this operation on a network filesystem, and would end up duplicating
+ * the work. Also, save the source and destination names. Skip it if the
+ * source has a "._" prefix.
+ */
+
+ if (!NATIVE_XATTR(fdvp) &&
+ !(fcnp->cn_nameptr[0] == '.' && fcnp->cn_nameptr[1] == '_')) {
+ size_t len;
+ int error;
+
+ /* Get source attribute file name. */
+ len = fcnp->cn_namelen + 3;
+ if (len > sizeof(smallname1)) {
+ MALLOC(xfromname, char *, len, M_TEMP, M_WAITOK);
+ } else {
+ xfromname = &smallname1[0];
+ }
+ strlcpy(xfromname, "._", min(sizeof smallname1, len));
+ strncat(xfromname, fcnp->cn_nameptr, fcnp->cn_namelen);
+ xfromname[len-1] = '\0';
+
+ /* Get destination attribute file name. */
+ len = tcnp->cn_namelen + 3;
+ if (len > sizeof(smallname2)) {
+ MALLOC(xtoname, char *, len, M_TEMP, M_WAITOK);
+ } else {
+ xtoname = &smallname2[0];
+ }
+ strlcpy(xtoname, "._", min(sizeof smallname2, len));
+ strncat(xtoname, tcnp->cn_nameptr, tcnp->cn_namelen);
+ xtoname[len-1] = '\0';
+
+ /*
+ * Look up source attribute file, keep reference on it if exists.
+ * Note that we do the namei with the nameiop of RENAME, which is different than
+ * in the rename syscall. It's OK if the source file does not exist, since this
+ * is only for AppleDouble files.
+ */
+ if (xfromname != NULL) {
+ MALLOC(fromnd, struct nameidata *, sizeof (struct nameidata), M_TEMP, M_WAITOK);
+ NDINIT(fromnd, RENAME, OP_RENAME, NOFOLLOW | USEDVP | CN_NBMOUNTLOOK,
+ UIO_SYSSPACE, CAST_USER_ADDR_T(xfromname), ctx);
+ fromnd->ni_dvp = fdvp;
+ error = namei(fromnd);
+
+ /*
+ * If there was an error looking up source attribute file,
+ * we'll behave as if it didn't exist.
+ */
+
+ if (error == 0) {
+ if (fromnd->ni_vp) {
+ /* src_attr_vp indicates need to call vnode_put / nameidone later */
+ src_attr_vp = fromnd->ni_vp;
+
+ if (fromnd->ni_vp->v_type != VREG) {
+ src_attr_vp = NULLVP;
+ vnode_put(fromnd->ni_vp);
+ }
+ }
+ /*
+ * Either we got an invalid vnode type (not a regular file) or the namei lookup
+ * suppressed ENOENT as a valid error since we're renaming. Either way, we don't
+ * have a vnode here, so we drop our namei buffer for the source attribute file
+ */
+ if (src_attr_vp == NULLVP) {
+ nameidone(fromnd);
+ }
+ }
+ }
+ }
+
+ if (batched) {
+ _err = VNOP_COMPOUND_RENAME(fdvp, fvpp, fcnp, fvap, tdvp, tvpp, tcnp, tvap, flags, ctx);
+ if (_err != 0) {
+ printf("VNOP_COMPOUND_RENAME() returned %d\n", _err);
+ }
+
+ } else {
+ _err = VNOP_RENAME(fdvp, *fvpp, fcnp, tdvp, *tvpp, tcnp, ctx);
+ }
+
+ if (_err == 0) {
+ mac_vnode_notify_rename(ctx, *fvpp, tdvp, tcnp);
+ }
+
+ /*
+ * Rename any associated extended attribute file (._ AppleDouble file).
+ */
+ if (_err == 0 && !NATIVE_XATTR(fdvp) && xfromname != NULL) {
+ int error = 0;
+
+ /*
+ * Get destination attribute file vnode.
+ * Note that tdvp already has an iocount reference. Make sure to check that we
+ * get a valid vnode from namei.
+ */
+ MALLOC(tond, struct nameidata *, sizeof(struct nameidata), M_TEMP, M_WAITOK);
+ NDINIT(tond, RENAME, OP_RENAME,
+ NOCACHE | NOFOLLOW | USEDVP | CN_NBMOUNTLOOK, UIO_SYSSPACE,
+ CAST_USER_ADDR_T(xtoname), ctx);
+ tond->ni_dvp = tdvp;
+ error = namei(tond);
+
+ if (error)
+ goto out;
+
+ if (tond->ni_vp) {
+ dst_attr_vp = tond->ni_vp;
+ }
+
+ if (src_attr_vp) {
+ const char *old_name = src_attr_vp->v_name;
+ vnode_t old_parent = src_attr_vp->v_parent;
+
+ if (batched) {
+ error = VNOP_COMPOUND_RENAME(fdvp, &src_attr_vp, &fromnd->ni_cnd, NULL,
+ tdvp, &dst_attr_vp, &tond->ni_cnd, NULL,
+ 0, ctx);
+ } else {
+ error = VNOP_RENAME(fdvp, src_attr_vp, &fromnd->ni_cnd,
+ tdvp, dst_attr_vp, &tond->ni_cnd, ctx);
+ }
+
+ if (error == 0 && old_name == src_attr_vp->v_name &&
+ old_parent == src_attr_vp->v_parent) {
+ int update_flags = VNODE_UPDATE_NAME;
+
+ if (fdvp != tdvp)
+ update_flags |= VNODE_UPDATE_PARENT;
+
+ vnode_update_identity(src_attr_vp, tdvp,
+ tond->ni_cnd.cn_nameptr,
+ tond->ni_cnd.cn_namelen,
+ tond->ni_cnd.cn_hash,
+ update_flags);
+ }
+
+ /* kevent notifications for moving resource files
+ * _err is zero if we're here, so no need to notify directories, code
+ * below will do that. only need to post the rename on the source and
+ * possibly a delete on the dest
+ */
+ post_event_if_success(src_attr_vp, error, NOTE_RENAME);
+ if (dst_attr_vp) {
+ post_event_if_success(dst_attr_vp, error, NOTE_DELETE);
+ }
+
+ } else if (dst_attr_vp) {
+ /*
+ * Just delete destination attribute file vnode if it exists, since
+ * we didn't have a source attribute file.
+ * Note that tdvp already has an iocount reference.
+ */
+
+ struct vnop_remove_args args;
+
+ args.a_desc = &vnop_remove_desc;
+ args.a_dvp = tdvp;
+ args.a_vp = dst_attr_vp;
+ args.a_cnp = &tond->ni_cnd;
+ args.a_context = ctx;
+
+#if CONFIG_VFS_FUNNEL
+ if (fdvp_unsafe != NULLVP)
+ error = lock_fsnode(dst_attr_vp, NULL);
+#endif /* CONFIG_VFS_FUNNEL */
+ if (error == 0) {
+ error = (*tdvp->v_op[vnop_remove_desc.vdesc_offset])(&args);
+
+#if CONFIG_VFS_FUNNEL
+ if (fdvp_unsafe != NULLVP)
+ unlock_fsnode(dst_attr_vp, NULL);
+#endif /* CONFIG_VFS_FUNNEL */
+
+ if (error == 0)
+ vnode_setneedinactive(dst_attr_vp);
+ }
+
+ /* kevent notification for deleting the destination's attribute file
+ * if it existed. Only need to post the delete on the destination, since
+ * the code below will handle the directories.
+ */
+ post_event_if_success(dst_attr_vp, error, NOTE_DELETE);
+ }
+ }
+out:
+ if (src_attr_vp) {
+ vnode_put(src_attr_vp);
+ nameidone(fromnd);
+ }
+ if (dst_attr_vp) {
+ vnode_put(dst_attr_vp);
+ nameidone(tond);
+ }
+ if (fromnd) {
+ FREE(fromnd, M_TEMP);
+ }
+ if (tond) {
+ FREE(tond, M_TEMP);
+ }
+ if (xfromname && xfromname != &smallname1[0]) {
+ FREE(xfromname, M_TEMP);
+ }
+ if (xtoname && xtoname != &smallname2[0]) {
+ FREE(xtoname, M_TEMP);
+ }
+
+ return _err;
+}
+