I had an interesting read over at the “Old new thing” website. Discussing if it’s possible to open a file by its id rather then using its full qualified path and filename. You can find the original article here: Raymond Chen
Background
On NTFS every single file on a volume has a object identifier which is a 16-byte buffer. If a file doesn’t have an object identifier you can ask for one to be created for this you need to make use of FSCTL_CREATE_OR_GET_OBJECT_ID.
As you may noticed these are IOCTL Codes used to send a control code directly to a specified device driver, causing the corresponding device to perform the corresponding operation.
DeviceIoControl Function
BOOL WINAPI DeviceIoControl( __in HANDLE hDevice, __in DWORD dwIoControlCode, __in_opt LPVOID lpInBuffer, __in DWORD nInBufferSize, __out_opt LPVOID lpOutBuffer, __in DWORD nOutBufferSize, __out_opt LPDWORD lpBytesReturned, __inout_opt LPOVERLAPPED lpOverlapped );
IOCTL Codes to be used with the object identifier
IOCTL Code Description
FSCTL_CREATE_OR_GET_OBJECT_ID Retrieve the existing object identifier associated with a file, or create one if there isn't one already.
FSCTL_SET_OBJECT_ID Specifies the GUID you want to use as the object identifier. The call fails if the file already has an object identifier.
FSCTL_GET_OBJECT_ID Retrieves the object identifier, if any.
Original code which we need to translate to a Delphi equivalent
#define UNICODE
#define _UNICODE
#include
#include
#include
#include
#include
int __cdecl _tmain(int argc, PTSTR *argv)
{
HANDLE h = CreateFile(argv[1], 0,
FILE_SHARE_READ | FILE_SHARE_WRITE |
FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, 0, NULL);
if (h != INVALID_HANDLE_VALUE) {
FILE_OBJECTID_BUFFER buf;
DWORD cbOut;
if (DeviceIoControl(h, FSCTL_CREATE_OR_GET_OBJECT_ID,
NULL, 0, &buf, sizeof(buf),
&cbOut, NULL)) {
GUID guid;
CopyMemory(&guid, &buf.ObjectId, sizeof(GUID));
WCHAR szGuid[39];
StringFromGUID2(guid, szGuid, 39);
_tprintf(_T("GUID is %ws\n"), szGuid);
}
CloseHandle(h);
}
return 0;
}
This program takes as parameter an directory or filename and prints out the object identifier.
2nd Part using the received GUID to open the file
#define UNICODE
#define _UNICODE
#include
#include
#include
#include
int __cdecl _tmain(int argc, PTSTR *argv)
{
HANDLE hRoot = CreateFile(_T("C:\\"), 0,
FILE_SHARE_READ | FILE_SHARE_WRITE |
FILE_SHARE_DELETE, NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hRoot != INVALID_HANDLE_VALUE) {
FILE_ID_DESCRIPTOR desc;
desc.dwSize = sizeof(desc);
desc.Type = ObjectIdType;
if (SUCCEEDED(CLSIDFromString(argv[1], &desc.ObjectId))) {
HANDLE h = OpenFileById(hRoot, &desc, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE |
FILE_SHARE_DELETE, NULL, 0);
if (h != INVALID_HANDLE_VALUE) {
BYTE b;
DWORD cb;
if (ReadFile(h, &b, 1, &cb, NULL)) {
_tprintf(_T("First byte of file is 0x%02x\n"), b);
}
CloseHandle(h);
}
}
CloseHandle(hRoot);
}
return 0;
}
Well, making things work you first need to open something on the volume the file resides on. This is just needed to let the OpenFileById function knows on what volume we are working. In the example above Mr Raymond Chen is working on the C: drive which means that the file search will take place on the C: drive.
The Delphi equivalent
As you can see from the two sample programs above we need to convert first a few types into Delphi bevor we can start writing our demo there are:
FILE_ID_TYPE
Discriminator for the union in the FILE_ID_DESCRIPTOR structure.
type
FILE_ID_TYPE = (
FileIdType,
ObjectIdType,
MaximumFileIdType
);
{$EXTERNALSYM FILE_ID_TYPE}
FILE_ID_DESCRIPTOR
Specifies the type of ID that is being used.
type
_File_ID_DESCRIPTOR = record
dwSize: DWORD;
cType: FILE_ID_TYPE;
case integer of
0:
(FileId: LARGE_INTEGER);
1:
(ObjectID: TGUID);
end;
FILE_ID_DESCRIPTOR = _File_ID_DESCRIPTOR;
{$EXTERNALSYM FILE_ID_DESCRIPTOR}
LPFILE_ID_DESCRIPTOR = ^_File_ID_DESCRIPTOR;
{$EXTERNALSYM LPFILE_ID_DESCRIPTOR}
TFILE_ID_DESCRIPTOR = _File_ID_DESCRIPTOR;
{$EXTERNALSYM TFILE_ID_DESCRIPTOR}
FILE_NAME_INFO
Receives the file name. Used for any handles. Use only when calling GetFileInformationByHandleEx.
type
_FILE_NAME_INFO = record
FileNameLength : DWORD;
FileName : array[0..Max_Path] of WCHAR;
end;
FILE_NAME_INFO = _FILE_NAME_INFO;
{$EXTERNALSYM FILE_NAME_INFO}
PFILE_NAME_INFO = ^_FILE_NAME_INFO;
{$EXTERNALSYM PFILE_NAME_INFO}
TFILE_NAME_INFO = _FILE_NAME_INFO;
{$EXTERNALSYM TFILE_NAME_INFO}
FILE_INFO_BY_HANDLE_CLASS
Identifies the type of file information that GetFileInformationByHandleExshould retrieve or SetFileInformationByHandle should set.
type
_FILE_INFO_BY_HANDLE_CLASS = (
FileBasicInfo = 0,
FileStandardInfo = 1,
FileNameInfo = 2,
FileRenameInfo = 3,
FileDispositionInfo = 4,
FileAllocationInfo = 5,
FileEndOfFileInfo = 6,
FileStreamInfo = 7,
FileCompressionInfo = 8,
FileAttributeTagInfo = 9,
FileIdBothDirectoryInfo = 10,
FileIdBothDirectoryRestartInfo = 11,
FileIoPriorityHintInfo = 12,
FileRemoteProtocolInfo = 13,
MaximumFileInfoByHandlesClass = 14
);
FILE_INFO_BY_HANDLE_CLASS = _FILE_INFO_BY_HANDLE_CLASS;
{$EXTERNALSYM FILE_INFO_BY_HANDLE_CLASS}
PFILE_INFO_BY_HANDLE_CLASS = ^_FILE_INFO_BY_HANDLE_CLASS;
{$EXTERNALSYM PFILE_INFO_BY_HANDLE_CLASS}
TFILE_INFO_BY_HANDLE_CLASS = _FILE_INFO_BY_HANDLE_CLASS;
{$EXTERNALSYM TFILE_INFO_BY_HANDLE_CLASS}
Full source of the Delphi demo program
unit uMain;
interface
uses
Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ActiveX,
JwaWindows, ExtCtrls;
type
FILE_ID_TYPE = (
FileIdType,
ObjectIdType,
MaximumFileIdType
);
{$EXTERNALSYM FILE_ID_TYPE}
type
_File_ID_DESCRIPTOR = record
dwSize: DWORD;
cType: FILE_ID_TYPE;
case integer of
0:
(FileId: LARGE_INTEGER);
1:
(ObjectID: TGUID);
end;
FILE_ID_DESCRIPTOR = _File_ID_DESCRIPTOR;
{$EXTERNALSYM FILE_ID_DESCRIPTOR}
LPFILE_ID_DESCRIPTOR = ^_File_ID_DESCRIPTOR;
{$EXTERNALSYM LPFILE_ID_DESCRIPTOR}
TFILE_ID_DESCRIPTOR = _File_ID_DESCRIPTOR;
{$EXTERNALSYM TFILE_ID_DESCRIPTOR}
type
_FILE_NAME_INFO = record
FileNameLength : DWORD;
FileName : array[0..Max_Path] of WCHAR;
end;
FILE_NAME_INFO = _FILE_NAME_INFO;
{$EXTERNALSYM FILE_NAME_INFO}
PFILE_NAME_INFO = ^_FILE_NAME_INFO;
{$EXTERNALSYM PFILE_NAME_INFO}
TFILE_NAME_INFO = _FILE_NAME_INFO;
{$EXTERNALSYM TFILE_NAME_INFO}
type
_FILE_INFO_BY_HANDLE_CLASS = (
FileBasicInfo = 0,
FileStandardInfo = 1,
FileNameInfo = 2,
FileRenameInfo = 3,
FileDispositionInfo = 4,
FileAllocationInfo = 5,
FileEndOfFileInfo = 6,
FileStreamInfo = 7,
FileCompressionInfo = 8,
FileAttributeTagInfo = 9,
FileIdBothDirectoryInfo = 10,
FileIdBothDirectoryRestartInfo = 11,
FileIoPriorityHintInfo = 12,
FileRemoteProtocolInfo = 13,
MaximumFileInfoByHandlesClass = 14
);
FILE_INFO_BY_HANDLE_CLASS = _FILE_INFO_BY_HANDLE_CLASS;
{$EXTERNALSYM FILE_INFO_BY_HANDLE_CLASS}
PFILE_INFO_BY_HANDLE_CLASS = ^_FILE_INFO_BY_HANDLE_CLASS;
{$EXTERNALSYM PFILE_INFO_BY_HANDLE_CLASS}
TFILE_INFO_BY_HANDLE_CLASS = _FILE_INFO_BY_HANDLE_CLASS;
{$EXTERNALSYM TFILE_INFO_BY_HANDLE_CLASS}
type
TMainForm = class(TForm)
FileOpenDialog1: TFileOpenDialog;
EdtFileName: TEdit;
EdtFileGuid: TEdit;
LblFilename: TLabel;
LblFileGuid: TLabel;
BtnOpnFile: TButton;
Button1: TButton;
btnCreateGUID: TButton;
edtFileInfo: TEdit;
lblFileInfo: TLabel;
Info: TImage;
procedure BtnOpnFileClick(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnCreateGUIDClick(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
function FilenameToGUIDStr(const AFilename: String): Boolean;
function PrintGUID(AGUID: TGUID): string;
function OpenFilenameFromGUID(const ARoot: string;
ACLSIDStr: string): Boolean;
end;
var
MainForm: TMainForm;
function OpenFileByID(hFile: THandle; lpFileID: LPFILE_ID_DESCRIPTOR;
dwDesiredAccess: DWORD; dwShareMode: DWORD;
lpSecurityAttributes: PSecurityAttributes; dwFlags: DWORD): THandle;
stdcall; external kernel32 name 'OpenFileById';
{$EXTERNALSYM OpenFileByID}
function GetFileInformationByHandleEx(hFile: THandle;
FileInformationClass: FILE_INFO_BY_HANDLE_CLASS;
lpFileInformation: LPVOID;
dwBufferSize: DWORD): BOOL; stdcall; external kernel32 name 'GetFileInformationByHandleEx';
{$EXTERNALSYM GetFileInformationByHandleEx}
implementation
{$R *.dfm}
procedure TMainForm.BtnOpnFileClick(Sender: TObject);
begin
if not(FileOpenDialog1.Execute) then
Exit;
FilenameToGUIDStr(FileOpenDialog1.FileName);
end;
function TMainForm.PrintGUID(AGUID: TGUID): string;
begin
Result := GUIDToString(AGUID);
end;
procedure TMainForm.Button1Click(Sender: TObject);
begin
OpenFilenameFromGUID('C:\', EdtFileGuid.Text);
end;
function TMainForm.FilenameToGUIDStr(const AFilename: String): Boolean;
var
h: THandle;
buf: FILE_OBJECTID_BUFFER;
lpOutBuffer: PDWORD;
GUID: TGUID;
begin
Result := False;
try
h := CreateFile(PChar(AFilename), 0,
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if not(h = INVALID_HANDLE_VALUE) then
if (DeviceIoControl(h, FSCTL_CREATE_OR_GET_OBJECT_ID
{ FSCTL_GET_OBJECT_ID }
{ FSCTL_SET_OBJECT_ID } , nil, 0, @buf, sizeof(buf), @lpOutBuffer,
nil)) then
CopyMemory(@GUID, @buf.ObjectID, sizeof(GUID));
EdtFileGuid.Text := PrintGUID(GUID);
EdtFileName.Text := AFilename;
except
RaiseLastOSError;
end;
CloseHandle(h);
Result := True;
end;
function TMainForm.OpenFilenameFromGUID(const ARoot: string;
ACLSIDStr: string): Boolean;
var
hRoot: THandle;
h: THandle;
desc: File_ID_DESCRIPTOR;
pFilenameInfoClass : PFILE_NAME_INFO;
dwSize: DWORD;
begin
Result := False;
hRoot := CreateFile(PChar(ARoot), 0,
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hRoot <> INVALID_HANDLE_VALUE) then
begin
desc.dwSize := sizeof(desc);
desc.cType := ObjectIdType;
if (SUCCEEDED(CLSIDFromString(PChar(ACLSIDStr), desc.ObjectID))) then
try
h := OpenFileByID(hRoot, @desc, GENERIC_READ,
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, 0);
if (h <> INVALID_HANDLE_VALUE) then
begin
dwSize := sizeof(TFILE_NAME_INFO) + sizeof(WCHAR) * $8000;
pFilenameInfoClass := AllocMem(dwSize);
if Assigned(pFilenameInfoClass) then
begin
if not GetFileInformationByHandleEx(h, FileNameInfo,
pFilenameInfoClass, dwSize) then
RaiseLastOSError;
end;
CloseHandle(h);
Result := True;
edtFileInfo.Text := pFilenameInfoClass.FileName;
// do whatever you need with your file here...
end;
except
CloseHandle(hRoot);
RaiseLastOSError;
end;
CloseHandle(hRoot);
FreeMem(pFilenameInfoClass, sizeof(pFilenameInfoClass));
end;
end;
end.
References
Raymond Chen original author of this article.
Microsoft OpenFileByID
-
http://www.remkoweijnen.nl Remko



