diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Common/src/Interop/Unix/System.Native/Interop.Stat.cs | 2 | ||||
-rw-r--r-- | src/Native/Unix/System.Native/pal_io.cpp | 2 | ||||
-rw-r--r-- | src/Native/Unix/System.Native/pal_io.h | 2 | ||||
-rw-r--r-- | src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs | 20 | ||||
-rw-r--r-- | src/System.IO.FileSystem/tests/File/Move.cs | 28 |
5 files changed, 52 insertions, 2 deletions
diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.Stat.cs b/src/Common/src/Interop/Unix/System.Native/Interop.Stat.cs index a8bc2ec7d1..cda00ac6c3 100644 --- a/src/Common/src/Interop/Unix/System.Native/Interop.Stat.cs +++ b/src/Common/src/Interop/Unix/System.Native/Interop.Stat.cs @@ -27,6 +27,8 @@ internal static partial class Interop internal long MTime; internal long CTime; internal long BirthTime; + internal long Dev; + internal long Ino; } internal static class FileTypes diff --git a/src/Native/Unix/System.Native/pal_io.cpp b/src/Native/Unix/System.Native/pal_io.cpp index a672c1e91e..a1c41e37d6 100644 --- a/src/Native/Unix/System.Native/pal_io.cpp +++ b/src/Native/Unix/System.Native/pal_io.cpp @@ -140,6 +140,8 @@ static_assert(PAL_IN_ISDIR == IN_ISDIR, ""); static void ConvertFileStatus(const struct stat_& src, FileStatus* dst) { + dst->Dev = static_cast<int64_t>(src.st_dev); + dst->Ino = static_cast<int64_t>(src.st_ino); dst->Flags = FILESTATUS_FLAGS_NONE; dst->Mode = static_cast<int32_t>(src.st_mode); dst->Uid = src.st_uid; diff --git a/src/Native/Unix/System.Native/pal_io.h b/src/Native/Unix/System.Native/pal_io.h index 083efa04ee..deace9ca30 100644 --- a/src/Native/Unix/System.Native/pal_io.h +++ b/src/Native/Unix/System.Native/pal_io.h @@ -24,6 +24,8 @@ struct FileStatus int64_t MTime; // time of last modification int64_t CTime; // time of last status change int64_t BirthTime; // time the file was created + int64_t Dev; // ID of the device containing the file + int64_t Ino; // inode number of the file }; /************ diff --git a/src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs b/src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs index a74578005f..59319fe26e 100644 --- a/src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs +++ b/src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs @@ -82,8 +82,24 @@ namespace System.IO { // The desired behavior for Move(source, dest) is to not overwrite the destination file // if it exists. Since rename(source, dest) will replace the file at 'dest' if it exists, - // link/unlink are used instead. Note that the Unix FileSystemWatcher will treat a Move - // as a Creation and Deletion instead of a Rename and thus differ from Windows. + // link/unlink are used instead. However, if the source path and the dest path refer to + // the same file, then do a rename rather than a link and an unlink. This is important + // for case-insensitive file systems (e.g. renaming a file in a way that just changes casing), + // so that we support changing the casing in the naming of the file. If this fails in any + // way (e.g. source file doesn't exist, dest file doesn't exist, rename fails, etc.), we + // just fall back to trying the link/unlink approach and generating any exceptional messages + // from there as necessary. + Interop.Sys.FileStatus sourceStat, destStat; + if (Interop.Sys.LStat(sourceFullPath, out sourceStat) == 0 && // source file exists + Interop.Sys.LStat(destFullPath, out destStat) == 0 && // dest file exists + sourceStat.Dev == destStat.Dev && // source and dest are on the same device + sourceStat.Ino == destStat.Ino && // and source and dest are the same file on that device + Interop.Sys.Rename(sourceFullPath, destFullPath) == 0) // try the rename + { + // Renamed successfully. + return; + } + if (Interop.Sys.Link(sourceFullPath, destFullPath) < 0) { // If link fails, we can fall back to doing a full copy, but we'll only do so for diff --git a/src/System.IO.FileSystem/tests/File/Move.cs b/src/System.IO.FileSystem/tests/File/Move.cs index eeb4c914b7..995754ba5e 100644 --- a/src/System.IO.FileSystem/tests/File/Move.cs +++ b/src/System.IO.FileSystem/tests/File/Move.cs @@ -121,6 +121,34 @@ namespace System.IO.Tests } [Fact] + public void MoveToSameName() + { + string testDir = GetTestFilePath(); + Directory.CreateDirectory(testDir); + + FileInfo testFileSource = new FileInfo(Path.Combine(testDir, GetTestFileName())); + testFileSource.Create().Dispose(); + + Move(testFileSource.FullName, testFileSource.FullName); + Assert.True(File.Exists(testFileSource.FullName)); + } + + [Fact] + public void MoveToSameNameDifferentCasing() + { + string testDir = GetTestFilePath(); + Directory.CreateDirectory(testDir); + + FileInfo testFileSource = new FileInfo(Path.Combine(testDir, Path.GetRandomFileName().ToLowerInvariant())); + testFileSource.Create().Dispose(); + + FileInfo testFileDest = new FileInfo(Path.Combine(testFileSource.DirectoryName, testFileSource.Name.ToUpperInvariant())); + + Move(testFileSource.FullName, testFileDest.FullName); + Assert.True(File.Exists(testFileDest.FullName)); + } + + [Fact] public void MultipleMoves() { FileInfo testFileSource = new FileInfo(GetTestFilePath()); |