Skip to content

Commit

Permalink
improve performance
Browse files Browse the repository at this point in the history
  • Loading branch information
adamsitnik authored Jan 12, 2022
1 parent 373a4a2 commit 02a8664
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 49 deletions.
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);
}
}
}

0 comments on commit 02a8664

Please sign in to comment.