Windows上でファイル/ディレクトリに対してリンク(複数のエントリを用意する)する方法にはシンボリックリンク、ジャンクション、ハードリンクがありますが、ここではDelphiからハードリンクを扱います。
ハードリンクはNTFS上のファイル本体に対するディレクトリエントリを複数用意する(あるいはファイル本体に複数のパス名をつける)、というもので、一般のユーザ権限で作れ、NTFS以外にSMB3.0でもサポート(ReFSは不可)されていますが、ディレクトリを扱うことができず、同一ボリューム上にしかリンクを作ることができません。また最大のリンク数が1023に制限されています。
Windowsのシンボリックリンクとジャンクションとハードリンクの違い:Tech TIPS - @IT
普通に(CreateFile関数で)作成したファイルはリンク数が1になっており、ハードリンクを作成する毎にリンク数が増え、逆にハードリンクをDeleteFile関数で削除する毎にリンク数は減っていき、リンク数が0になるとそのファイルの実体も削除されます。
ここではハードリンクの作成と、指定されたファイルのハードリンクの数と一覧の取得をDelphiから行います。以下のコードは(System.)IOUtilsユニットのTPathレコード型や無名メソッドを使っているためにDelphi 2010以降の対応になっていますが、それ以前のバージョンであっても適当に修正すれば動くはずです。
interface
{$IF RTLVersion < 21.0}
{$MESSAGE ERROR 'Delphi 2010 or later is required.'}
{$IFEND}
uses
{$IF RTLVersion < 23.0}
Windows, SysUtils, Classes, IOUtils;
{$ELSE}
Winapi.Windows,
System.SysUtils, System.Classes, System.IOUtils;
{$IFEND}
type
{ THardLink }
THardLink = record
private
class procedure DoGetFileList(const Filename: String; EnumProc: TProc); static;
public
class procedure Create(const LinkFile: String; const SourceFile: String); static;
class function GetFileList(const Filename: String): TArray; overload; static;
class procedure GetFileList(const Filename: String; Strings: TStrings); overload; static;
class function GetNumberOfLinks(const Filename: String): Integer; static;
end;
implementation
{ HANDLE FindFirstFileNameW(LPCWSTR lpFileName, DWORD dwFlags, LPDWORD StringLength, PWSTR LinkName); }
function FindFirstFileNameW(const lpFileName: PWideChar; dwFlags: DWORD; var StringLength: DWORD; LinkName: PWideChar): THandle; stdcall; external kernel32;
{$EXTERNALSYM FindFirstFileNameW}
{ BOOL FindNextFileNameW(HANDLE hFindStream, LPDWORD StringLength, PWSTR LinkName); }
function FindNextFileNameW(hFindStream: THandle; var StringLength: DWORD; LinkName: PWideChar): BOOL; stdcall; external kernel32;
{$EXTERNALSYM FindNextFileNameW}
class procedure THardLink.Create(const LinkFile: String; const SourceFile: String);
begin
if CreateHardLink(PChar(LinkFile),PChar(SourceFile),nil) = False then
begin
RaiseLastOSError;
end;
end;
class function THardLink.GetFileList(const Filename: String): TArray;
var
Files: TArray;
begin
SetLength(Files,0);
DoGetFileList(Filename,
procedure (Filename: String)
begin
{$IF RTLVersion >= 28.0}
Files := Files + [Filename];
{$ELSE}
SetLength(Files,Length(Files) + 1);
Files[Length(Files) - 1] := Filename;
{$IFEND}
end);
Result := Files;
end;
class procedure THardLink.GetFileList(const Filename: String; Strings: TStrings);
begin
Strings.Clear;
DoGetFileList(Filename,
procedure (Filename: String)
begin
Strings.Add(Filename);
end);
end;
class function THardLink.GetNumberOfLinks(const Filename: String): Integer;
var
hFile: THandle;
FileInformation: TByHandleFileInformation;
begin
hFile := CreateFile(PChar(Filename),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
if hFile = INVALID_HANDLE_VALUE then
begin
RaiseLastOSError;
end;
try
if GetFileInformationByHandle(hFile,FileInformation) = False then
begin
RaiseLastOSError;
end;
Result := FileInformation.nNumberOfLinks;
finally
CloseHandle(hFile);
end;
end;
class procedure THardLink.DoGetFileList(const Filename: String; EnumProc: TProc);
var
hFindStream: THandle;
Len: DWORD;
Buffer: String;
Root: String;
begin
Root := ExcludeTrailingPathDelimiter(TPath.GetPathRoot(Filename));
{ Retrieve buffer size }
Len := 0;
FindFirstFilenameW(PWideChar(Filename),0,Len,nil);
if GetLastError <> ERROR_MORE_DATA then
begin
RaiseLastOSError;
end;
SetLength(Buffer,Len);
{ Get first filename without drive letter }
hFindStream := FindFirstFilenameW(PWideChar(Filename),0,Len,PWideChar(Buffer));
if hFindStream = INVALID_HANDLE_VALUE then
begin
RaiseLastOSError;
end;
try
while True do
begin
{ Adjust buffer size }
SetLength(Buffer,Len - 1);
{ Callback }
EnumProc(Root + Buffer);
{ Retrieve buffer size }
Len := 0;
FindNextFileNameW(hFindStream,Len,nil);
case GetLastError of
ERROR_HANDLE_EOF:
begin
Break;
end;
ERROR_MORE_DATA:
begin
end;
else
begin
RaiseLastOSError;
end;
end;
{ Get next filename without drive letter }
SetLength(Buffer,Len);
if FindNextFileNameW(hFindStream,Len,PWideChar(Buffer)) = False then
begin
RaiseLastOSError;
end;
end;
finally
{ Close }
{$IF RTLVersion >= 23.0}Winapi.{$IFEND}Windows.FindClose(hFindStream);
end;
end;
ハードリンクはCreateHardLink関数で作成し、リンク数はGetFileInformationByHandle関数で取得したBY_HANDLE_FILE_INFORMATION構造体のnNumberOfLinksで知ることができます。またハードリンクの一覧はFindFirstFileName関数/FindNextFileName関数/FindClose関数で取得できます。このときFindFirstFilename関数/FindNextFileName関数を一旦LinkName=nilで呼び出して必要なサイズを取得し、ファイル名の格納に必要な領域を確保してからもう一度FindFirstFilename関数/FindNextFileName関数を呼び出しています。
→WindowsのNTFSでハードリンクを扱う(Gist)
0 件のコメント:
コメントを投稿