【问题标题】:File.Copy() and Symbolic LinksFile.Copy() 和符号链接
【发布时间】:2012-11-29 13:16:10
【问题描述】:

我想将一个文件复制到一个新的文件名。有时源文件可能是一个符号(文件)链接,用

创建
mklink C:\MyPath\ThisIsASymbolicLink.xml C:\MyPath\ThisIsTheOriginal.xml

我正在使用此代码:

string from = @"C:\MyPath\ThisIsASymbolicLink.xml";
string to = @"C:\MyPath\WantCopyOfOriginalFileHere.xml";
File.Copy(from, to, true);

但是,我收到一个 IOException

系统无法解析文件名。

from 文件确实是一个符号链接时。

如果源文件可能是真实文件或文件的符号链接,我该如何编写代码?

【问题讨论】:

  • 嗯,这是文件系统故障。类似于另一个驱动器上不再存在的链接目标。
  • @HansPassant:都是本地的,在一个驱动器上,我今天下午刚刚创建了链接。
  • @HansPassant:这可能与我刚刚发布的一个类似问题有关:stackoverflow.com/q/13831759/141172

标签: c# .net exception-handling io symlink


【解决方案1】:

在此blog post 上进行扩展,我创建了扩展方法,它们采用可以引用原始链接或符号链接的 DirectoryInfo 或 FileInfo,并返回指示原始文件的完全限定路径名的字符串。

应用代码

应用代码修改如下:

// Works whether or not file is a symbolic link
string from = 
    new FileInfo(@"C:\MyPath\ThisIsASymbolicLink.xml").GetSymbolicLinkTarget();

扩展方法代码

    private const int FILE_SHARE_READ = 1;
    private const int FILE_SHARE_WRITE = 2;

    private const int CREATION_DISPOSITION_OPEN_EXISTING = 3;

    private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;

    // http://msdn.microsoft.com/en-us/library/aa364962%28VS.85%29.aspx
    [DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetFinalPathNameByHandle(IntPtr handle, [In, Out] StringBuilder path, int bufLen, int flags);

    // http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx
    [DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
    IntPtr SecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);

    public static string GetSymbolicLinkTarget(this FileSystemInfo symlink)
    {
        using (SafeFileHandle fileHandle = CreateFile(symlink.FullName, 0, 2, System.IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, System.IntPtr.Zero))
        {
            if (fileHandle.IsInvalid)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            StringBuilder path = new StringBuilder(512);
            int size = GetFinalPathNameByHandle(fileHandle.DangerousGetHandle(), path, path.Capacity, 0);
            if (size < 0)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            // The remarks section of GetFinalPathNameByHandle mentions the return being prefixed with "\\?\"
            // More information about "\\?\" here -> http://msdn.microsoft.com/en-us/library/aa365247(v=VS.85).aspx
            if (path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\')
                return path.ToString().Substring(4);
            else
                return path.ToString();
        }
    }

单元测试

[TestClass]
public class SymlinkTest
{
    [TestInitialize]
    public void SetupFiles()
    {
        if (!File.Exists(@"C:\Temp\SymlinkUnitTest\Original.txt")) throw new Exception("Run Symlinksetup.bat as Admin to create test data.");
    }

    [TestMethod]
    public void OrdinaryFile()
    {
        string file = @"C:\Temp\SymlinkUnitTest\Original.txt";
        string actual = new FileInfo(file).GetSymbolicLinkTarget();
        Assert.IsTrue(actual.EndsWith(@"SymlinkUnitTest\Original.txt"));
    }

    [TestMethod]
    public void FileSymlink()
    {
        string file = @"C:\Temp\SymlinkUnitTest\Symlink.txt";
        string actual = new FileInfo(file).GetSymbolicLinkTarget();
        Assert.IsTrue(actual.EndsWith(@"SymlinkUnitTest\Original.txt"));
    }

    [TestMethod]
    public void OrdinaryDirectory()
    {
        string dir = @"C:\Temp\SymlinkUnitTest";
        string actual = new DirectoryInfo(dir).GetSymbolicLinkTarget();
        Assert.IsTrue(actual.EndsWith(@"SymlinkUnitTest"));
    }

    [TestMethod]
    public void DirectorySymlink()
    {
        string dir = @"C:\Temp\SymlinkUnitTest";
        string actual = new DirectoryInfo(dir).GetSymbolicLinkTarget();
        Assert.IsTrue(actual.EndsWith(@"SymlinkUnitTest"));
    }
}

创建单元测试数据的批处理文件

必须以管理员身份运行... mklink 的要求。

@Echo Off
Echo Must be run as Administrator (due to mklink)
mkdir C:\Temp
mkdir C:\Temp\SymlinkUnitTest
c:
cd C:\Temp\SymlinkUnitTest

echo Original File>Original.txt
mklink Symlink.txt Original.txt

mklink /D C:\Temp\SymlinkUnitTest\SymDir C:\Temp\SymlinkUnitTest

【讨论】:

  • 感谢您的回答,这对我很有帮助!不过,我发现了一个问题 - SafeFileHandle 是一次性的,您忘记使用 using() 或 .dispose()。在我的情况下,我在检查目标文件的大小后删除了一个符号链接,因此实际上并没有立即删除该文件,因为我仍然持有该文件的句柄。
  • @BigScary:很好。我更新了代码。 MSDN 开始养成在他们的示例中不包含正确编码模式的习惯,不幸的是,一些博主已经继承了这一习惯。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多