4

我想编写一个程序,用硬链接显示另一个驱动器的文件。

我想让两个硬链接在文件名和其他内容上保持一致,所以我必须得到一个函数/方法,我可以在其中列出文件的所有当前硬链接。

例如:

我有一个文件C:\file.txt和第二个硬链接到D:\file.txt.

然后我重命名D:\file.txtD:\file_new.txt.

我现在也希望能够重命名 C 驱动器上的硬链接。

因此,我需要一个返回D:\file_new.txt以下硬链接的函数:

C:\file.txt
D:\file_new.txt

然后我也可以重命名硬链接C:\以获得D:\file_new.txt

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

希望有人能帮忙!

编辑

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

4

5 回答 5

9

以下代码应该可以正常工作(最初由 Peter provost 在 PowerShell Code Repository 上发布):

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;
       }

   }
}
于 2012-07-05T09:45:34.690 回答
2

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

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

看看这篇文章。它可能会帮助你。

更新

据我所知,在不同的驱动器之间不可能做到这一点。连接点绝对不是你的朋友,因为它只适用于折叠。但是在阅读了这篇维基百科文章之后,您似乎可以在 Vista 和 Win7 上使用符号链接进行操作。还有一个指向这个 shell 扩展的链接,它似乎涵盖了您可以使用这些 NTFS 特殊功能做的所有事情。也许有了这个,您可以检查您的目标是否可以达到,然后检查 MSDN 以获得所需的 Win32 API 功能。

于 2010-11-16T10:57:48.097 回答
1

笔记:

  • 硬链接只能是同一卷上的文件,这与问题的要求相矛盾,导致OP自己回答的问题正文中有一个后续问题。

  • 然而,鉴于问题的标题,通过谷歌搜索找到这篇文章的用户很可能对标题中所述的问题的解决方案感兴趣:给定一个文件,我如何找到指向它的所有硬链接(根据定义都在同一卷上)。

  • 下面的解决方案是对Marcel Nolte 的有用答案的简化和现代化改编

它的行为和约束是:

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

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

  • 如果路径引用不支持硬链接的卷,或者路径不存在,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();
    }

  }
}
于 2020-03-06T22:58:36.857 回答
0

我找到了一个解决方案:

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

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

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

我希望可以帮助别人!

于 2010-11-18T08:26:27.740 回答
-2

尝试:

using System.IO;

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

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

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

于 2010-11-16T10:51:39.960 回答