Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More perf #6

Merged
merged 5 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Runtime.InteropServices;
using System.Text;

internal static partial class Interop
{
Expand All @@ -18,5 +19,20 @@ internal static partial class Sys
/// </returns>
[GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Rename", CharSet = CharSet.Ansi, SetLastError = true)]
internal static partial int Rename(string oldPath, string newPath);

[GeneratedDllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Rename", SetLastError = true)]
internal static partial int Rename(ref byte oldPath, ref byte newPath);

internal static int Rename(ReadOnlySpan<char> oldPath, ReadOnlySpan<char> newPath)
{
ValueUtf8Converter converterNewPath = new(stackalloc byte[DefaultPathBufferSize]);
ValueUtf8Converter converterOldPath = new(stackalloc byte[DefaultPathBufferSize]);
int result = Rename(
ref MemoryMarshal.GetReference(converterOldPath.ConvertAndTerminateString(oldPath)),
ref MemoryMarshal.GetReference(converterNewPath.ConvertAndTerminateString(newPath)));
converterNewPath.Dispose();
converterOldPath.Dispose();
return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,7 @@ public static void Move(string sourceDirName, string destDirName)
if (destDirName.Length == 0)
throw new ArgumentException(SR.Argument_EmptyFileName, nameof(destDirName));

string destinationFullPath = Path.GetFullPath(destDirName);
string destinationWithSeparator = PathInternal.EnsureTrailingSeparator(destinationFullPath);

FileSystem.MoveDirectory(Path.GetFullPath(sourceDirName), destinationFullPath, destinationWithSeparator);
FileSystem.MoveDirectory(Path.GetFullPath(sourceDirName), Path.GetFullPath(destDirName));
}

public static void Delete(string path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,11 @@ public void MoveTo(string destDirName)
throw new ArgumentException(SR.Argument_EmptyFileName, nameof(destDirName));

string destination = Path.GetFullPath(destDirName);
string destinationWithSeparator = PathInternal.EnsureTrailingSeparator(destination);

FileSystem.MoveDirectory(FullPath, destination, destinationWithSeparator,
OperatingSystem.IsWindows() ? null : Exists); // on Windows we don't need to perform the extra check
FileSystem.MoveDirectory(FullPath, destination);

Init(originalPath: destDirName,
fullPath: destinationWithSeparator,
fullPath: PathInternal.EnsureTrailingSeparator(destination),
fileName: null,
isNormalized: true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,58 +387,49 @@ private static void CreateParentsAndDirectory(string fullPath)
}
}

private static void MoveDirectory(string sourceFullPath, string destFullPath, bool sameDirectoryDifferentCase, bool? sourceDirectoryExists)
private static void MoveDirectory(string sourceFullPath, string destFullPath, bool sameDirectoryDifferentCase)
{
sourceDirectoryExists ??= DirectoryExists(sourceFullPath);
ReadOnlySpan<char> srcNoDirectorySeparator = Path.TrimEndingDirectorySeparator(sourceFullPath.AsSpan());
ReadOnlySpan<char> destNoDirectorySeparator = Path.TrimEndingDirectorySeparator(destFullPath.AsSpan());

// Windows will throw if the source file/directory doesn't exist, we preemptively check
// to make sure our cross platform behavior matches .NET Framework behavior.
if (!sourceDirectoryExists.Value)
if (!sameDirectoryDifferentCase) // This check is to allow renaming of directories
{
if (!FileExists(sourceFullPath))
{
throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, sourceFullPath));
}

// Windows doesn't care if you try and copy a file via "MoveDirectory"...
// ... but it doesn't like the source to have a trailing slash ...

// On Windows we end up with ERROR_INVALID_NAME, which is
// "The filename, directory name, or volume label syntax is incorrect."
//
// This surfaces as an IOException, if we let it go beyond here it would
// give DirectoryNotFound.

if (Path.EndsInDirectorySeparator(sourceFullPath))
if (Interop.Sys.Stat(destNoDirectorySeparator, out _) >= 0)
{
throw new IOException(SR.Format(SR.IO_PathNotFound_Path, sourceFullPath));
}
// destination exists, but before we throw we need to check whether source exists or not

// ... but it doesn't care if the destination has a trailing separator.
destFullPath = Path.TrimEndingDirectorySeparator(destFullPath);
}
// Windows will throw if the source file/directory doesn't exist, we preemptively check
// to make sure our cross platform behavior matches .NET Framework behavior.
if (Interop.Sys.Stat(srcNoDirectorySeparator, out Interop.Sys.FileStatus sourceFileStatus) < 0)
{
throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, sourceFullPath));
}
else if ((sourceFileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) != Interop.Sys.FileTypes.S_IFDIR
&& Path.EndsInDirectorySeparator(sourceFullPath))
{
throw new IOException(SR.Format(SR.IO_PathNotFound_Path, sourceFullPath));
}

if (!sameDirectoryDifferentCase) // This check is to allow renaming of directories
{
if (DirectoryExists(destFullPath))
{
throw new IOException(SR.Format(SR.IO_AlreadyExists_Name, destFullPath));
}
else if (FileExists(destFullPath))
{
// Some Unix distros will overwrite the destination file if it already exists.
// Throwing IOException to match Windows behavior.
throw new IOException(SR.Format(SR.IO_AlreadyExists_Name, destFullPath));
}
}

if (Interop.Sys.Rename(sourceFullPath, destFullPath) < 0)
if (Interop.Sys.Rename(sourceFullPath, destNoDirectorySeparator) < 0)
{
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
switch (errorInfo.Error)
{
case Interop.Error.EACCES: // match Win32 exception
throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, sourceFullPath), errorInfo.RawErrno);
case Interop.Error.ENOENT:
throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path,
Interop.Sys.Stat(srcNoDirectorySeparator, out _) >= 0
? destFullPath // the source directory exists, so destination does not. Example: Move("/tmp/existing/", "/tmp/nonExisting1/nonExisting2/")
: sourceFullPath));
case Interop.Error.ENOTDIR: // sourceFullPath exists and it's not a directory
throw new IOException(SR.Format(SR.IO_PathNotFound_Path, sourceFullPath));
default:
throw Interop.GetExceptionForIoErrno(errorInfo, isDirectory: true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public static DateTimeOffset GetLastWriteTime(string fullPath)
return data.ftLastWriteTime.ToDateTimeOffset();
}

private static void MoveDirectory(string sourceFullPath, string destFullPath, bool sameDirectoryDifferentCase, bool? sourceDirectoryExists)
private static void MoveDirectory(string sourceFullPath, string destFullPath, bool sameDirectoryDifferentCase)
{
if (!Interop.Kernel32.MoveFile(sourceFullPath, destFullPath, overwrite: false))
{
Expand Down
13 changes: 7 additions & 6 deletions src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ internal static void VerifyValidPath(string path, string argName)
}
}

internal static void MoveDirectory(string sourceFullPath, string destFullPath, string destinationWithSeparator, bool? sourceDirectoryExists = default)
internal static void MoveDirectory(string sourceFullPath, string destFullPath)
{
string sourcePath = PathInternal.EnsureTrailingSeparator(sourceFullPath);
ReadOnlySpan<char> srcNoDirectorySeparator = Path.TrimEndingDirectorySeparator(sourceFullPath.AsSpan());
ReadOnlySpan<char> destNoDirectorySeparator = Path.TrimEndingDirectorySeparator(destFullPath.AsSpan());

ReadOnlySpan<char> sourceDirNameFromFullPath = Path.GetFileName(sourceFullPath.AsSpan());
ReadOnlySpan<char> destDirNameFromFullPath = Path.GetFileName(destFullPath.AsSpan());
Expand All @@ -37,17 +38,17 @@ internal static void MoveDirectory(string sourceFullPath, string destFullPath, s
destDirNameFromFullPath.Equals(sourceDirNameFromFullPath, fileSystemSensitivity);

// If the destination directories are the exact same name
if (!sameDirectoryDifferentCase && string.Equals(sourcePath, destinationWithSeparator, fileSystemSensitivity))
if (!sameDirectoryDifferentCase && srcNoDirectorySeparator.Equals(destNoDirectorySeparator, fileSystemSensitivity))
throw new IOException(SR.IO_SourceDestMustBeDifferent);

ReadOnlySpan<char> sourceRoot = Path.GetPathRoot(sourcePath.AsSpan());
ReadOnlySpan<char> destinationRoot = Path.GetPathRoot(destinationWithSeparator.AsSpan());
ReadOnlySpan<char> sourceRoot = Path.GetPathRoot(srcNoDirectorySeparator);
ReadOnlySpan<char> destinationRoot = Path.GetPathRoot(destNoDirectorySeparator);

// Compare paths for the same, skip this step if we already know the paths are identical.
if (!sourceRoot.Equals(destinationRoot, StringComparison.OrdinalIgnoreCase))
throw new IOException(SR.IO_SourceDestMustHaveSameRoot);

MoveDirectory(sourceFullPath, destFullPath, sameDirectoryDifferentCase, sourceDirectoryExists);
MoveDirectory(sourceFullPath, destFullPath, sameDirectoryDifferentCase);
}
}
}