Создание hardlink и symbolic link
Автор: Alex Konshin
WEB-сайт: http://delphibase.endimus.com
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Создание hardlink и symbolic link.
Исходный код утилиты, которая создает hard и symbolic links почти как в unix.
Hardlink можно создать только для файлов и только на NTFS.
Symbolic link можно создать только для директориев и только на NTFS5 (Win2K/XP)
и он не может указывать на сетевой ресурс.
Зависимости: Windows, SysUtils
Автор: Alex Konshin, akonshin@earthlink.net, Boston, USA
Copyright: http://home.earthlink.net/~akonshin/files/xlink.zip
Дата: 30 декабря 2002 г.
***************************************************** }
program xlink;
uses
Windows, SysUtils;
{$APPTYPE CONSOLE}
{$R xlink.res}
type
TOptions = set of (optSymbolicLink, optOverwrite, optRecursive, optDirectory);
int64rec = packed record
lo: LongWord;
hi: LongInt;
end;
const
FILE_DOES_NOT_EXIST = DWORD(-1);
//=============================================================
function isFileExists(const AFileName: string): Boolean;
var
h: THandle;
rFindData: TWin32FindData;
begin
h := Windows.FindFirstFile(PChar(AFileName), rFindData);
Result := h <> INVALID_HANDLE_VALUE;
if not Result then
Exit;
Windows.FindClose(h);
Result := (rFindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0;
end;
//-------------------------------------------------------------
// warning: function assumes that it is correct directory name
function isDirectoryEmpty(const ADirectoryName: string): Boolean;
var
h: THandle;
len: Integer;
rFindData: TWin32FindData;
sSeachMask: string;
begin
len := Length(ADirectoryName);
if (PChar(ADirectoryName) + len - 1)^ = '\' then
sSeachMask := ADirectoryName + '*'
else
sSeachMask := ADirectoryName + '\*';
h := Windows.FindFirstFile(PChar(sSeachMask), rFindData);
Result := (h = INVALID_HANDLE_VALUE);
Windows.FindClose(h);
end;
//-------------------------------------------------------------
function SysErrorMessage(ErrorCode: Integer): string;
var
Len: Integer;
Buffer: array[0..255] of Char;
begin
Len := FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_ARGUMENT_ARRAY,
nil, ErrorCode, 0, Buffer, SizeOf(Buffer), nil);
while (Len > 0) and (Buffer[Len - 1] in [#0..' ', '.']) do
Dec(Len);
SetString(Result, Buffer, Len);
end;
//-------------------------------------------------------------
procedure _CreateHardlink(AFileName: string; AFileWCName: PWideChar; ALinkName:
string; overwrite: Boolean);
var
aLinkWCFileName, aLinkFullName: array[0..MAX_PATH] of WChar;
pwFilePart: LPWSTR;
hFileSource: THandle;
rStreamId: WIN32_STREAM_ID;
cbPathLen, dwStreamHeaderSize, dwBytesWritten: DWORD;
lpContext: Pointer;
begin
StringToWidechar(ALinkName, aLinkWCFileName, MAX_PATH);
hFileSource :=
Windows.CreateFile(
PChar(AFileName),
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
nil,
OPEN_EXISTING,
0,
0
);
if hFileSource = INVALID_HANDLE_VALUE then
raise Exception.Create('Can''t open file "' + AFileName + '"');
try
cbPathLen := Windows.GetFullPathNameW(aLinkWCFileName, MAX_PATH,
aLinkFullName, pwFilePart);
if cbPathLen <= 0 then
raise Exception.Create('Invalid link name "' + ALinkName + '"');
cbPathLen := (cbPathLen + 1) * SizeOf(WChar);
lpContext := nil;
rStreamId.dwStreamId := BACKUP_LINK;
rStreamId.dwStreamAttributes := 0;
rStreamId.dwStreamNameSize := 0;
int64rec(rStreamId.Size).hi := 0;
int64rec(rStreamId.Size).lo := cbPathLen;
dwStreamHeaderSize := PChar(@rStreamId.cStreamName) - PChar(@rStreamId)
+ LongInt(rStreamId.dwStreamNameSize);
if not BackupWrite(
hFileSource,
Pointer(@rStreamId), // buffer to write
dwStreamHeaderSize, // number of bytes to write
dwBytesWritten,
False, // don't abort yet
False, // don't process security
lpContext
) then
RaiseLastOSError;
if not BackupWrite(
hFileSource,
Pointer(@aLinkFullName), // buffer to write
cbPathLen, // number of bytes to write
dwBytesWritten,
False, // don't abort yet
False, // don't process security
lpContext
) then
RaiseLastOSError;
// free context
if not BackupWrite(
hFileSource,
nil, // buffer to write
0, // number of bytes to write
dwBytesWritten,
True, // abort
False, // don't process security
lpContext
) then
RaiseLastOSError;
finally
CloseHandle(hFileSource);
end;
end;
//-------------------------------------------------------------
// ADirName and ADirForLinks must not end with backslach
procedure _CreateHardlinksForSubDirectory(const ADirName, ADirForLinks: string;
options: TOptions);
var
h: THandle;
sExistedFile, sLinkName: string;
dwAttributes: DWORD;
rFindData: TWin32FindData;
awcFileName: array[0..MAX_PATH] of WChar;
begin
dwAttributes := GetFileAttributes(PChar(ADirForLinks));
if dwAttributes = FILE_DOES_NOT_EXIST then
begin
// WriteLn('Create Directory ',ADirForLinks);
if not CreateDir(ADirForLinks) then
raise Exception.Create('Can''t create directory "' + ADirForLinks + '".');
end
else if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
raise Exception.Create('File "' + ADirName
+ '" already exists and it is not a directory.');
h := Windows.FindFirstFile(PChar(ADirName + '\*'), rFindData);
if h = INVALID_HANDLE_VALUE then
Exit;
try
repeat
if (rFindData.cFileName[0] = '.') and
((rFindData.cFileName[1] = #0) or ((rFindData.cFileName[1] = '.') and
(rFindData.cFileName[2] = #0))) then
Continue;
sExistedFile := ADirName + '\' + rFindData.cFileName;
sLinkName := ADirForLinks + '\' + rFindData.cFileName;
if (rFindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
begin
awcFileName[
Windows.MultiByteToWideChar(0, 0, PChar(sExistedFile),
MAX_PATH, awcFileName, MAX_PATH)
] := #0;
_CreateHardlink(sExistedFile, awcFileName, sLinkName,
optOverwrite in options);
end
else if optRecursive in options then
begin
_CreateHardlinksForSubDirectory(sExistedFile, sLinkName, options);
end;
until not Windows.FindNextFile(h, rFindData);
finally
Windows.FindClose(h);
end;
end;
//-------------------------------------------------------------
procedure CreateHardlink(AFileName, ALinkName: string; options: TOptions);
var
dwAttributes: DWORD;
aFileSource: array[0..MAX_PATH] of WChar;
begin
dwAttributes := Windows.GetFileAttributes(PChar(AFileName));
if dwAttributes = FILE_DOES_NOT_EXIST then
raise Exception.Create('File "' + AFileName + '" does not exist.');
if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
raise Exception.Create('Can''t create hardlink for directory (file "'
+ AFileName + '").');
dwAttributes := Windows.GetFileAttributes(PChar(ALinkName));
if dwAttributes <> FILE_DOES_NOT_EXIST then
begin
if not (optOverwrite in options) then
raise Exception.Create('File "' + ALinkName + '" already exists.');
if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
raise Exception.Create('Can''t overwrite directory "' + AFileName + '".');
end;
StringToWidechar(AFileName, aFileSource, MAX_PATH);
_CreateHardlink(AFileName, aFileSource, ALinkName, optOverwrite in options);
end;
//-------------------------------------------------------------
procedure CreateHardlinksForDirectory(const ADirName, ADirForLinks: string;
options: TOptions);
var
dwAttributes: DWORD;
len: Integer;
sDirName, sDirForLinks: string;
begin
dwAttributes := Windows.GetFileAttributes(PChar(ADirName));
if dwAttributes = FILE_DOES_NOT_EXIST then
raise Exception.Create('Directory "' + ADirName + '" does not exist.');
if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
raise Exception.Create('File "' + ADirName + '" is not a directory.');
len := Length(ADirName);
if (PChar(ADirName) + len - 1)^ = '\' then
sDirName := Copy(ADirName, 1, len - 1)
else
sDirName := ADirName;
if (PChar(ADirForLinks) + Length(ADirForLinks) - 1)^ <> '\' then
sDirForLinks := ADirForLinks
else
sDirForLinks := Copy(ADirForLinks, 1, Length(ADirForLinks) - 1);
_CreateHardlinksForSubDirectory(sDirName, sDirForLinks, options);
end;
//-------------------------------------------------------------
procedure CreateHardlinksInDirectory(const AFileName, ADirForLinks: string;
options: TOptions);
var
dwAttributes: DWORD;
len: Integer;
sFileName, sDirForLinks, sLinkName: string;
aFileSource: array[0..MAX_PATH] of WChar;
begin
dwAttributes := Windows.GetFileAttributes(PChar(AFileName));
if dwAttributes = FILE_DOES_NOT_EXIST then
raise Exception.Create('File or directory "' + AFileName +
'" does not exist.');
if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
begin
sLinkName := ADirForLinks + '\' + SysUtils.ExpandFileName(AFileName);
dwAttributes := Windows.GetFileAttributes(PChar(sLinkName));
if dwAttributes <> FILE_DOES_NOT_EXIST then
begin
if not (optOverwrite in options) then
raise Exception.Create('File "' + sLinkName + '" already exists.');
if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
raise Exception.Create('Can''t overwrite directory "' + AFileName +
'".');
end;
StringToWidechar(AFileName, aFileSource, MAX_PATH);
_CreateHardlink(AFileName, aFileSource, sLinkName,
optOverwrite in options);
end
else
begin
len := Length(AFileName);
if (PChar(AFileName) + len - 1)^ = '\' then
sFileName := Copy(AFileName, 1, len - 1)
else
sFileName := AFileName;
if (PChar(ADirForLinks) + Length(ADirForLinks) - 1)^ <> '\' then
sDirForLinks := ADirForLinks
else
sDirForLinks := Copy(ADirForLinks, 1, Length(ADirForLinks) - 1);
_CreateHardlinksForSubDirectory(sFileName, sDirForLinks, options);
end;
end;
//-------------------------------------------------------------
procedure DeleteDirectoryContent(const ADirName: string);
type
PDirRef = ^TDirRef;
PPDirRef = ^PDirRef;
TDirRef = record
Next: PDirRef;
DirName: string;
end;
var
h: THandle;
sFileName: string;
pSubDirs: PDirRef;
ppLast: PPDirRef;
pDir: PDirRef;
rFindData: TWin32FindData;
begin
pSubDirs := nil;
ppLast := @pSubDirs;
h := Windows.FindFirstFile(PChar(ADirName + '\*'), rFindData);
if h = INVALID_HANDLE_VALUE then
Exit;
try
try
repeat
if (rFindData.cFileName[0] = '.') and
((rFindData.cFileName[1] = #0) or ((rFindData.cFileName[1] = '.') and
(rFindData.cFileName[2] = #0))) then
Continue;
sFileName := ADirName + '\' + rFindData.cFileName;
if (rFindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
begin
New(pDir);
with pDir^ do
begin
Next := nil;
DirName := sFileName;
end;
ppLast^ := pDir;
ppLast := @pDir^.Next;
end
else if not DeleteFile(sFileName) then
raise Exception.Create('Can''t delete file "' + sFileName + '".');
until not Windows.FindNextFile(h, rFindData);
finally
Windows.FindClose(h);
end;
if pSubDirs <> nil then
begin
repeat
pDir := pSubDirs;
pSubDirs := pDir^.Next;
sFileName := pDir^.DirName;
Dispose(pDir);
DeleteDirectoryContent(sFileName);
if not RemoveDir(sFileName) then
raise Exception.Create('Can''t delete directory "' + sFileName +
'".');
until pSubDirs = nil;
end;
except
while pSubDirs <> nil do
begin
pDir := pSubDirs;
pSubDirs := pDir^.Next;
Dispose(pDir);
end;
raise;
end;
end;
//-------------------------------------------------------------
const
FILE_DEVICE_FILE_SYSTEM = $0009;
// Define the method codes for how buffers are passed for I/O and FS controls
METHOD_BUFFERED = 0;
METHOD_IN_DIRECT = 1;
METHOD_OUT_DIRECT = 2;
METHOD_NEITHER = 3;
// Define the access check value for any access
FILE_ANY_ACCESS = 0;
FILE_READ_DATA = 1;
FILE_WRITE_DATA = 2;
FSCTL_SET_REPARSE_POINT = (FILE_DEVICE_FILE_SYSTEM shl 16) or
(FILE_ANY_ACCESS shl 14) or (41 shl 2) or (METHOD_BUFFERED);
FSCTL_GET_REPARSE_POINT = (FILE_DEVICE_FILE_SYSTEM shl 16) or
(FILE_ANY_ACCESS shl 14) or (42 shl 2) or (METHOD_BUFFERED);
FSCTL_DELETE_REPARSE_POINT = (FILE_DEVICE_FILE_SYSTEM shl 16) or
(FILE_ANY_ACCESS shl 14) or (43 shl 2) or (METHOD_BUFFERED);
FILE_FLAG_OPEN_REPARSE_POINT = $00200000;
FILE_ATTRIBUTE_REPARSE_POINT = $00000400;
IO_REPARSE_TAG_MOUNT_POINT = $A0000003;
REPARSE_MOUNTPOINT_HEADER_SIZE = 8;
type
REPARSE_MOUNTPOINT_DATA_BUFFER = packed record
ReparseTag: DWORD;
ReparseDataLength: DWORD;
Reserved: Word;
ReparseTargetLength: Word;
ReparseTargetMaximumLength: Word;
Reserved1: Word;
ReparseTarget: array[0..0] of WChar;
end;
TReparseMountpointDataBuffer = REPARSE_MOUNTPOINT_DATA_BUFFER;
PReparseMountpointDataBuffer = ^TReparseMountpointDataBuffer;
//-------------------------------------------------------------
function CreateSymlink(ATargetName, ALinkName: string; const options: TOptions):
Boolean;
const
pwcNativeFileNamePrefix: PWideChar = '\??\';
nNativeFileNamePrefixWCharLength = 4;
nNativeFileNamePrefixByteLength = nNativeFileNamePrefixWCharLength * 2;
var
hLink: THandle;
pReparseInfo: PReparseMountpointDataBuffer;
len, size: Integer;
pwcLinkFileName: PWideChar;
pwcTargetNativeFileName: PWideChar;
pwcTargetFileName: PWideChar;
pwc: PWideChar;
pc: PChar;
dwBytesReturned: DWORD;
dwAttributes: DWORD;
bDirectoryCreated: Boolean;
aTargetFullName: array[0..MAX_PATH] of Char;
begin
Result := False;
pReparseInfo := nil;
hLink := INVALID_HANDLE_VALUE;
bDirectoryCreated := False;
len := Length(ALinkName);
if ((PChar(ALinkName) + len - 1)^ = '\') and ((PChar(ALinkName) + len - 2)^ <>
':') then
begin
Dec(len);
SetLength(ALinkName, len);
end;
System.GetMem(pwcLinkFileName, len + len + 2);
try
pwcLinkFileName[
Windows.MultiByteToWideChar(0, 0, PChar(ALinkName), len, wcLinkFileName,
len)
] := #0;
dwAttributes := Windows.getFileAttributesW(pwcLinkFileName);
if dwAttributes <> FILE_DOES_NOT_EXIST then
begin
if not (optOverwrite in options) then
begin
if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
raise Exception.Create('The file "' + ALinkName + '" already exists');
if not isDirectoryEmpty(ALinkName) then
raise Exception.Create(
'The directory "' + ALinkName +
'" already exists and is not empty');
dwAttributes := FILE_DOES_NOT_EXIST;
end
else if ((dwAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0) then
begin
if not DeleteFile(ALinkName) then
raise Exception.Create('Can''t overwrite file "' + ALinkName + '"');
dwAttributes := FILE_DOES_NOT_EXIST;
end
else if (dwAttributes and FILE_ATTRIBUTE_REPARSE_POINT)
<> FILE_ATTRIBUTE_REPARSE_POINT then
if not isDirectoryEmpty(ALinkName) then
begin
if not (optDirectory in options) then
raise Exception.Create('Can''t overwrite non-empty directory "'
+ ALinkName + '"');
DeleteDirectoryContent(ALinkName);
end;
end;
if dwAttributes = FILE_DOES_NOT_EXIST then
begin
Windows.CreateDirectoryW(pwcLinkFileName, nil);
bDirectoryCreated := True;
end;
try
hLink := Windows.CreateFileW(pwcLinkFileName, GENERIC_WRITE, 0, nil,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT or FILE_FLAG_BACKUP_SEMANTICS, 0);
if hLink = INVALID_HANDLE_VALUE then
RaiseLastOSError;
len := Length(ATargetName);
if ((PChar(ATargetName) + len - 1)^ = '\')
and ((PChar(ATargetName) + len - 2)^ <> ':') then
begin
Dec(len);
SetLength(ATargetName, len);
end;
len := Windows.GetFullPathName(PChar(ATargetName), MAX_PATH,
aTargetFullName, pc);
size := len + len + 2
+ nNativeFileNamePrefixByteLength + REPARSE_MOUNTPOINT_HEADER_SIZE + 12;
System.GetMem(pReparseInfo, size);
FillChar(pReparseInfo^, size, #0);
pwcTargetNativeFileName := @pReparseInfo^.ReparseTarget;
System.Move(pwcNativeFileNamePrefix^, pwcTargetNativeFileName^,
nNativeFileNamePrefixByteLength + 2);
pwcTargetFileName := pwcTargetNativeFileName +
nNativeFileNamePrefixWCharLength;
pwc := pwcTargetFileName + Windows.MultiByteToWideChar(0, 0,
aTargetFullName, len, pwcTargetFileName, len);
pwc^ := #0;
with pReparseInfo^ do
begin
ReparseTag := IO_REPARSE_TAG_MOUNT_POINT;
ReparseTargetLength := PChar(pwc) - PChar(pwcTargetNativeFileName);
ReparseTargetMaximumLength := ReparseTargetLength + 2;
ReparseDataLength := ReparseTargetLength + 12;
end;
dwBytesReturned := 0;
if not DeviceIoControl(hLink, FSCTL_SET_REPARSE_POINT, pReparseInfo,
pReparseInfo^.ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE,
nil, 0, dwBytesReturned, nil) then
RaiseLastOSError;
except
if bDirectoryCreated then
RemoveDirectoryW(pwcLinkFileName);
raise;
end;
Result := true;
finally
if hLink <> INVALID_HANDLE_VALUE then
Windows.CloseHandle(hLink);
if pwcLinkFileName <> nil then
System.FreeMem(pwcLinkFileName);
if pReparseInfo <> nil then
System.FreeMem(pReparseInfo);
end;
end;
//-------------------------------------------------------------
procedure Help;
begin
WriteLn;
WriteLn('Create link(s) on NTFS.');
WriteLn;
WriteLn('Usage:');
WriteLn;
WriteLn('To create hardlink(s) (works only for files):');
WriteLn('xlink [-fr] <existed_file> <link_name>');
WriteLn;
WriteLn('To create symbolic link (works on Windows 2k/XP for directories only):');
WriteLn('xlink -s[f|F] <existed_directory> <link_name>');
WriteLn;
WriteLn('Options:');
WriteLn('-f Overwrite file with name <link_name> if it exists.');
WriteLn('-F Overwrite file/directory with name <link_name> if it exists.');
WriteLn('-r Recursive.');
WriteLn;
WriteLn('(c) 2002 Alex Konshin');
Halt;
end;
//-------------------------------------------------------------
procedure Execute;
var
iArg: Integer;
sArg: string;
ptr: PChar;
options: TOptions;
sExistedFileName: string;
sLink: string;
dwAttrs: DWORD;
begin
iArg := 1;
repeat
sArg := ParamStr(iArg);
if sArg = '' then
Help;
if PChar(sArg)^ <> '-' then
Break;
ptr := PChar(sArg) + 1;
while ptr^ <> #0 do
begin
case ptr^ of
's', 'S': Include(options, optSymbolicLink);
'h', 'H': Help;
'F': options := options + [optOverwrite, optDirectory];
'f': Include(options, optOverwrite);
'r', 'R': Include(options, optRecursive);
'd', 'D': Include(options, optDirectory);
else
WriteLn('Error: Invalid option ''-', ptr^, '''');
Exit;
end;
Inc(ptr);
end;
Inc(iArg);
until iArg <= ParamCount;
if ParamCount <= iArg then
Help;
if ParamCount - iArg > 1 then
Include(options, optDirectory);
if optSymbolicLink in options then
begin
sLink := ParamStr(ParamCount);
repeat
sExistedFileName := ParamStr(iArg);
if not CreateSymlink(sExistedFileName, sLink, options) then
WriteLn('The symbolic link creation failed.');
Inc(iArg);
until iArg >= ParamCount;
end
else if (options * [optRecursive, optDirectory]) <> [] then
begin
sLink := ParamStr(ParamCount);
repeat
sExistedFileName := ParamStr(iArg);
CreateHardlinksInDirectory(sExistedFileName, sLink, options);
Inc(iArg);
until iArg >= ParamCount;
end
else
begin
sExistedFileName := ParamStr(iArg);
sLink := ParamStr(ParamCount);
dwAttrs := GetFileAttributes(PChar(sExistedFileName));
if dwAttrs = FILE_DOES_NOT_EXIST then
begin
writeln('Error: The source file does not exist');
Exit;
end;
if (dwAttrs and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
begin
writeln('Error: Can''t create hardlink for directory');
Exit;
end;
CreateHardlink(sExistedFileName, sLink, options);
end;
end;
//=============================================================
begin
if ParamCount < 2 then
Help;
try
Execute;
except
on E: Exception do
begin
WriteLn(E.ClassName + ': ' + E.Message);
end;
end;
end.
|