# Tuesday, December 08, 2009
« CLR Team Blog | Main | Sharing Silverlight Assemblies with .NET... »

I'm doing a little project right now that requires me to know (among other things) the number of hardlinks for a given file. I have a tool that already will tell me the number of hard links for a given file, but I need to collect a bunch more information, and I really hate calling out to an exe and collecting and parsing its output, as part of a larger app. So, I set out to write that slightly larger app.

Having never written an app that cares about hardlinks, I didn't know whether or not the BCL had APIs that exposed this info. It doesn't. Whether it should expose this info is a whole other question. So, I resorted to p/invoke, which is fine. I did a few searches and found that there isn't too much info out there on doing this from .NET, hence my motivation for writing this post.

I started out with NtQueryInformationFile. I'm pretty sure that the p/invoke signature on p/invoke.net is wrong. I wasn't able to get this function working to my satisfaction, but can share where I ended up, in the hope that it might help someone:

        [DllImport("ntdll.dll", SetLastError=true)] 
        static extern Int32 NtQueryInformationFile(IntPtr fileHandle, ref IO_STATUS_BLOCK IoStatusBlock, [MarshalAs(UnmanagedType.LPArray)] Byte[] arrayBuffer, uint length, FILE_INFORMATION_CLASS fileInformation); 

FWIW, I allocated a byte[2048], and passed in (uint)array.Length as the length paramter. 2k was overkill for what I needed, but at least got me a bunch of bytes. I wasn't too sure what to do next, so decided to look for something easier. I was guessing that I needed to write a struct who fields mapped over that byte[], but I wasn't sure what the meaning of those fields was going to be. I couldn't find any docs that would have that easier, and didn't see any code samples either.

The good news is that I found another function -- GetFileInformationByHandle -- that is much easier to use, and that did work for me.

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

You can see my app below. For now, it just contains calculates the hard-links, which is the part that I tackled first. I'll now move to writing the rest of my experiment.

using System;
using System.Runtime.InteropServices;
using System.IO;

namespace banff
{
    class Program
    {
        static void Main(string[] args)
        {

            if (args == null || args.Length == 0)
            {
                Console.WriteLine("Please specify a file to check");
                return;
            }
            else if (!File.Exists(args[0]))
            {
                Console.WriteLine("File doesn't exist");
            }

            

            FileStream fs = null;

            fs = new FileStream(args[0],FileMode.Open,FileAccess.Read);
            BY_HANDLE_FILE_INFORMATION fileInfo;

            Boolean result = GetFileInformationByHandle(fs.Handle, out fileInfo);

            Console.WriteLine(fileInfo.NumberOfLinks);

            fs.Close();

        }

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


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

    }
}