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