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 件のコメント:
コメントを投稿