Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit ea514e2

Browse files
authored
Fix using Directory to move files (#19716)
* Fix using Directory to move files See #19710. NetFX behavior is that you can use Directory to move files. This was broken trying to match other legacy behavior in #17913. Tested on NetFX and CoreFX. * Fix check for Unix Unix impl doesn't tolerate a trailing separator. Public api explicitly checks for this. * More fixes and tests Unix won't move the file with a trailing slash. Add more tests for different combinations of trailing slashes. Validated against NetFX. * Attempt to get MoveDirectory further aligned Trying to handle trailing separator on a source file cases in the same way as Windows. * Extend tests, move Unix condition * Another tweak and more tests * Fix comment
1 parent ce7e645 commit ea514e2

File tree

5 files changed

+124
-18
lines changed

5 files changed

+124
-18
lines changed

src/System.IO.FileSystem/src/System/IO/Directory.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -563,8 +563,10 @@ public static void Move(String sourceDirName, String destDirName)
563563
String destinationRoot = Path.GetPathRoot(destPath);
564564
if (!String.Equals(sourceRoot, destinationRoot, pathComparison))
565565
throw new IOException(SR.IO_SourceDestMustHaveSameRoot);
566-
567-
if (!FileSystem.Current.DirectoryExists(fullsourceDirName))
566+
567+
// Windows will throw if the source file/directory doesn't exist, we preemptively check
568+
// to make sure our cross platform behavior matches NetFX behavior.
569+
if (!FileSystem.Current.DirectoryExists(fullsourceDirName) && !FileSystem.Current.FileExists(fullsourceDirName))
568570
throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, fullsourceDirName));
569571

570572
if (FileSystem.Current.DirectoryExists(fulldestDirName))

src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -379,19 +379,20 @@ public DirectoryInfo Root
379379
}
380380

381381
[System.Security.SecuritySafeCritical]
382-
public void MoveTo(String destDirName)
382+
public void MoveTo(string destDirName)
383383
{
384384
if (destDirName == null)
385385
throw new ArgumentNullException(nameof(destDirName));
386386
if (destDirName.Length == 0)
387387
throw new ArgumentException(SR.Argument_EmptyFileName, nameof(destDirName));
388388
Contract.EndContractBlock();
389389

390-
String fullDestDirName = Path.GetFullPath(destDirName);
391-
if (fullDestDirName[fullDestDirName.Length - 1] != Path.DirectorySeparatorChar)
392-
fullDestDirName = fullDestDirName + PathHelpers.DirectorySeparatorCharAsString;
390+
string destination = Path.GetFullPath(destDirName);
391+
string destinationWithSeparator = destination;
392+
if (destinationWithSeparator[destinationWithSeparator.Length - 1] != Path.DirectorySeparatorChar)
393+
destinationWithSeparator = destinationWithSeparator + PathHelpers.DirectorySeparatorCharAsString;
393394

394-
String fullSourcePath;
395+
string fullSourcePath;
395396
if (FullPath.Length > 0 && FullPath[FullPath.Length - 1] == Path.DirectorySeparatorChar)
396397
fullSourcePath = FullPath;
397398
else
@@ -400,28 +401,30 @@ public void MoveTo(String destDirName)
400401
if (PathInternal.IsDirectoryTooLong(fullSourcePath))
401402
throw new PathTooLongException(SR.IO_PathTooLong);
402403

403-
if (PathInternal.IsDirectoryTooLong(fullDestDirName))
404+
if (PathInternal.IsDirectoryTooLong(destinationWithSeparator))
404405
throw new PathTooLongException(SR.IO_PathTooLong);
405406

406407
StringComparison pathComparison = PathInternal.StringComparison;
407-
if (String.Equals(fullSourcePath, fullDestDirName, pathComparison))
408+
if (string.Equals(fullSourcePath, destinationWithSeparator, pathComparison))
408409
throw new IOException(SR.IO_SourceDestMustBeDifferent);
409410

410-
String sourceRoot = Path.GetPathRoot(fullSourcePath);
411-
String destinationRoot = Path.GetPathRoot(fullDestDirName);
411+
string sourceRoot = Path.GetPathRoot(fullSourcePath);
412+
string destinationRoot = Path.GetPathRoot(destinationWithSeparator);
412413

413-
if (!String.Equals(sourceRoot, destinationRoot, pathComparison))
414+
if (!string.Equals(sourceRoot, destinationRoot, pathComparison))
414415
throw new IOException(SR.IO_SourceDestMustHaveSameRoot);
415416

416-
if (!Exists)
417+
// Windows will throw if the source file/directory doesn't exist, we preemptively check
418+
// to make sure our cross platform behavior matches NetFX behavior.
419+
if (!Exists && !FileSystem.Current.FileExists(FullPath))
417420
throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, FullPath));
418421

419-
if (FileSystem.Current.DirectoryExists(fullDestDirName))
420-
throw new IOException(SR.Format(SR.IO_AlreadyExists_Name, fullDestDirName));
422+
if (FileSystem.Current.DirectoryExists(destinationWithSeparator))
423+
throw new IOException(SR.Format(SR.IO_AlreadyExists_Name, destinationWithSeparator));
421424

422-
FileSystem.Current.MoveDirectory(FullPath, fullDestDirName);
425+
FileSystem.Current.MoveDirectory(FullPath, destination);
423426

424-
FullPath = fullDestDirName;
427+
FullPath = destinationWithSeparator;
425428
OriginalPath = destDirName;
426429
DisplayPath = GetDisplayName(OriginalPath);
427430

src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,24 @@ public override void CreateDirectory(string fullPath)
276276

277277
public override void MoveDirectory(string sourceFullPath, string destFullPath)
278278
{
279+
// Windows doesn't care if you try and copy a file via "MoveDirectory"...
280+
if (FileExists(sourceFullPath))
281+
{
282+
// ... but it doesn't like the source to have a trailing slash ...
283+
284+
// On Windows we end up with ERROR_INVALID_NAME, which is
285+
// "The filename, directory name, or volume label syntax is incorrect."
286+
//
287+
// This surfaces as a IOException, if we let it go beyond here it would
288+
// give DirectoryNotFound.
289+
290+
if (PathHelpers.EndsInDirectorySeparator(sourceFullPath))
291+
throw new IOException(SR.Format(SR.IO_PathNotFound_Path, sourceFullPath));
292+
293+
// ... but it doesn't care if the destination has a trailing separator.
294+
destFullPath = PathHelpers.TrimEndingDirectorySeparator(destFullPath);
295+
}
296+
279297
if (Interop.Sys.Rename(sourceFullPath, destFullPath) < 0)
280298
{
281299
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
@@ -389,7 +407,9 @@ private static bool DirectoryExists(string fullPath, out Interop.ErrorInfo error
389407
public override bool FileExists(string fullPath)
390408
{
391409
Interop.ErrorInfo ignored;
392-
return FileExists(fullPath, Interop.Sys.FileTypes.S_IFREG, out ignored);
410+
411+
// Windows doesn't care about the trailing separator
412+
return FileExists(PathHelpers.TrimEndingDirectorySeparator(fullPath), Interop.Sys.FileTypes.S_IFREG, out ignored);
393413
}
394414

395415
private static bool FileExists(string fullPath, int fileType, out Interop.ErrorInfo errorInfo)

src/System.IO.FileSystem/tests/Directory/Move.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,62 @@ public void MoveOntoExistingDirectory()
5656
Assert.Throws<IOException>(() => Move(testDir.FullName, secondDir.FullName));
5757
}
5858

59+
[Fact]
60+
public void MoveFile()
61+
{
62+
// Regression https://github.com/dotnet/corefx/issues/19710
63+
string source = GetTestFilePath();
64+
string destination = GetTestFilePath();
65+
File.Create(source).Dispose();
66+
Move(source, destination);
67+
Assert.True(File.Exists(destination));
68+
Assert.False(File.Exists(source));
69+
}
70+
71+
[Fact]
72+
public void MoveFile_TrailingDestinationSlash()
73+
{
74+
// Regression https://github.com/dotnet/corefx/issues/19710
75+
string source = GetTestFilePath();
76+
string destination = GetTestFilePath();
77+
File.Create(source).Dispose();
78+
Move(source, destination + Path.DirectorySeparatorChar);
79+
Assert.True(File.Exists(destination));
80+
Assert.False(File.Exists(source));
81+
}
82+
83+
[Fact]
84+
[PlatformSpecific(TestPlatforms.Windows)]
85+
public void MoveFile_TrailingDestinationAltSlash_Windows()
86+
{
87+
// Regression https://github.com/dotnet/corefx/issues/19710
88+
string source = GetTestFilePath();
89+
string destination = GetTestFilePath();
90+
File.Create(source).Dispose();
91+
Move(source, destination + Path.AltDirectorySeparatorChar);
92+
Assert.True(File.Exists(destination));
93+
Assert.False(File.Exists(source));
94+
}
95+
96+
[Fact]
97+
public void MoveFile_TrailingSourceSlash()
98+
{
99+
string source = GetTestFilePath();
100+
string destination = GetTestFilePath();
101+
File.Create(source).Dispose();
102+
Assert.Throws<IOException>(() => Move(source + Path.DirectorySeparatorChar, destination));
103+
}
104+
105+
[Fact]
106+
[PlatformSpecific(TestPlatforms.Windows)]
107+
public void MoveFile_TrailingSourceAltSlash_Windows()
108+
{
109+
string source = GetTestFilePath();
110+
string destination = GetTestFilePath();
111+
File.Create(source).Dispose();
112+
Assert.Throws<IOException>(() => Move(source + Path.AltDirectorySeparatorChar, destination));
113+
}
114+
59115
[Fact]
60116
public void MoveOntoFile()
61117
{

src/System.IO.FileSystem/tests/File/Exists.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,31 @@ public void PathEndsInTrailingSlash()
8080
Assert.False(Exists(path));
8181
}
8282

83+
[Fact]
84+
[PlatformSpecific(TestPlatforms.Windows)]
85+
public void PathEndsInAltTrailingSlash_Windows()
86+
{
87+
string path = GetTestFilePath() + Path.DirectorySeparatorChar;
88+
Assert.False(Exists(path));
89+
}
90+
91+
[Fact]
92+
public void PathEndsInTrailingSlash_AndExists()
93+
{
94+
string path = GetTestFilePath();
95+
File.Create(path).Dispose();
96+
Assert.False(Exists(path + Path.DirectorySeparatorChar));
97+
}
98+
99+
[Fact]
100+
[PlatformSpecific(TestPlatforms.Windows)]
101+
public void PathEndsInAltTrailingSlash_AndExists_Windows()
102+
{
103+
string path = GetTestFilePath();
104+
File.Create(path).Dispose();
105+
Assert.False(Exists(path + Path.DirectorySeparatorChar));
106+
}
107+
83108
[Fact]
84109
public void PathAlreadyExistsAsDirectory()
85110
{

0 commit comments

Comments
 (0)