Delphi - объектно-ориентированный язык программирования, разработанный компанией Borland в 1995 году. Он основан на языке программирования Pascal, но имеет более расширенные возможности и добавлены новые функции.
Delphi является интегрированной средой разработки (IDE), которая позволяет разрабатывать программное обеспечение для различных платформ, включая Windows, macOS, Android и iOS. Delphi достигает многоплатформенности с помощью...
{ **** 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
.