NTSTATUS rename_internals_fsp(connection_struct *conn,
			files_struct *fsp,
			struct smb_filename *smb_fname_dst_in,
			const char *dst_original_lcomp,
			uint32_t attrs,
			bool replace_if_exists)
{
	TALLOC_CTX *ctx = talloc_tos();
	struct smb_filename *parent_dir_fname_dst = NULL;
	struct smb_filename *parent_dir_fname_dst_atname = NULL;
	struct smb_filename *parent_dir_fname_src = NULL;
	struct smb_filename *parent_dir_fname_src_atname = NULL;
	struct smb_filename *smb_fname_dst = NULL;
	NTSTATUS status = NT_STATUS_OK;
	struct share_mode_lock *lck = NULL;
	uint32_t access_mask = SEC_DIR_ADD_FILE;
	bool dst_exists, old_is_stream, new_is_stream;
	int ret;
	bool case_sensitive = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) ?
				true : conn->case_sensitive;
	bool case_preserve = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) ?
				true : conn->case_preserve;

	status = parent_dirname_compatible_open(conn, smb_fname_dst_in);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	if (file_has_open_streams(fsp)) {
		return NT_STATUS_ACCESS_DENIED;
	}

	/* Make a copy of the dst smb_fname structs */

	smb_fname_dst = cp_smb_filename(ctx, smb_fname_dst_in);
	if (smb_fname_dst == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto out;
	}

	/*
	 * Check for special case with case preserving and not
	 * case sensitive. If the new last component differs from the original
	 * last component only by case, then we should allow
	 * the rename (user is trying to change the case of the
	 * filename).
	 */
	if (!case_sensitive && case_preserve &&
	    strequal(fsp->fsp_name->base_name, smb_fname_dst->base_name) &&
	    strequal(fsp->fsp_name->stream_name, smb_fname_dst->stream_name)) {
		char *fname_dst_parent = NULL;
		const char *fname_dst_lcomp = NULL;
		char *orig_lcomp_path = NULL;
		char *orig_lcomp_stream = NULL;
		bool ok = true;

		/*
		 * Split off the last component of the processed
		 * destination name. We will compare this to
		 * the split components of dst_original_lcomp.
		 */
		if (!parent_dirname(ctx,
				smb_fname_dst->base_name,
				&fname_dst_parent,
				&fname_dst_lcomp)) {
			status = NT_STATUS_NO_MEMORY;
			goto out;
		}

		/*
		 * The dst_original_lcomp component contains
		 * the last_component of the path + stream
		 * name (if a stream exists).
		 *
		 * Split off the stream name so we
		 * can check them separately.
		 */

		if (fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES) {
			/* POSIX - no stream component. */
			orig_lcomp_path = talloc_strdup(ctx,
						dst_original_lcomp);
			if (orig_lcomp_path == NULL) {
				ok = false;
			}
		} else {
			ok = split_stream_filename(ctx,
					dst_original_lcomp,
					&orig_lcomp_path,
					&orig_lcomp_stream);
		}

		if (!ok) {
			TALLOC_FREE(fname_dst_parent);
			status = NT_STATUS_NO_MEMORY;
			goto out;
		}

		/* If the base names only differ by case, use original. */
		if(!strcsequal(fname_dst_lcomp, orig_lcomp_path)) {
			char *tmp;
			/*
			 * Replace the modified last component with the
			 * original.
			 */
			if (!ISDOT(fname_dst_parent)) {
				tmp = talloc_asprintf(smb_fname_dst,
					"%s/%s",
					fname_dst_parent,
					orig_lcomp_path);
			} else {
				tmp = talloc_strdup(smb_fname_dst,
					orig_lcomp_path);
			}
			if (tmp == NULL) {
				status = NT_STATUS_NO_MEMORY;
				TALLOC_FREE(fname_dst_parent);
				TALLOC_FREE(orig_lcomp_path);
				TALLOC_FREE(orig_lcomp_stream);
				goto out;
			}
			TALLOC_FREE(smb_fname_dst->base_name);
			smb_fname_dst->base_name = tmp;
		}

		/* If the stream_names only differ by case, use original. */
		if(!strcsequal(smb_fname_dst->stream_name,
			       orig_lcomp_stream)) {
			/* Use the original stream. */
			char *tmp = talloc_strdup(smb_fname_dst,
					    orig_lcomp_stream);
			if (tmp == NULL) {
				status = NT_STATUS_NO_MEMORY;
				TALLOC_FREE(fname_dst_parent);
				TALLOC_FREE(orig_lcomp_path);
				TALLOC_FREE(orig_lcomp_stream);
				goto out;
			}
			TALLOC_FREE(smb_fname_dst->stream_name);
			smb_fname_dst->stream_name = tmp;
		}
		TALLOC_FREE(fname_dst_parent);
		TALLOC_FREE(orig_lcomp_path);
		TALLOC_FREE(orig_lcomp_stream);
	}

	/*
	 * If the src and dest names are identical - including case,
	 * don't do the rename, just return success.
	 */

	if (strcsequal(fsp->fsp_name->base_name, smb_fname_dst->base_name) &&
	    strcsequal(fsp->fsp_name->stream_name,
		       smb_fname_dst->stream_name)) {
		DEBUG(3, ("rename_internals_fsp: identical names in rename %s "
			  "- returning success\n",
			  smb_fname_str_dbg(smb_fname_dst)));
		status = NT_STATUS_OK;
		goto out;
	}

	old_is_stream = is_ntfs_stream_smb_fname(fsp->fsp_name);
	new_is_stream = is_ntfs_stream_smb_fname(smb_fname_dst);

	/* Return the correct error code if both names aren't streams. */
	if (!old_is_stream && new_is_stream) {
		status = NT_STATUS_OBJECT_NAME_INVALID;
		goto out;
	}

	if (old_is_stream && !new_is_stream) {
		status = NT_STATUS_INVALID_PARAMETER;
		goto out;
	}

	dst_exists = vfs_stat(conn, smb_fname_dst) == 0;

	if(!replace_if_exists && dst_exists) {
		DEBUG(3, ("rename_internals_fsp: dest exists doing rename "
			  "%s -> %s\n", smb_fname_str_dbg(fsp->fsp_name),
			  smb_fname_str_dbg(smb_fname_dst)));
		status = NT_STATUS_OBJECT_NAME_COLLISION;
		goto out;
	}

	/*
	 * Drop the pathref fsp on the destination otherwise we trip upon in in
	 * the below check for open files check.
	 */
	if (smb_fname_dst_in->fsp != NULL) {
		fd_close(smb_fname_dst_in->fsp);
		file_free(NULL, smb_fname_dst_in->fsp);
		SMB_ASSERT(smb_fname_dst_in->fsp == NULL);
	}

	if (dst_exists) {
		struct file_id fileid = vfs_file_id_from_sbuf(conn,
		    &smb_fname_dst->st);
		files_struct *dst_fsp = file_find_di_first(conn->sconn,
							   fileid, true);
		/* The file can be open when renaming a stream */
		if (dst_fsp && !new_is_stream) {
			DEBUG(3, ("rename_internals_fsp: Target file open\n"));
			status = NT_STATUS_ACCESS_DENIED;
			goto out;
		}
	}

	/* Ensure we have a valid stat struct for the source. */
	status = vfs_stat_fsp(fsp);
	if (!NT_STATUS_IS_OK(status)) {
		goto out;
	}

	status = can_rename(conn, fsp, attrs);

	if (!NT_STATUS_IS_OK(status)) {
		DEBUG(3, ("rename_internals_fsp: Error %s rename %s -> %s\n",
			  nt_errstr(status), smb_fname_str_dbg(fsp->fsp_name),
			  smb_fname_str_dbg(smb_fname_dst)));
		if (NT_STATUS_EQUAL(status,NT_STATUS_SHARING_VIOLATION))
			status = NT_STATUS_ACCESS_DENIED;
		goto out;
	}

	if (rename_path_prefix_equal(fsp->fsp_name, smb_fname_dst)) {
		status = NT_STATUS_ACCESS_DENIED;
		goto out;
	}

	/* Do we have rights to move into the destination ? */
	if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
		/* We're moving a directory. */
		access_mask = SEC_DIR_ADD_SUBDIR;
	}

	/*
	 * Get a pathref on the destination parent directory, so
	 * we can call check_parent_access_fsp().
	 */
	status = parent_pathref(ctx,
				conn->cwd_fsp,
				smb_fname_dst,
				&parent_dir_fname_dst,
				&parent_dir_fname_dst_atname);
	if (!NT_STATUS_IS_OK(status)) {
		goto out;
	}

	status = check_parent_access_fsp(parent_dir_fname_dst->fsp,
				access_mask);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_INFO("check_parent_access_fsp on "
			"dst %s returned %s\n",
			smb_fname_str_dbg(smb_fname_dst),
			nt_errstr(status));
		goto out;
	}

	/*
	 * If the target existed, make sure the destination
	 * atname has the same stat struct.
	 */
	parent_dir_fname_dst_atname->st = smb_fname_dst->st;

	/*
	 * It's very common that source and
	 * destination directories are the same.
	 * Optimize by not opening the
	 * second parent_pathref if we know
	 * this is the case.
	 */

	status = SMB_VFS_PARENT_PATHNAME(conn,
					 ctx,
					 fsp->fsp_name,
					 &parent_dir_fname_src,
					 &parent_dir_fname_src_atname);
	if (!NT_STATUS_IS_OK(status)) {
		goto out;
	}

	/*
	 * We do a case-sensitive string comparison. We want to be *sure*
	 * this is the same path. The worst that can happen if
	 * the case doesn't match is we lose out on the optimization,
	 * the code still works.
	 *
	 * We can ignore twrp fields here. Rename is not allowed on
	 * shadow copy handles.
	 */

	if (strcmp(parent_dir_fname_src->base_name,
		   parent_dir_fname_dst->base_name) == 0) {
		/*
		 * parent directory is the same for source
		 * and destination.
		 */
		/* Reparent the src_atname to the parent_dir_dest fname. */
		parent_dir_fname_src_atname = talloc_move(
						parent_dir_fname_dst,
						&parent_dir_fname_src_atname);
		/* Free the unneeded duplicate parent name. */
		TALLOC_FREE(parent_dir_fname_src);
		/*
		 * And make the source parent name a copy of the
		 * destination parent name.
		 */
		parent_dir_fname_src = parent_dir_fname_dst;
	} else {
		/*
		 * source and destination parent directories are
		 * different.
		 *
		 * Get a pathref on the source parent directory, so
		 * we can do a relative rename.
		 */
		TALLOC_FREE(parent_dir_fname_src);
		status = parent_pathref(ctx,
				conn->cwd_fsp,
				fsp->fsp_name,
				&parent_dir_fname_src,
				&parent_dir_fname_src_atname);
		if (!NT_STATUS_IS_OK(status)) {
			goto out;
		}
	}

	/*
	 * Some modules depend on the source smb_fname having a valid stat.
	 * The parent_dir_fname_src_atname is the relative name of the
	 * currently open file, so just copy the stat from the open fsp.
	 */
	parent_dir_fname_src_atname->st = fsp->fsp_name->st;

	lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);

	/*
	 * We have the file open ourselves, so not being able to get the
	 * corresponding share mode lock is a fatal error.
	 */

	SMB_ASSERT(lck != NULL);

	ret = SMB_VFS_RENAMEAT(conn,
			parent_dir_fname_src->fsp,
			parent_dir_fname_src_atname,
			parent_dir_fname_dst->fsp,
			parent_dir_fname_dst_atname);
	if (ret == 0) {
		uint32_t create_options = fh_get_private_options(fsp->fh);

		DEBUG(3, ("rename_internals_fsp: succeeded doing rename on "
			  "%s -> %s\n", smb_fname_str_dbg(fsp->fsp_name),
			  smb_fname_str_dbg(smb_fname_dst)));

		notify_rename(conn,
			      fsp->fsp_flags.is_directory,
			      fsp->fsp_name,
			      smb_fname_dst);

		rename_open_files(conn, lck, fsp->file_id, fsp->name_hash,
				  smb_fname_dst);

		if (!fsp->fsp_flags.is_directory &&
		    !(fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES) &&
		    (lp_map_archive(SNUM(conn)) ||
		     lp_store_dos_attributes(SNUM(conn))))
		{
			/*
			 * We must set the archive bit on the newly renamed
			 * file.
			 */
			status = vfs_stat_fsp(fsp);
			if (NT_STATUS_IS_OK(status)) {
				uint32_t old_dosmode;
				old_dosmode = fdos_mode(fsp);
				/*
				 * We can use fsp->fsp_name here as it has
				 * already been changed to the new name.
				 */
				SMB_ASSERT(fsp->fsp_name->fsp == fsp);
				file_set_dosmode(conn,
						fsp->fsp_name,
						old_dosmode | FILE_ATTRIBUTE_ARCHIVE,
						NULL,
						true);
			}
		}

		/*
		 * A rename acts as a new file create w.r.t. allowing an initial delete
		 * on close, probably because in Windows there is a new handle to the
		 * new file. If initial delete on close was requested but not
		 * originally set, we need to set it here. This is probably not 100% correct,
		 * but will work for the CIFSFS client which in non-posix mode
		 * depends on these semantics. JRA.
		 */

		if (create_options & FILE_DELETE_ON_CLOSE) {
			status = can_set_delete_on_close(fsp, 0);

			if (NT_STATUS_IS_OK(status)) {
				/* Note that here we set the *initial* delete on close flag,
				 * not the regular one. The magic gets handled in close. */
				fsp->fsp_flags.initial_delete_on_close = true;
			}
		}
		TALLOC_FREE(lck);
		status = NT_STATUS_OK;
		goto out;
	}

	TALLOC_FREE(lck);

	if (errno == ENOTDIR || errno == EISDIR) {
		status = NT_STATUS_OBJECT_NAME_COLLISION;
	} else {
		status = map_nt_error_from_unix(errno);
	}

	DEBUG(3, ("rename_internals_fsp: Error %s rename %s -> %s\n",
		  nt_errstr(status), smb_fname_str_dbg(fsp->fsp_name),
		  smb_fname_str_dbg(smb_fname_dst)));

 out:

	/*
	 * parent_dir_fname_src may be a copy of parent_dir_fname_dst.
	 * See the optimization for same source and destination directory
	 * above. Only free one in that case.
	 */
	if (parent_dir_fname_src != parent_dir_fname_dst) {
		TALLOC_FREE(parent_dir_fname_src);
	}
	TALLOC_FREE(parent_dir_fname_dst);
	TALLOC_FREE(smb_fname_dst);

	return status;
}