开发者

List Hard Links of a file (in C#)

I want to write a program that shows the files of another dri开发者_StackOverflow中文版ve with hard links.

I want to keep both hardlinks consistent in filename and other things, so I have to get a function/method where I can list all current hard links of a file.

For example:

I have a file C:\file.txt and a second hard link to D:\file.txt.

Then I rename D:\file.txt to D:\file_new.txt.

I now want to be able to also rename the hardlink on the C drive as well.

So I need a function which returns for D:\file_new.txt that there are the following hardlinks:

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

then I can rename the hard link on C:\ also to get D:\file_new.txt

So I need to get all hard links of a physical file. Or: All hard links of a file addressed with a hard link.

Hope somebody can help!

Edit:

Oliver noticed that hard links can't be used on different disks. thanks... So I extend the question to: What do I need? Junction Points? Symbolic Links? It should also work with files not only with folders!


the following code should work well (originally postet by Peter provost on 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;
       }

   }
}


Maybe i misunderstand your questions, but hardlinks can't go from one drive to another. They can only exist on a single drive.

Within the .Net framwork there is no support to get these informations. But the Win32 API can provide you with these informations.

Take a look at this article. It may help you.

Update

As far as i know it is not possible to do it between different drives. Junction Points are definitely not your friend cause it only works on foldes. But after reading this wikipedia article it seems that you can do it on Vista and Win7 with symbolic links. There is also a link to this shell extension which seems to cover everything you can do with these NTFS special features. Maybe with this you can check if your goal is reachable and maybe afterwards check the MSDN for the desired Win32 API function.


Note:

  • Hard links can only be files on the same volume, which contradicts the requirements of the question, which led to a follow-up question in the question body that the OP himself answered.

  • Given the title of the question, however, users who find this post by googling are most likely interest in a solution to the problem as stated in the title: given a file, how can I find all hard links to it (which are by definition all on the same volume).

  • The solution below is a streamlined and modernized adaptation of Marcel Nolte's helpful answer.

Its behavior and constraints are:

  • For a given input file, its array of hard links is returned, as full file paths, which includes the input file's path itself.

  • If the file has only one hard link (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 hard links, or the path doesn't exist, null is returned.

    • NiKiZe notes that you cannot query hardlinks via a CIFS/SMB connection (network drive).

The following is a self-contained Windows console application that you should be able to compile and run as-is; the method of interest is 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();
    }

  }
}


I found a solution:

First I don't have to use hard links (since they can't point to an other disk). I have to use symbolic links instead. So I have one hard linked file on the original disk and symbolic links on other disks to this file. The limitation is OS must be Vista or newer.

Second I have to be able to find out where the symbolic link is pointing to. Here I found a good example how to find out the information I need: http://www.codeproject.com/KB/vista/ReparsePointID.aspx

The only thing I don't managed is to find all symbolic links from a specific file (hard link). I guess there is no out of the box solution and I have to recurse all symbolic links and test the target. But in my case that's no problem.

I hope that can help others!


try:

using System.IO;

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

and loop through the arrays, find the files and change the name

EDIT: By reading the comments I know that I answered before I knew what a hardlink is. I realise now that this answer is not helping.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜