0

I am trying to get file information on a NTFS filesystem using only the unique file ID. The problem I have is in generating a handle from the ID because my code is causing access violations and I do not know why.

To get the unique file ID I used the code from HERE. I am pretty sure the problem is the descriptor. I've read HERE (1st answer 2nd comment) I can use a long Integer instead of the guid but this seems not to work. But I don't know how to create a guid with my file ID information.

This is the code I have so far.

public class WinAPI
{
    [DllImport("ntdll.dll", SetLastError = true)]
    public static extern IntPtr NtQueryInformationFile(IntPtr fileHandle, ref IO_STATUS_BLOCK IoStatusBlock, IntPtr pInfoBlock, uint length, FILE_INFORMATION_CLASS fileInformation);

    public struct IO_STATUS_BLOCK
    {
        uint status;
        ulong information;
    }
    public struct _FILE_INTERNAL_INFORMATION
    {
        public ulong IndexNumber;
    }

    // Abbreviated, there are more values than shown
    public enum FILE_INFORMATION_CLASS
    {
        FileDirectoryInformation = 1,     // 1
        FileFullDirectoryInformation,     // 2
        FileBothDirectoryInformation,     // 3
        FileBasicInformation,         // 4
        FileStandardInformation,      // 5
        FileInternalInformation      // 6
    }

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

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr OpenFileById(IntPtr hFile, FILE_ID_DESCRIPTOR lpFileID, uint dwDesiredAccess, uint dwShareMode, uint dwFlagas);

    [StructLayout(LayoutKind.Explicit)]
    public struct FILE_ID_DESCRIPTOR
    {
        [FieldOffset(0)] public uint dwSize;
        [FieldOffset(4)] public FILE_ID_TYPE type;
       // [FieldOffset(8)] public Guid guid;
        [FieldOffset(8)] public long FileReferenceNumber;
    }

    public enum FILE_ID_TYPE
    {
        FileIdType = 0,
        ObjectIdType = 1,
        ExtendedFileIdType = 2,
        MaximumFileIdType
    };

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

public class File_Handle
{

    public ulong Get_Index()
    {
        WinAPI.BY_HANDLE_FILE_INFORMATION objectFileInfo = new WinAPI.BY_HANDLE_FILE_INFORMATION();

        FileInfo fi = new FileInfo(@"D:\Test\Testfile.txt");
        FileStream fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

        WinAPI.GetFileInformationByHandle(fs.Handle, out objectFileInfo);

        fs.Close();

        ulong fileIndex = ((ulong)objectFileInfo.FileIndexHigh << 32) + (ulong)objectFileInfo.FileIndexLow;

        return fileIndex;
    }

    public string Retrieve_File(ulong Index)
    {                    
        WinAPI.FILE_ID_DESCRIPTOR Descriptor = new WinAPI.FILE_ID_DESCRIPTOR { dwSize = 24, type = WinAPI.FILE_ID_TYPE.FileIdType, FileReferenceNumber = (long)Index };

        FileInfo fi = new FileInfo(@"D:\Test\TestfileRef.txt");
        FileStream fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

        FileStream wf = new FileStream(WinAPI.OpenFileById(fs.Handle, Descriptor, 0, 0, 0x08000000), FileAccess.ReadWrite);

        WinAPI.BY_HANDLE_FILE_INFORMATION objectFileInfo = new WinAPI.BY_HANDLE_FILE_INFORMATION();
        WinAPI.GetFileInformationByHandle(wf.Handle, out objectFileInfo);
        fs.Close();
        wf.Close();

        return "Dummy";

    }
}
Community
  • 1
  • 1
B. Ueno
  • 159
  • 2
  • 13
  • Best place to go is www.pinvoke.net. Try the managed code solution here : http://www.pinvoke.net/default.aspx/kernel32.GetFileInformationByHandle – jdweng Apr 30 '17 at 08:41
  • Thanks for the fast response, but my problem is the creation of the handle using only the file ID. "GetFileinformationByHandle" only works when said handle already exists. Or am I wrong here ? – B. Ueno Apr 30 '17 at 08:51
  • I tested Get_Index on a file on c:\ and it works. There is a warning message in Retrieve_File that says the constructor FileStream needs a safe handle : new FileStream(SafeFileHandle handle, FileAccess access), Open file is just using kernel32 dll. See pinvoke : http://www.pinvoke.net/default.aspx/kernel32.OpenFile – jdweng Apr 30 '17 at 09:21
  • Yes, you can get the Index, but now try to get the file information from that index by using the Retrieve function. You'll face an access violation. And thats the point where I have the problems. The SafeFileHandle is a recommendation. It should work with this method too. Later it will be changed to run safely. – B. Ueno Apr 30 '17 at 09:24
  • `ulong fileIndex` - this is error - fileindex is 64 bit – RbMm Apr 30 '17 at 09:52
  • `FileReferenceNumber` not long - it 64bit – RbMm Apr 30 '17 at 10:04

2 Answers2

1

I know this is late, but I just spent a few days on this problem and figured it out:

According to the OpenByFileId API, this is the definition of the method:

HANDLE OpenFileById(
  HANDLE                hVolumeHint,
  LPFILE_ID_DESCRIPTOR  lpFileId,
  DWORD                 dwDesiredAccess,
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD                 dwFlagsAndAttributes
);

Note how lpFileId is an LP, aka a pointer to the FILE_ID_DESCRIPTOR struct (also mentioned in the description of the argument). So, if we modify the c# method to take a ref for that, we'll get the signature like this:

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr OpenFileById(IntPtr hFile, ref FILE_ID_DESCRIPTOR lpFileID, uint dwDesiredAccess, uint dwShareMode, uint dwFlagas);

This should get you past the Access Violation exception you're seeing, however you will likely get an invalid handle returned from here. This is because your FILE_ID_DESCRIPTOR class might be the correct size, but does not match the FILE_ID_DESCRIPTOR signature.

My Solution

You can find a full, compliable solution (both in c# and in c++) that will both get a FileId from path, and also get the Path from the FileId here: https://github.com/nolanblew/openbyfileid

NOTE: For this to work properly, you must have a handle on some file or directory on the drive you want to access. This example (and the github one) uses the local path of the program, so if you are running the program on C:\ but your file is on E:, it will fail.

Here's my code in getting a path from a FileId:

public static class FileIdHelper
{
    public static string GetFilePath(long fileSystemId)
    {
        using var handle = _CreateSafeFileHandle(".");

        if (handle == null || handle.IsInvalid)
        {
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

        var size = Marshal.SizeOf(typeof(FILE_ID_DESCRIPTOR));
        var descriptor = new FILE_ID_DESCRIPTOR { Type = FILE_ID_TYPE.FileIdType, FileId = fileSystemId, dwSize = size };

        try
        {
            using var handle2 = _OpenFileById(handle, ref descriptor);

            if (handle2 == null || handle2.IsInvalid)
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }

            const int length = 128;
            var builder = new StringBuilder(length);
            GetFinalPathNameByHandleW(handle2, builder, length, 0);
            return builder.ToString();
        }
        catch
        {
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

        return null;
    }

    static SafeFileHandle _CreateSafeFileHandle(string path) =>
        CreateFileW(
            path,
            FileAccess.Read,
            FileShare.ReadWrite,
            IntPtr.Zero,
            FileMode.Open,
            (FileAttributes)0x02000000, //FILE_FLAG_BACKUP_SEMANTICS
            IntPtr.Zero);

    static SafeFileHandle _OpenFileById(SafeFileHandle hint, ref FILE_ID_DESCRIPTOR fileId) =>
    OpenFileById(
        hint,
        ref fileId,
        FileAccess.ReadWrite,
        FileShare.ReadWrite,
        IntPtr.Zero,
        (FileAttributes)0x02000000); //FILE_FLAG_BACKUP_SEMANTICS

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateFileW")]
    static extern SafeFileHandle CreateFileW(
        [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
        [In, MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
        [In, MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
        [In] IntPtr lpSecurityAttributes,
        [In, MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
        [In, MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
        [In] IntPtr hTemplateFile);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "OpenFileById")]
    static extern SafeFileHandle OpenFileById(
        [In] SafeFileHandle hVolumeHint,
        [In, Out] ref FILE_ID_DESCRIPTOR lpFileId,
        [In, MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
        [In, MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
        [In] IntPtr lpSecurityAttributes,
        [In, MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes
    );

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "GetFinalPathNameByHandleW")]
    static extern int GetFinalPathNameByHandleW(
        SafeFileHandle hFile,
        StringBuilder lpszFilePath,
        int cchFilePath,
        int dwFlags);
}

[StructLayout(LayoutKind.Explicit)]
public struct FILE_ID_DESCRIPTOR
{
    [FieldOffset(0)]
    public int dwSize;
    [FieldOffset(4)]
    public FILE_ID_TYPE Type;
    [FieldOffset(8)]
    public long FileId;
    [FieldOffset(8)]
    public Guid ObjectId;
    [FieldOffset(8)]
    public Guid ExtendedFileId; //Use for ReFS; need to use v3 structures or later instead of v2 as done in this sample
}

public enum FILE_ID_TYPE
{
    FileIdType = 0,
    ObjectIdType = 1,
    ExtendedFileIdType = 2,
    MaximumFileIdType
};
Nolan Blew
  • 374
  • 3
  • 18
0

As I said, use code from pinvoke. This works

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            File_Handle handle = new File_Handle();
            handle.Get_Index();
            handle.Retrieve_File();
        }
    }
    public class WinAPI
    {

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

        [DllImport("kernel32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)]
        public static extern int OpenFile([System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPStr)]string lpFileName, out OFSTRUCT lpReOpenBuff,
           OpenFileStyle uStyle);

        [StructLayout(LayoutKind.Explicit)]
        public struct FILE_ID_DESCRIPTOR
        {
            [FieldOffset(0)]
            public uint dwSize;
            [FieldOffset(4)]
            public FILE_ID_TYPE type;
            // [FieldOffset(8)] public Guid guid;
            [FieldOffset(8)]
            public long FileReferenceNumber;
        }

        public enum FILE_ID_TYPE
        {
            FileIdType = 0,
            ObjectIdType = 1,
            ExtendedFileIdType = 2,
            MaximumFileIdType
        };

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

        [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential)]
        public struct OFSTRUCT
        {
            public byte cBytes;
            public byte fFixedDisc;
            public UInt16 nErrCode;
            public UInt16 Reserved1;
            public UInt16 Reserved2;
            [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 128)]
            public string szPathName;
        }
        [Flags]
        public enum OpenFileStyle : uint
        {
            OF_CANCEL = 0x00000800,  // Ignored. For a dialog box with a Cancel button, use OF_PROMPT.
            OF_CREATE = 0x00001000,  // Creates a new file. If file exists, it is truncated to zero (0) length.
            OF_DELETE = 0x00000200,  // Deletes a file.
            OF_EXIST = 0x00004000,  // Opens a file and then closes it. Used to test that a file exists
            OF_PARSE = 0x00000100,  // Fills the OFSTRUCT structure, but does not do anything else.
            OF_PROMPT = 0x00002000,  // Displays a dialog box if a requested file does not exist 
            OF_READ = 0x00000000,  // Opens a file for reading only.
            OF_READWRITE = 0x00000002,  // Opens a file with read/write permissions.
            OF_REOPEN = 0x00008000,  // Opens a file by using information in the reopen buffer.

            // For MS-DOS–based file systems, opens a file with compatibility mode, allows any process on a 
            // specified computer to open the file any number of times.
            // Other efforts to open a file with other sharing modes fail. This flag is mapped to the 
            // FILE_SHARE_READ|FILE_SHARE_WRITE flags of the CreateFile function.
            OF_SHARE_COMPAT = 0x00000000,

            // Opens a file without denying read or write access to other processes.
            // On MS-DOS-based file systems, if the file has been opened in compatibility mode
            // by any other process, the function fails.
            // This flag is mapped to the FILE_SHARE_READ|FILE_SHARE_WRITE flags of the CreateFile function.
            OF_SHARE_DENY_NONE = 0x00000040,

            // Opens a file and denies read access to other processes.
            // On MS-DOS-based file systems, if the file has been opened in compatibility mode,
            // or for read access by any other process, the function fails.
            // This flag is mapped to the FILE_SHARE_WRITE flag of the CreateFile function.
            OF_SHARE_DENY_READ = 0x00000030,

            // Opens a file and denies write access to other processes.
            // On MS-DOS-based file systems, if a file has been opened in compatibility mode,
            // or for write access by any other process, the function fails.
            // This flag is mapped to the FILE_SHARE_READ flag of the CreateFile function.
            OF_SHARE_DENY_WRITE = 0x00000020,

            // Opens a file with exclusive mode, and denies both read/write access to other processes.
            // If a file has been opened in any other mode for read/write access, even by the current process,
            // the function fails.
            OF_SHARE_EXCLUSIVE = 0x00000010,

            // Verifies that the date and time of a file are the same as when it was opened previously.
            // This is useful as an extra check for read-only files.
            OF_VERIFY = 0x00000400,

            // Opens a file for write access only.
            OF_WRITE = 0x00000001
        }
    }

    public class File_Handle
    {

        public ulong Get_Index()
        {
            WinAPI.BY_HANDLE_FILE_INFORMATION objectFileInfo = new WinAPI.BY_HANDLE_FILE_INFORMATION();

            FileInfo fi = new FileInfo(@"c:\Temp\Test.txt");
            FileStream fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

            WinAPI.GetFileInformationByHandle(fs.Handle, out objectFileInfo);

            fs.Close();

            ulong fileIndex = ((ulong)objectFileInfo.FileIndexHigh << 32) + (ulong)objectFileInfo.FileIndexLow;

            return fileIndex;
        }

        public string Retrieve_File()
        {

            WinAPI.OFSTRUCT ofStruct = new WinAPI.OFSTRUCT();
            int err = WinAPI.OpenFile(@"c:\Temp\Test.txt", out ofStruct, WinAPI.OpenFileStyle.OF_READ);

            return "Dummy";

        }
    }
}
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • It is not being used. I just copied code from original posting and fixed the Retrieve_File() method. – jdweng Apr 30 '17 at 10:02
  • Sorry, maybe I am blind now, but I don't see how you use the index from function "Get_Index" as input of the "Retrieve_File". The matter is to get file information only from the file index, because the file path may have changed. Therefore opening the file in "Retrieve_File" hast to be done using the ID not a fixed path. – B. Ueno Apr 30 '17 at 10:31
  • WHY DO YOU NEED THE INDEX? You can pass the filename to the Retrieve_File() method. You are already getting the index from the filename. – jdweng Apr 30 '17 at 10:51
  • 1
    @jdweng Imagine this szenario: I want to store a link to a file in a .xml document. Now I move the file to another folder on the same drive. If I store the path in the .xml I can't access the file anymore, because the file has been moved. If I store the Index I can retrieve the current location of the file, even if it has been moved. So the link is still valid. But this means, that I can't pass the file path to the Retrieve method, it must be the index. Therefore I have to create a handle to the file from the index, which leads to my previous problem of the access violation. – B. Ueno Apr 30 '17 at 11:06
  • This is due to Microsoft creating a managed objects. Microsoft was loosing business due to computers going Blue screen and things like this had to be done to minimize the chances of a Blue Screen. Access of a file by handle was considered a high risk of accessing the wrong file and corrupting the disk. So it was eliminated. – jdweng Apr 30 '17 at 12:17
  • Yes but it should be possible right? And my attempt with OpenFileByID should work somehow but I think I messed up the descriptor... Can you have a look at it though? – B. Ueno May 01 '17 at 07:27
  • Not by handle, only by name. – jdweng May 06 '17 at 10:44