unit LazFileUtils; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LazUTF8, LazUtf8Classes, SysConst, LazUtilsStrConsts; {$IFDEF Windows} {$define CaseInsensitiveFilenames} {$define HasUNCPaths} {$ENDIF} {$IFDEF darwin} {$define CaseInsensitiveFilenames} {$ENDIF} {$IF defined(CaseInsensitiveFilenames) or defined(darwin)} {$DEFINE NotLiteralFilenames} // e.g. HFS+ normalizes file names {$ENDIF} function CompareFilenames(const Filename1, Filename2: string): integer; function CompareFilenamesIgnoreCase(const Filename1, Filename2: string): integer; function CompareFileExt(const Filename, Ext: string; CaseSensitive: boolean): integer; overload; function CompareFileExt(const Filename, Ext: string): integer; overload; function CompareFilenameStarts(const Filename1, Filename2: string): integer; function CompareFilenames(Filename1: PChar; Len1: integer; Filename2: PChar; Len2: integer): integer; function CompareFilenamesP(Filename1, Filename2: PChar; IgnoreCase: boolean = false // false = use default ): integer; function DirPathExists(DirectoryName: string): boolean; function DirectoryIsWritable(const DirectoryName: string): boolean; function ExtractFileNameOnly(const AFilename: string): string; function FilenameIsAbsolute(const TheFilename: string):boolean; function FilenameIsWinAbsolute(const TheFilename: string):boolean; function FilenameIsUnixAbsolute(const TheFilename: string):boolean; function ForceDirectory(DirectoryName: string): boolean; procedure CheckIfFileIsExecutable(const AFilename: string); procedure CheckIfFileIsSymlink(const AFilename: string); function FileIsExecutable(const AFilename: string): boolean; function FileIsSymlink(const AFilename: string): boolean; function FileIsHardLink(const AFilename: string): boolean; function FileIsReadable(const AFilename: string): boolean; function FileIsWritable(const AFilename: string): boolean; function FileIsText(const AFilename: string): boolean; function FileIsText(const AFilename: string; out FileReadable: boolean): boolean; function FilenameIsTrimmed(const TheFilename: string): boolean; function FilenameIsTrimmed(StartPos: PChar; NameLen: integer): boolean; function TrimFilename(const AFilename: string): string; function ResolveDots(const AFilename: string): string; function CleanAndExpandFilename(const Filename: string): string; // empty string returns current directory function CleanAndExpandDirectory(const Filename: string): string; // empty string returns current directory function TrimAndExpandFilename(const Filename: string; const BaseDir: string = ''): string; // empty string returns empty string function TrimAndExpandDirectory(const Filename: string; const BaseDir: string = ''): string; // empty string returns empty string function TryCreateRelativePath(const Dest, Source: String; UsePointDirectory: boolean; out RelPath: String): Boolean; function CreateRelativePath(const Filename, BaseDirectory: string; UsePointDirectory: boolean = false): string; function FileIsInPath(const Filename, Path: string): boolean; function AppendPathDelim(const Path: string): string; function ChompPathDelim(const Path: string): string; // search paths function CreateAbsoluteSearchPath(const SearchPath, BaseDirectory: string): string; function CreateRelativeSearchPath(const SearchPath, BaseDirectory: string): string; function MinimizeSearchPath(const SearchPath: string): string; function FindPathInSearchPath(APath: PChar; APathLen: integer; SearchPath: PChar; SearchPathLen: integer): PChar; // file operations function FileExistsUTF8(const Filename: string): boolean; function FileAgeUTF8(const FileName: string): Longint; function DirectoryExistsUTF8(const Directory: string): Boolean; function ExpandFileNameUTF8(const FileName: string; {const} BaseDir: string = ''): string; function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint; function FindNextUTF8(var Rslt: TSearchRec): Longint; procedure FindCloseUTF8(var F: TSearchrec); function FileSetDateUTF8(const FileName: String; Age: Longint): Longint; function FileGetAttrUTF8(const FileName: String): Longint; function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint; function DeleteFileUTF8(const FileName: String): Boolean; function RenameFileUTF8(const OldName, NewName: String): Boolean; function FileSearchUTF8(const Name, DirList : String): String; function FileIsReadOnlyUTF8(const FileName: String): Boolean; function GetCurrentDirUTF8: String; function SetCurrentDirUTF8(const NewDir: String): Boolean; function CreateDirUTF8(const NewDir: String): Boolean; function RemoveDirUTF8(const Dir: String): Boolean; function ForceDirectoriesUTF8(const Dir: string): Boolean; function FileOpenUTF8(Const FileName : string; Mode : Integer) : THandle; function FileCreateUTF8(Const FileName : string) : THandle; overload; function FileCreateUTF8(Const FileName : string; Rights: Cardinal) : THandle; overload; Function FileCreateUtf8(Const FileName : String; ShareMode : Integer; Rights : Cardinal) : THandle; overload; function FileSizeUtf8(const Filename: string): int64; // UNC paths function IsUNCPath(const {%H-}Path: String): Boolean; function ExtractUNCVolume(const {%H-}Path: String): String; function ExtractFileRoot(FileName: String): String; procedure SplitCmdLineParams(const Params: string; ParamList: TStrings; ReadBackslash: boolean = false); type TInvalidateFileStateCacheEvent = procedure(const Filename: string); var OnInvalidateFileStateCache: TInvalidateFileStateCacheEvent = nil; procedure InvalidateFileStateCache(const Filename: string = ''); inline; implementation // to get more detailed error messages consider the os uses {$IFDEF Windows} Windows; {$ELSE} {$IFDEF darwin} MacOSAll, {$ENDIF} Unix, BaseUnix; {$ENDIF} {$I lazfileutils.inc} {$IFDEF windows} {$I winlazfileutils.inc} {$ELSE} {$I unixlazfileutils.inc} {$ENDIF} function CompareFilenames(const Filename1, Filename2: string): integer; {$IFDEF darwin} var F1: CFStringRef; F2: CFStringRef; {$ENDIF} begin {$IFDEF darwin} if Filename1=Filename2 then exit(0); if (Filename1='') or (Filename2='') then exit(length(Filename2)-length(Filename1)); F1:=CFStringCreateWithCString(nil,Pointer(Filename1),kCFStringEncodingUTF8); F2:=CFStringCreateWithCString(nil,Pointer(Filename2),kCFStringEncodingUTF8); Result:=CFStringCompare(F1,F2,kCFCompareNonliteral {$IFDEF CaseInsensitiveFilenames}+kCFCompareCaseInsensitive{$ENDIF}); CFRelease(F1); CFRelease(F2); {$ELSE} {$IFDEF CaseInsensitiveFilenames} Result:=UTF8CompareText(Filename1, Filename2); {$ELSE} Result:=CompareStr(Filename1, Filename2); {$ENDIF} {$ENDIF} end; function CompareFilenamesIgnoreCase(const Filename1, Filename2: string ): integer; {$IFDEF darwin} var F1: CFStringRef; F2: CFStringRef; {$ENDIF} begin {$IFDEF darwin} if Filename1=Filename2 then exit(0); F1:=CFStringCreateWithCString(nil,Pointer(Filename1),kCFStringEncodingUTF8); F2:=CFStringCreateWithCString(nil,Pointer(Filename2),kCFStringEncodingUTF8); Result:=CFStringCompare(F1,F2,kCFCompareNonliteral+kCFCompareCaseInsensitive); CFRelease(F1); CFRelease(F2); {$ELSE} Result:=UTF8CompareText(Filename1, Filename2); {$ENDIF} end; function CompareFileExt(const Filename, Ext: string; CaseSensitive: boolean): integer; // Ext can contain a point or not var n, e : AnsiString; FileLen, FilePos, ExtLen, ExtPos: integer; begin FileLen := length(Filename); ExtLen := length(Ext); FilePos := FileLen; while (FilePos>=1) and (Filename[FilePos]<>'.') do dec(FilePos); if FilePos < 1 then begin // no extension in filename Result:=1; exit; end; // skip point inc(FilePos); ExtPos := 1; if (ExtPos <= ExtLen) and (Ext[1] = '.') then inc(ExtPos); // compare extensions n := Copy(Filename, FilePos, length(FileName)); e := Copy(Ext, ExtPos, length(Ext)); if CaseSensitive then Result := CompareStr(n, e) else Result := UTF8CompareText(n, e); if Result < 0 then Result := -1 else if Result > 0 then Result := 1; end; function CompareFileExt(const Filename, Ext: string): integer; begin Result := CompareFileExt(Filename, Ext, False); end; function ExtractFileNameOnly(const AFilename: string): string; var ExtLen: integer; begin // beware: filename.ext1.ext2 Result:=ExtractFilename(AFilename); ExtLen:=length(ExtractFileExt(Result)); Result:=copy(Result,1,length(Result)-ExtLen); end; {$IFDEF darwin} function GetDarwinSystemFilename(Filename: string): string; var s: CFStringRef; l: CFIndex; begin if Filename='' then exit(''); s:=CFStringCreateWithCString(nil,Pointer(Filename),kCFStringEncodingUTF8); l:=CFStringGetMaximumSizeOfFileSystemRepresentation(s); SetLength(Result,l); if Result<>'' then begin CFStringGetFileSystemRepresentation(s,@Result[1],length(Result)); SetLength(Result,StrLen(PChar(Result))); end; CFRelease(s); end; {$ENDIF} function CompareFilenameStarts(const Filename1, Filename2: string): integer; var len1: Integer; len2: Integer; begin len1:=length(Filename1); len2:=length(Filename2); if len1=len2 then begin Result:=CompareFilenames(Filename1,Filename2); exit; end else if len1>len2 then Result:=CompareFilenames(copy(Filename1,1,len2),Filename2) else Result:=CompareFilenames(Filename1,copy(Filename2,1,len1)); if Result<>0 then exit; if len1#0) do begin inc(Filename1); Inc(Filename2); end; Result:=ord(Filename1^)-ord(Filename2^); end; {$ENDIF} end; function DirPathExists(DirectoryName: string): boolean; begin Result:=DirectoryExistsUTF8(ChompPathDelim(DirectoryName)); end; function DirectoryIsWritable(const DirectoryName: string): boolean; var TempFilename: String; fs: TFileStreamUtf8; s: String; begin TempFilename:=SysUtils.GetTempFilename(AppendPathDelim(DirectoryName),'tstperm'); Result:=false; try fs:=TFileStreamUtf8.Create(TempFilename, fmCreate); s:='WriteTest'; fs.Write(s[1],length(s)); fs.Free; if not DeleteFileUTF8(TempFilename) then InvalidateFileStateCache(TempFilename); Result:=true; except end; end; function ForceDirectory(DirectoryName: string): boolean; var i: integer; Dir: string; begin DoDirSeparators(DirectoryName); DirectoryName:=AppendPathDelim(DirectoryName); i:=1; while i<=length(DirectoryName) do begin if DirectoryName[i]=PathDelim then begin Dir:=copy(DirectoryName,1,i-1); if not DirPathExists(Dir) then begin Result:=CreateDirUTF8(Dir); if not Result then exit; end; end; inc(i); end; Result:=true; end; function FileIsText(const AFilename: string): boolean; var FileReadable: Boolean; begin Result:=FileIsText(AFilename,FileReadable); if FileReadable then ; end; function FileIsText(const AFilename: string; out FileReadable: boolean): boolean; var fs: TFileStreamUtf8; Buf: string; Len: integer; NewLine: boolean; p: PChar; ZeroAllowed: Boolean; begin Result:=false; FileReadable:=true; try fs := TFileStreamUtf8.Create(AFilename, fmOpenRead or fmShareDenyNone); try // read the first 1024 bytes Len:=1024; SetLength(Buf,Len+1); Len:=fs.Read(Buf[1],Len); if Len>0 then begin Buf[Len+1]:=#0; p:=PChar(Buf); ZeroAllowed:=false; if (p[0]=#$EF) and (p[1]=#$BB) and (p[2]=#$BF) then begin // UTF-8 BOM (Byte Order Mark) inc(p,3); end else if (p[0]=#$FF) and (p[1]=#$FE) then begin // ucs-2le BOM FF FE inc(p,2); ZeroAllowed:=true; end else if (p[0]=#$FE) and (p[1]=#$FF) then begin // ucs-2be BOM FE FF inc(p,2); ZeroAllowed:=true; end; NewLine:=false; while true do begin case p^ of #0: if p-PChar(Buf)>=Len then break else if not ZeroAllowed then exit; // #10,#13: new line // #12: form feed // #26: end of file #1..#8,#11,#14..#25,#27..#31: exit; #10,#13: NewLine:=true; end; inc(p); end; if NewLine or (Len<1024) then Result:=true; end else Result:=true; finally fs.Free; end; except on E: Exception do begin FileReadable:=false; end; end; end; function FilenameIsTrimmed(const TheFilename: string): boolean; begin Result:=FilenameIsTrimmed(PChar(Pointer(TheFilename)),// pointer type cast avoids #0 check length(TheFilename)); end; function FilenameIsTrimmed(StartPos: PChar; NameLen: integer): boolean; var i: Integer; begin Result:=false; if NameLen<=0 then begin Result:=true; exit; end; // check heading spaces if StartPos[0]=' ' then exit; // check trailing spaces if StartPos[NameLen-1]=' ' then exit; // check ./ at start if (StartPos[0]='.') and (StartPos[1]=PathDelim) then exit; i:=0; while iPathDelim then inc(i) else begin inc(i); if i=NameLen then break; // check for double path delimiter if (StartPos[i]=PathDelim) then exit; if (StartPos[i]='.') and (i>0) then begin inc(i); // check /./ or /. at end if (StartPos[i]=PathDelim) or (i=NameLen) then exit; if StartPos[i]='.' then begin inc(i); // check /../ or /.. at end if (StartPos[i]=PathDelim) or (i=NameLen) then exit; end; end; end; end; Result:=true; end; function TrimFilename(const AFilename: string): string; //Trim leading and trailing spaces //then call ResolveDots to trim double path delims and expand special dirs like .. and . var Len, Start: Integer; begin Result := AFileName; Len := Length(AFileName); if (Len > 0) and not FilenameIsTrimmed(Result) then begin Start := 1; while (Len > 0) and (AFileName[Len] = #32) do Dec(Len); while (Start <= Len) and (AFilename[Start] = #32) do Inc(Start); if Start > 1 then System.Delete(Result,1,Start-1); SetLength(Result, Len - (Start - 1)); Result := ResolveDots(Result); end; end; {------------------------------------------------------------------------------ function CleanAndExpandFilename(const Filename: string): string; ------------------------------------------------------------------------------} function CleanAndExpandFilename(const Filename: string): string; begin Result:=ExpandFileNameUTF8(TrimFileName(SetDirSeparators(Filename))); end; {------------------------------------------------------------------------------ function CleanAndExpandDirectory(const Filename: string): string; ------------------------------------------------------------------------------} function CleanAndExpandDirectory(const Filename: string): string; begin Result:=AppendPathDelim(CleanAndExpandFilename(Filename)); end; function TrimAndExpandFilename(const Filename: string; const BaseDir: string): string; begin Result:=ChompPathDelim(TrimFilename(SetDirSeparators(Filename))); if Result='' then exit; Result:=TrimFilename(ExpandFileNameUTF8(Result,BaseDir)); end; function TrimAndExpandDirectory(const Filename: string; const BaseDir: string): string; begin Result:=TrimFilename(SetDirSeparators(Filename)); if Result='' then exit; Result:=TrimFilename(AppendPathDelim(ExpandFileNameUTF8(Result,BaseDir))); end; {------------------------------------------------------------------------------ function FileIsInPath(const Filename, Path: string): boolean; ------------------------------------------------------------------------------} function FileIsInPath(const Filename, Path: string): boolean; var ExpFile: String; ExpPath: String; l: integer; begin if Path='' then begin Result:=false; exit; end; ExpFile:=TrimFilename(Filename); ExpPath:=AppendPathDelim(TrimFilename(Path)); l:=length(ExpPath); Result:=(l>0) and (length(ExpFile)>l) and (ExpFile[l]=PathDelim) and (CompareFilenames(ExpPath,LeftStr(ExpFile,l))=0); end; function AppendPathDelim(const Path: string): string; begin if (Path<>'') and (Path[length(Path)]<>PathDelim) then Result:=Path+PathDelim else Result:=Path; end; function ChompPathDelim(const Path: string): string; var Len, MinLen: Integer; begin if Path = '' then exit; Result:=Path; Len:=length(Result); if (Result[1]=PathDelim) then begin MinLen := 1; {$IFDEF HasUNCPaths} if (Len >= 2) and (Result[2]=PathDelim) then MinLen := 2; // keep UNC '\\', chomp 'a\' to 'a' {$ENDIF} end else begin MinLen := 0; {$IFdef MSWindows} if (Len >= 3) and (Result[1] in ['a'..'z', 'A'..'Z']) and (Result[2] = ':') and (Result[3]=PathDelim) then MinLen := 3; {$ENDIF} end; while (Len > MinLen) and (Result[Len]=PathDelim) do dec(Len); if LenPathLen then exit; end; EndPos:=StartPos; while (EndPos<=PathLen) and (Result[EndPos]<>';') do inc(EndPos); CurDir:=copy(Result,StartPos,EndPos-StartPos); if not FilenameIsAbsolute(CurDir) then begin NewCurDir:=BaseDir+CurDir; if NewCurDir<>CurDir then begin DiffLen:=length(NewCurDir)-length(CurDir); Result:=copy(Result,1,StartPos-1)+NewCurDir +copy(Result,EndPos,PathLen-EndPos+1); inc(EndPos,DiffLen); inc(PathLen,DiffLen); end; end; StartPos:=EndPos; end; end; function CreateRelativeSearchPath(const SearchPath, BaseDirectory: string ): string; var PathLen: Integer; EndPos: Integer; StartPos: Integer; CurDir: String; NewCurDir: String; DiffLen: Integer; begin Result:=SearchPath; if (SearchPath='') or (BaseDirectory='') then exit; PathLen:=length(Result); EndPos:=1; while EndPos<=PathLen do begin StartPos:=EndPos; while (Result[StartPos]=';') do begin inc(StartPos); if StartPos>PathLen then exit; end; EndPos:=StartPos; while (EndPos<=PathLen) and (Result[EndPos]<>';') do inc(EndPos); CurDir:=copy(Result,StartPos,EndPos-StartPos); if FilenameIsAbsolute(CurDir) then begin NewCurDir:=CreateRelativePath(CurDir,BaseDirectory); if (NewCurDir<>CurDir) and (NewCurDir='') then NewCurDir:='.'; if NewCurDir<>CurDir then begin DiffLen:=length(NewCurDir)-length(CurDir); Result:=copy(Result,1,StartPos-1)+NewCurDir +copy(Result,EndPos,PathLen-EndPos+1); inc(EndPos,DiffLen); inc(PathLen,DiffLen); end; end; StartPos:=EndPos; end; end; function MinimizeSearchPath(const SearchPath: string): string; // trim the paths, remove doubles and empty paths var StartPos: Integer; EndPos: LongInt; NewPath: String; begin Result:=SearchPath; StartPos:=1; while StartPos<=length(Result) do begin EndPos:=StartPos; while (EndPos<=length(Result)) and (Result[EndPos]<>';') do inc(EndPos); if StartPos 0) and (FindPathInSearchPath(@Result[StartPos],EndPos-StartPos, @Result[1],StartPos-1) <> nil) then begin // remove path System.Delete(Result,StartPos,EndPos-StartPos+1); end else begin StartPos:=EndPos+1; end; end else begin // remove empty path System.Delete(Result,StartPos,1); end; end; if (Result<>'') and (Result[length(Result)]=';') then SetLength(Result,length(Result)-1); end; function FindPathInSearchPath(APath: PChar; APathLen: integer; SearchPath: PChar; SearchPathLen: integer): PChar; var StartPos: Integer; EndPos: LongInt; NextStartPos: LongInt; CmpPos: LongInt; UseQuickCompare: Boolean; PathStr: String; CurFilename: String; begin Result:=nil; if SearchPath=nil then exit; if (APath=nil) or (APathLen=0) then exit; // ignore trailing PathDelim at end while (APathLen>1) and (APath[APathLen-1]=PathDelim) do dec(APathLen); {$IFDEF CaseInsensitiveFilenames} UseQuickCompare:=false; {$ELSE} {$IFDEF NotLiteralFilenames} CmpPos:=0; while (CmpPos';') and (NextStartPosStartPos+1) and (SearchPath[EndPos-1]=PathDelim) do dec(EndPos); // compare current path if UseQuickCompare then begin if EndPos-StartPos=APathLen then begin CmpPos:=0; while CmpPosSearchPath[StartPos+CmpPos] then break; inc(CmpPos); end; if CmpPos=APathLen then begin Result:=@SearchPath[StartPos]; exit; end; end; end else if EndPos>StartPos then begin // use CompareFilenames CurFilename:=''; SetLength(CurFilename,EndPos-StartPos); System.Move(SearchPath[StartPos],CurFilename[1],EndPos-StartPos); if CompareFilenames(PathStr,CurFilename)=0 then begin Result:=@SearchPath[StartPos]; exit; end; end; StartPos:=NextStartPos+1; end; end; function FileSearchUTF8(const Name, DirList: String): String; begin Result:=SysToUTF8(SysUtils.FileSearch(UTF8ToSys(Name),UTF8ToSys(DirList))); end; function FileIsReadOnlyUTF8(const FileName: String): Boolean; begin Result:=SysUtils.FileIsReadOnly(UTF8ToSys(Filename)); end; function ForceDirectoriesUTF8(const Dir: string): Boolean; var E: EInOutError; ADrv : String; function DoForceDirectories(Const Dir: string): Boolean; var ADir : String; APath: String; begin Result:=True; ADir:=ExcludeTrailingPathDelimiter(Dir); if (ADir='') then Exit; if Not DirectoryExistsUTF8(ADir) then begin APath := ExtractFilePath(ADir); //this can happen on Windows if user specifies Dir like \user\name/test/ //and would, if not checked for, cause an infinite recusrsion and a stack overflow if (APath = ADir) then Result := False else Result:=DoForceDirectories(APath); if Result then Result := CreateDirUTF8(ADir); end; end; function IsUncDrive(const Drv: String): Boolean; begin Result := (Length(Drv) > 2) and (Drv[1] = PathDelim) and (Drv[2] = PathDelim); end; begin Result := False; ADrv := ExtractFileDrive(Dir); if (ADrv<>'') and (not DirectoryExistsUTF8(ADrv)) {$IFNDEF FORCEDIR_NO_UNC_SUPPORT} and (not IsUncDrive(ADrv)){$ENDIF} then Exit; if Dir='' then begin E:=EInOutError.Create(SCannotCreateEmptyDir); E.ErrorCode:=3; Raise E; end; Result := DoForceDirectories(SetDirSeparators(Dir)); end; procedure InvalidateFileStateCache(const Filename: string); begin if Assigned(OnInvalidateFileStateCache) then OnInvalidateFileStateCache(Filename); end; procedure SplitCmdLineParams(const Params: string; ParamList: TStrings; ReadBackslash: boolean = false); // split spaces, quotes are parsed as single parameter // if ReadBackslash=true then \" is replaced to " and not treated as quote // #0 is always end type TMode = (mNormal,mApostrophe,mQuote); var p: Integer; Mode: TMode; Param: String; begin p:=1; while p<=length(Params) do begin // skip whitespace while (p<=length(Params)) and (Params[p] in [' ',#9,#10,#13]) do inc(p); if (p>length(Params)) or (Params[p]=#0) then break; //writeln('SplitCmdLineParams After Space p=',p,'=[',Params[p],']'); // read param Param:=''; Mode:=mNormal; while p<=length(Params) do begin case Params[p] of #0: break; '\': begin inc(p); if ReadBackslash then begin // treat next character as normal character if (p>length(Params)) or (Params[p]=#0) then break; if ord(Params[p])<128 then begin Param+=Params[p]; inc(p); end else begin // next character is already a normal character end; end else begin // treat backslash as normal character Param+='\'; end; end; '''': begin inc(p); case Mode of mNormal: Mode:=mApostrophe; mApostrophe: Mode:=mNormal; mQuote: Param+=''''; end; end; '"': begin inc(p); case Mode of mNormal: Mode:=mQuote; mApostrophe: Param+='"'; mQuote: Mode:=mNormal; end; end; ' ',#9,#10,#13: begin if Mode=mNormal then break; Param+=Params[p]; inc(p); end; else Param+=Params[p]; inc(p); end; end; //writeln('SplitCmdLineParams Param=#'+Param+'#'); ParamList.Add(Param); end; end; function IsUNCPath(const Path: String): Boolean; begin {$IFDEF Windows} Result := (Length(Path) > 2) and (Path[1] = PathDelim) and (Path[2] = PathDelim); {$ELSE} Result := false; {$ENDIF} end; function ExtractUNCVolume(const Path: String): String; {$IFDEF Windows} var I, Len: Integer; // the next function reuses Len variable function NextPathDelim(const Start: Integer): Integer;// inline; begin Result := Start; while (Result <= Len) and (Path[Result] <> PathDelim) do inc(Result); end; begin if not IsUNCPath(Path) then Exit(''); I := 3; Len := Length(Path); if Path[I] = '?' then begin // Long UNC path form like: // \\?\UNC\ComputerName\SharedFolder\Resource or // \\?\C:\Directory inc(I); if Path[I] <> PathDelim then Exit(''); if UpperCase(Copy(Path, I + 1, 3)) = 'UNC' then begin inc(I, 4); if I < Len then I := NextPathDelim(I + 1); if I < Len then I := NextPathDelim(I + 1); end; end else begin I := NextPathDelim(I); if I < Len then I := NextPathDelim(I + 1); end; Result := Copy(Path, 1, I); end; {$ELSE} begin Result := ''; end; {$ENDIF} { Returns - DriveLetter + : + PathDelim on Windows (if present) or - UNC Share on Windows if present or - PathDelim if FileName starts with PathDelim on Unix or Wince or - Empty string of non eof the above applies } function ExtractFileRoot(FileName: String): String; var Len: Integer; begin Result := ''; Len := Length(FileName); if (Len > 0) then begin if IsUncPath(FileName) then begin Result := ExtractUNCVolume(FileName); // is it like \\?\C:\Directory? then also include the "C:\" part if (Result = '\\?\') and (Length(FileName) > 6) and (UpCase(FileName[5]) in ['A'..'Z']) and (FileName[6] = ':') and (FileName[7] = PathDelim) then Result := Copy(FileName, 1, 7); end else begin {$if defined(unix) or defined(wince)} if (FileName[1] = PathDelim) then Result := PathDelim; {$else} if (Len > 2) and (UpCase(FileName[1]) in ['A'..'Z']) and (FileName[2] = ':') and (FileName[3] = PathDelim) then Result := UpperCase(Copy(FileName,1,3)); {$endif} end; end; end; initialization InitLazFileUtils; end.