diff options
author | Alexis Christoforides <alexis@thenull.net> | 2019-08-01 07:38:43 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-01 07:38:43 +0300 |
commit | 8e3d1356d179c219f134c9b2c075f21ab77e0b86 (patch) | |
tree | c9e2cc5fd64a693875027732479a8e8e8bda8d58 | |
parent | 97a0b3fddb29164e7b1a735b54d3c0d5d59f9915 (diff) | |
parent | 7715668a23e07252639622f3e7a7b2002a2d47ff (diff) |
Merge pull request #314 from mono/fix-gh-14971
Allow copying/moving/replacing broken symlinks
-rw-r--r-- | src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Symlink.cs | 16 | ||||
-rw-r--r-- | src/Native/Unix/System.Native/pal_io.c | 5 | ||||
-rw-r--r-- | src/Native/Unix/System.Native/pal_io.h | 7 | ||||
-rw-r--r-- | src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs | 32 | ||||
-rw-r--r-- | src/System.IO.FileSystem/tests/File/Copy.cs | 17 | ||||
-rw-r--r-- | src/System.IO.FileSystem/tests/File/Move.cs | 17 |
6 files changed, 94 insertions, 0 deletions
diff --git a/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Symlink.cs b/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Symlink.cs new file mode 100644 index 0000000000..68bebb152c --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Symlink.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32.SafeHandles; +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Symlink", SetLastError = true)] + internal static extern int Symlink(string target, string linkPath); + } +} diff --git a/src/Native/Unix/System.Native/pal_io.c b/src/Native/Unix/System.Native/pal_io.c index d68eb4b583..f2011d61c4 100644 --- a/src/Native/Unix/System.Native/pal_io.c +++ b/src/Native/Unix/System.Native/pal_io.c @@ -1451,3 +1451,8 @@ int32_t SystemNative_LockFileRegion(intptr_t fd, int64_t offset, int64_t length, while ((ret = fcntl (ToFileDescriptor(fd), F_SETLK, &lockArgs)) < 0 && errno == EINTR); return ret; } + +int32_t SystemNative_Symlink(const char* target, const char* linkPath) +{ + return symlink(target, linkPath); +} diff --git a/src/Native/Unix/System.Native/pal_io.h b/src/Native/Unix/System.Native/pal_io.h index 9f80c8b00c..aab12e6ed3 100644 --- a/src/Native/Unix/System.Native/pal_io.h +++ b/src/Native/Unix/System.Native/pal_io.h @@ -750,4 +750,11 @@ DLLEXPORT int32_t SystemNative_GetPeerID(intptr_t socket, uid_t* euid); */ DLLEXPORT int32_t SystemNative_LockFileRegion(intptr_t fd, int64_t offset, int64_t length, int16_t lockType); +/** +* Creates a symbolic link at "linkPath", pointing at "target". +* "target" may or may not exist (dangling symbolic links are valid filesystem objects) +* Returns 0 on success; otherwise, returns -1 and errno is set. +*/ +DLLEXPORT int32_t SystemNative_Symlink(const char* target, const char* linkPath); + END_EXTERN_C diff --git a/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs b/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs index daddcedb3d..283c85fc93 100644 --- a/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs +++ b/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs @@ -12,6 +12,32 @@ namespace System.IO { internal const int DefaultBufferSize = 4096; + private static bool CopyDanglingSymlink(string sourceFullPath, string destFullPath) + { + // Check if the source is a dangling symlink. In those cases, we just want to copy the link + Interop.Sys.FileStatus ignored; + if (! (Interop.Sys.Stat(sourceFullPath, out ignored) < 0 && + Interop.Sys.LStat(sourceFullPath, out ignored) == 0)) + { + return false; + } + + Interop.ErrorInfo errorInfo; + // get the target of the symlink + string linkTarget = Interop.Sys.ReadLink(sourceFullPath); + if (linkTarget is null) + { + errorInfo = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(errorInfo, sourceFullPath); + } + + if (Interop.Sys.Symlink(linkTarget, destFullPath) == 0) + return true; + + errorInfo = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(errorInfo, destFullPath); + } + public static void CopyFile(string sourceFullPath, string destFullPath, bool overwrite) { // The destination path may just be a directory into which the file should be copied. @@ -21,6 +47,9 @@ namespace System.IO destFullPath = Path.Combine(destFullPath, Path.GetFileName(sourceFullPath)); } + if (CopyDanglingSymlink(sourceFullPath, destFullPath)) + return; + // Copy the contents of the file from the source to the destination, creating the destination in the process using (var src = new FileStream(sourceFullPath, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, FileOptions.None)) using (var dst = new FileStream(destFullPath, overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, DefaultBufferSize, FileOptions.None)) @@ -31,6 +60,9 @@ namespace System.IO private static void LinkOrCopyFile (string sourceFullPath, string destFullPath) { + if (CopyDanglingSymlink(sourceFullPath, destFullPath)) + return; + if (Interop.Sys.Link(sourceFullPath, destFullPath) >= 0) return; diff --git a/src/System.IO.FileSystem/tests/File/Copy.cs b/src/System.IO.FileSystem/tests/File/Copy.cs index 81338ceb91..2cf108e8db 100644 --- a/src/System.IO.FileSystem/tests/File/Copy.cs +++ b/src/System.IO.FileSystem/tests/File/Copy.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Xunit; +using System.Runtime.InteropServices; namespace System.IO.Tests { @@ -48,6 +49,22 @@ namespace System.IO.Tests Assert.Throws<IOException>(() => Copy(testFile, testFile)); } + [DllImport("libc", SetLastError = true)] + private static extern int symlink(string target, string linkpath); + + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void DanglingSymlinkCopy() + { + string dangling_symlink = GetTestFileName(); + string missing_target = GetTestFileName(); + string dangling_symlink_new_location = GetTestFileName(); + Assert.False(File.Exists(missing_target)); + Assert.Equal(symlink(missing_target, dangling_symlink), 0); + Copy(dangling_symlink, dangling_symlink_new_location); + Assert.True(File.Exists(dangling_symlink_new_location)); // File.Exists returns true for dangling symlinks + } + [Fact] [SkipOnTargetFramework(TargetFrameworkMonikers.Mono, "CoreFX FileStream not yet imported")] public void NonExistentPath() diff --git a/src/System.IO.FileSystem/tests/File/Move.cs b/src/System.IO.FileSystem/tests/File/Move.cs index e9da920edd..6548b4f564 100644 --- a/src/System.IO.FileSystem/tests/File/Move.cs +++ b/src/System.IO.FileSystem/tests/File/Move.cs @@ -4,6 +4,7 @@ using Xunit; using System.Linq; +using System.Runtime.InteropServices; namespace System.IO.Tests { @@ -174,6 +175,22 @@ namespace System.IO.Tests Assert.False(File.Exists(testFileSource.FullName)); } + [DllImport("libc", SetLastError = true)] + private static extern int symlink(string target, string linkpath); + + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void DanglingSymlinkMove() + { + string dangling_symlink = GetTestFileName(); + string missing_target = GetTestFileName(); + string dangling_symlink_new_location = GetTestFileName(); + Assert.False(File.Exists(missing_target)); + Assert.Equal(symlink(missing_target, dangling_symlink), 0); + Move(dangling_symlink, dangling_symlink_new_location); + Assert.True(File.Exists(dangling_symlink_new_location)); // File.Exists returns true for dangling symlinks + } + [Fact] public void FileNameWithSignificantWhitespace() { |