Создание hardlink и symbolic link.

Создание hardlink и symbolic link.

{ **** UBPFD *********** by kladovka.net.ru ****
>>
Исходный код утилиты, которая создает hard и symbolic links почти как в unix.
Hardlink можно создать только для файлов и только на NTFS.
Symbolic link можно создать только для директориев и только на
NTFS5 (Win2K/XP) и он не может указывать на сетевой ресурс.
Зависимости: Windows, SysUtils
Автор: Alex Konshin, <a href="mailto:akonshin@earthlink.net">akonshin@earthlink.net</a>, 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..#32, '.']) 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('Cannot 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('Cannot 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.FindFile(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('Cannot 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('Cannot 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('Cannot 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
   : 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
   := nil;
  DirName := sFileName;
  end;
  ppLast^ := pDir;
  ppLast := @pDir^.;
  end
  else if not DeleteFile(sFileName) then
  raise Exception.Create('Cannot delete file "'+sFileName+'".');
  until not Windows.FindFile(h,rFindData);
  finally
  Windows.FindClose(h);
  end;
  if pSubDirs<>nil then
  begin
  repeat
  pDir := pSubDirs;
  pSubDirs := pDir^.;
  sFileName := pDir^.DirName;
  Dispose(pDir);
  DeleteDirectoryContent(sFileName);
  if not RemoveDir(sFileName) then
  raise Exception.Create('Cannot delete directory "'+sFileName+'".');
  until pSubDirs=nil;
  end;
 except
  while pSubDirs<>nil do
  begin
  pDir := pSubDirs;
  pSubDirs := pDir^.;
  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('Cannot 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('Cannot 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: Cannot 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.

Отправить комментарий

Проверка
Антиспам проверка
Image CAPTCHA
...