列出文件的硬链接(在C#中)

时间:2010-11-16 10:45:11

标签: c# junction

我想编写一个程序,显示带有硬链接的其他驱动器的文件。 我想保持两个硬链接在文件名和其他内容一致,所以我必须得到一个函数/方法,我可以列出文件的所有当前硬链接。

例如:

我有一个文件C:\ file.txt和第二个到D:\ file.txt的硬链接 然后我将D:\ file.txt重命名为D:\ file_new.txt。我现在也希望能够重命名C驱动器上的硬链接。
所以我需要一个为D:\ file_new.txt返回的函数,它有以下硬链接:
C:\ file.txt的
d:\ file_new.txt
然后我可以重命名C:\上的硬链接以获得D:\ file_new.txt

所以我需要获取物理文件的所有硬链接。或者:使用硬链接寻址的文件的所有硬链接。

希望有人可以帮忙!

编辑:

Oliver注意到硬链接不能用于不同的磁盘。谢谢...所以我把问题扩展到:我需要什么?交汇点?符号链接?它还应该不仅与文件夹一起使用文件!

5 个答案:

答案 0 :(得分:5)

以下代码应该运行良好(最初由Peter Provost在PowerShell代码存储库上发布):

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;

namespace HardLinkEnumerator
{
   public static class Kernel32Api
   {
       [StructLayout(LayoutKind.Sequential)]
       public struct BY_HANDLE_FILE_INFORMATION
       {
           public uint FileAttributes;
           public FILETIME CreationTime;
           public FILETIME LastAccessTime;
           public FILETIME LastWriteTime;
           public uint VolumeSerialNumber;
           public uint FileSizeHigh;
           public uint FileSizeLow;
           public uint NumberOfLinks;
           public uint FileIndexHigh;
           public uint FileIndexLow;
       }

       [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
       static extern SafeFileHandle CreateFile(
           string lpFileName,
           [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
           [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
           IntPtr lpSecurityAttributes,
           [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
           [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
           IntPtr hTemplateFile);

       [DllImport("kernel32.dll", SetLastError = true)]
       static extern bool GetFileInformationByHandle(SafeFileHandle handle, out BY_HANDLE_FILE_INFORMATION lpFileInformation);

       [DllImport("kernel32.dll", SetLastError = true)]
       [return: MarshalAs(UnmanagedType.Bool)]
       static extern bool CloseHandle(SafeHandle hObject);

       [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
       static extern IntPtr FindFirstFileNameW(
           string lpFileName,
           uint dwFlags,
           ref uint stringLength,
           StringBuilder fileName);

       [DllImport("kernel32.dll", SetLastError = true, CharSet=CharSet.Unicode)]
       static extern bool FindNextFileNameW(
           IntPtr hFindStream,
           ref uint stringLength,
           StringBuilder fileName);

       [DllImport("kernel32.dll", SetLastError = true)]
       static extern bool FindClose(IntPtr fFindHandle);

       [DllImport("kernel32.dll")]
       static extern bool GetVolumePathName(string lpszFileName,
           [Out] StringBuilder lpszVolumePathName, uint cchBufferLength);

       [DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
       static extern bool PathAppend([In, Out] StringBuilder pszPath, string pszMore);

       public static int GetFileLinkCount(string filepath)
       {
           int result = 0;
           SafeFileHandle handle = CreateFile(filepath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero);
           BY_HANDLE_FILE_INFORMATION fileInfo = new BY_HANDLE_FILE_INFORMATION();
           if (GetFileInformationByHandle(handle, out fileInfo))
               result = (int)fileInfo.NumberOfLinks;
           CloseHandle(handle);
           return result;
       }

       public static string[] GetFileSiblingHardLinks(string filepath)
       {
           List<string> result = new List<string>();
           uint stringLength = 256;
           StringBuilder sb = new StringBuilder(256);
           GetVolumePathName(filepath, sb, stringLength);
           string volume = sb.ToString();
           sb.Length = 0; stringLength = 256;
           IntPtr findHandle = FindFirstFileNameW(filepath, 0, ref stringLength, sb);
           if (findHandle.ToInt32() != -1)
           {
               do
               {
                   StringBuilder pathSb = new StringBuilder(volume, 256);
                   PathAppend(pathSb, sb.ToString());
                   result.Add(pathSb.ToString());
                   sb.Length = 0; stringLength = 256;
               } while (FindNextFileNameW(findHandle, ref stringLength, sb));
               FindClose(findHandle);
               return result.ToArray();
           }
           return null;
       }

   }
}

答案 1 :(得分:1)

也许我误解了你的问题,但硬链接不能从一个驱动器转到另一个驱动器。它们只能存在于一个驱动器上。

在.Net框架内,没有人支持获取这些信息。但是Win32 API可以为您提供这些信息。

看看this article。它可能对你有帮助。

更新

据我所知,不可能在不同的驱动器之间进行此操作。交汇点绝对不是你的朋友因为它只适用于foldes。但在阅读this wikipedia article之后,您似乎可以使用符号链接在Vista和Win7上执行此操作。还有this shell extension的链接,似乎涵盖了您可以使用这些NTFS特殊功能所做的一切。也许有了这个,你可以检查你的目标是否可以访问,然后可以检查MSDN以获得所需的Win32 API函数。

答案 2 :(得分:1)

注意:

  • 硬链接只能是相同卷 上的文件,这与问题的要求相抵触,导致the OP himself answered的问题正文中的后续问题。

  • 给出问题的标题,但是,通过谷歌搜索找到此帖子的用户最有可能对问题的解决方案感兴趣,如标题中所述 给出了一个文件,如何找到所有硬链接 (根据定义,这些硬链接都在同一卷上)。

  • 下面的解决方案是对Marcel Nolte's helpful answer简化和现代化的改编。

它的行为和约束是:

  • 对于给定的输入文件,其硬链接数组作为完整文件路径返回,其中包括输入文件的路径本身。

  • 如果文件(本身)只有一个硬链接,或者您指定了目录,则仅返回该文件/目录的完整路径。

  • 如果路径引用的卷不支持硬链接,或者路径不存在,则返回null

    • NiKiZe注意,您无法通过CIFS / SMB连接(网络驱动器)查询硬链接。

以下是一个自包含的Windows控制台应用程序,您应该可以直接编译和运行该应用程序;感兴趣的方法是HardLinkHelper.GetHardLinks()

using System;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace demo
{
  public static class Program
  {
    public static void Main()
    {
      // Sample file that is known to have (one) hard link.
      var file = Environment.ExpandEnvironmentVariables(@"%SYSTEMROOT%\explorer.exe");
      foreach (var link in HardLinkHelper.GetHardLinks(file) ?? new string[] { "n/a" })
      {
        Console.WriteLine(link);
      }
    }
  }

  public static class HardLinkHelper
  {

    #region WinAPI P/Invoke declarations
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern IntPtr FindFirstFileNameW(string lpFileName, uint dwFlags, ref uint StringLength, StringBuilder LinkName);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool FindNextFileNameW(IntPtr hFindStream, ref uint StringLength, StringBuilder LinkName);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool FindClose(IntPtr hFindFile);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool GetVolumePathName(string lpszFileName, [Out] StringBuilder lpszVolumePathName, uint cchBufferLength);

    public static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); // 0xffffffff;
    public const int MAX_PATH = 65535; // Max. NTFS path length.
    #endregion

    /// <summary>
    //// Returns the enumeration of hardlinks for the given *file* as full file paths, which includes
    /// the input path itself.
    /// </summary>
    /// <remarks>
    /// If the file has only one hardlink (itself), or you specify a directory, only that
    /// file's / directory's full path is returned.
    /// If the path refers to a volume that doesn't support hardlinks, or the path
    /// doesn't exist, null is returned.
    /// </remarks>
    public static string[] GetHardLinks(string filepath)
    {
      StringBuilder sbPath = new StringBuilder(MAX_PATH);
      uint charCount = (uint)sbPath.Capacity; // in/out character-count variable for the WinAPI calls.
      // Get the volume (drive) part of the target file's full path (e.g., @"C:\")
      GetVolumePathName(filepath, sbPath, (uint)sbPath.Capacity);
      string volume = sbPath.ToString();
      // Trim the trailing "\" from the volume path, to enable simple concatenation
      // with the volume-relative paths returned by the FindFirstFileNameW() and FindFirstFileNameW() functions,
      // which have a leading "\"
      volume = volume.Substring(0, volume.Length - 1);
      // Loop over and collect all hard links as their full paths.
      IntPtr findHandle;
      if (INVALID_HANDLE_VALUE == (findHandle = FindFirstFileNameW(filepath, 0, ref charCount, sbPath))) return null;
      List<string> links = new List<string>();
      do
      {
        links.Add(volume + sbPath.ToString()); // Add the full path to the result list.
        charCount = (uint)sbPath.Capacity; // Prepare for the next FindNextFileNameW() call.
      } while (FindNextFileNameW(findHandle, ref charCount, sbPath));
      FindClose(findHandle);
      return links.ToArray();
    }

  }
}

答案 3 :(得分:0)

尝试:

using System.IO;

string[] filePathsC = Directory.GetFiles(@"c:\");
string[] filePathsD = Directory.GetFiles(@"d:\");

并遍历数组,找到文件并更改名称

编辑: 通过阅读评论,我知道在我知道硬链接之前我已经回答了。我现在意识到这个答案没有帮助。

答案 4 :(得分:0)

我找到了解决方案:

首先,我不必使用硬链接(因为它们不能指向其他磁盘)。我必须使用符号链接。所以我在原始磁盘上有一个硬链接文件,在这个文件的其他磁盘上有符号链接。限制是操作系统必须是Vista或更新。

其次,我必须能够找到符号链接指向的位置。 在这里,我找到了一个很好的例子,如何找到我需要的信息: http://www.codeproject.com/KB/vista/ReparsePointID.aspx

我唯一没有管理的是找到特定文件(硬链接)中的所有符号链接。我想没有开箱即用的解决方案,我必须递归所有符号链接并测试目标。但就我而言,没问题。

我希望能帮到别人!