Search code examples
c#ntfsinodehardlink

How to know if two hard links point to the same inode? (C#)


Anyway of checking, in C#, that two files (hard links) point to the same inode? And also get the count of this inode, in case there are more than two... ?


Solution

  • You can get count of hard links pointing to the node using GetFileInformationByHandle function. For example:

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool GetFileInformationByHandle(
        SafeFileHandle hFile,
        out BY_HANDLE_FILE_INFORMATION lpFileInformation
    );
    
    [StructLayout(LayoutKind.Sequential)]
    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;
    }
    
    // then in another place
    using (var fs = File.OpenRead("path to your file")) {                
        BY_HANDLE_FILE_INFORMATION info;
        GetFileInformationByHandle(fs.SafeFileHandle, out info);
        var numberOfLinks = info.NumberOfLinks;
    }
    

    To get what files they are pointing to, you will need another win api functions: FindFirstFileNameW and FineNextFileNameW. Use them like this:

    [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);
    
    public static string[] GetFileHardLinks(string filePath) {
        // first get drive letter
        var drive = new DriveInfo(Path.GetPathRoot(filePath));
        var result = new List<string>();
        // buffer for return value
        var sb = new StringBuilder(256);
        // length of buffer
        uint sbLength = 256;
        // third argument contains reference to buffer length (buffer is StringBuilder). 
        // it's a reference because if it's too small, call returns an error and will put required length there instead
        IntPtr findHandle = FindFirstFileNameW(filePath, 0, ref sbLength, sb);
        // returns -1 on error
        if (findHandle.ToInt64() != -1) {
            do {
                // combine the result with drive letter (it comes without it)
                result.Add(Path.Combine(drive.RootDirectory.FullName, sb.ToString().TrimStart(new [] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar})));
                sb.Clear();
                sbLength = 256;
                // and repeat
            } while (FindNextFileNameW(findHandle, ref sbLength, sb));
            FindClose(findHandle);
            return result.ToArray();
        }
        return null;
    }
    

    This code might be not production ready, so take care. But it should at least give you an idea. If you will use it - carefully read what those function return on errors and act accordingly (for example, handle the case when buffer length is not enough, or just use larger buffer than 256).