prcutils.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. unit PRCUtils;
  2. {$mode ObjFPC}{$H+}
  3. interface
  4. uses
  5. {$IFDEF UNIX}
  6. BaseUnix,
  7. {$ENDIF}
  8. Classes, SysUtils;
  9. function GetCompiledTargetOS: string;
  10. function GetCompiledTargetCPU: string;
  11. function GetExeExt: string;
  12. function GetLibExt(TargetOS: string = ''): string;
  13. function AppendPathDelim(const Path: string): string;
  14. function ChompPathDelim(const Path: string): string;
  15. function FilenameIsAbsolute(const TheFilename: string):boolean;
  16. function FileIsExecutable(const AFilename: string): boolean;
  17. function FileSize(const Filename: string): int64; overload;
  18. function FindDefaultExecutablePath(const Executable: string; const BaseDir: string = ''): string;
  19. // file search
  20. type
  21. TSearchFileInPathFlag = (
  22. sffDontSearchInBasePath, // do not search in BasePath, search only in SearchPath.
  23. sffSearchLoUpCase,
  24. sffFile, // must be file, not directory
  25. sffExecutable, // file must be executable
  26. sffDequoteSearchPath // ansi dequote
  27. );
  28. TSearchFileInPathFlags = set of TSearchFileInPathFlag;
  29. const
  30. sffFindProgramInPath = [
  31. {$IFDEF Unix}sffDontSearchInBasePath,{$ENDIF}
  32. {$IFDEF Windows}sffDequoteSearchPath,{$ENDIF}
  33. sffFile,
  34. sffExecutable
  35. ];
  36. function SearchFileInPath(const Filename, BasePath: string;
  37. SearchPath: string; const Delimiter: string;
  38. Flags: TSearchFileInPathFlags): string; overload;
  39. function ForceDirectory(DirectoryName: string): boolean;
  40. function DeleteDirectory(const DirectoryName: string; OnlyChildren: boolean): boolean;
  41. type
  42. TCopyFileFlag = (
  43. cffOverwriteFile,
  44. cffCreateDestDirectory,
  45. cffPreserveTime,
  46. cffExceptionOnError
  47. );
  48. TCopyFileFlags = set of TCopyFileFlag;
  49. function CopyFile(const SrcFilename, DestFilename: string;
  50. Flags: TCopyFileFlags=[cffOverwriteFile]): boolean;
  51. function CopyDirTree(SrcDir, DestDir: string; Flags: TCopyFileFlags): boolean;
  52. implementation
  53. function GetCompiledTargetOS: string;
  54. begin
  55. Result:=lowerCase({$I %FPCTARGETOS%});
  56. end;
  57. function GetCompiledTargetCPU: string;
  58. begin
  59. Result:=lowerCase({$I %FPCTARGETCPU%});
  60. end;
  61. function GetExeExt: string;
  62. begin
  63. {$IFDEF WINDOWS}
  64. Result:='.exe';
  65. {$ELSE}
  66. Result:='';
  67. {$ENDIF}
  68. end;
  69. function GetLibExt(TargetOS: string): string;
  70. begin
  71. if TargetOS='' then
  72. TargetOS:=GetCompiledTargetOS;
  73. TargetOS:=LowerCase(TargetOS);
  74. if copy(TargetOS,1,3)='win' then
  75. Result:='.dll'
  76. else
  77. case TargetOS of
  78. 'darwin',
  79. 'ios':
  80. Result:='.dylib';
  81. 'linux',
  82. 'android',
  83. 'freebsd',
  84. 'openbsd',
  85. 'netbsd',
  86. 'dragonfly',
  87. 'haiku':
  88. Result:='.so';
  89. 'browser',
  90. 'nodejs',
  91. 'electron',
  92. 'module':
  93. Result:='.js';
  94. else
  95. Result:='';
  96. end;
  97. end;
  98. function AppendPathDelim(const Path: string): string;
  99. begin
  100. if (Path<>'') and not (Path[length(Path)] in AllowDirectorySeparators) then
  101. Result:=Path+PathDelim
  102. else
  103. Result:=Path;
  104. end;
  105. function ChompPathDelim(const Path: string): string;
  106. var
  107. Len, MinLen: Integer;
  108. begin
  109. Result:=Path;
  110. if Path = '' then
  111. exit;
  112. Len:=length(Result);
  113. if (Result[1] in AllowDirectorySeparators) then begin
  114. MinLen := 1;
  115. {$IFDEF HasUNCPaths}
  116. if (Len >= 2) and (Result[2] in AllowDirectorySeparators) then
  117. MinLen := 2; // keep UNC '\\', chomp 'a\' to 'a'
  118. {$ENDIF}
  119. end
  120. else begin
  121. MinLen := 0;
  122. {$IFdef MSWindows}
  123. if (Len >= 3) and (Result[1] in ['a'..'z', 'A'..'Z']) and
  124. (Result[2] = ':') and (Result[3] in AllowDirectorySeparators)
  125. then
  126. MinLen := 3;
  127. {$ENDIF}
  128. end;
  129. while (Len > MinLen) and (Result[Len] in AllowDirectorySeparators) do dec(Len);
  130. if Len<length(Result) then
  131. SetLength(Result,Len);
  132. end;
  133. function FilenameIsAbsolute(const TheFilename: string):boolean;
  134. begin
  135. {$IFDEF Unix}
  136. Result:=(TheFilename<>'') and (TheFilename[1]='/');
  137. {$ELSE}
  138. Result:=((length(TheFilename)>=3) and
  139. (TheFilename[1] in ['A'..'Z','a'..'z']) and (TheFilename[2]=':') and (TheFilename[3]in AllowDirectorySeparators))
  140. or ((length(TheFilename)>=2) and (TheFilename[1] in AllowDirectorySeparators) and (TheFilename[2] in AllowDirectorySeparators))
  141. ;
  142. {$ENDIF}
  143. end;
  144. function FileIsExecutable(const AFilename: string): boolean;
  145. {$IFDEF Unix}
  146. var
  147. Info : Stat;
  148. {$ENDIF}
  149. begin
  150. {$IFDEF Unix}
  151. // first check AFilename is not a directory and then check if executable
  152. Result:= (FpStat(AFilename,info{%H-})<>-1) and FPS_ISREG(info.st_mode) and
  153. (BaseUnix.FpAccess(AFilename,BaseUnix.X_OK)=0);
  154. {$ELSE}
  155. Result:=FileExists(AFilename);
  156. {$ENDIF}
  157. end;
  158. function FileSize(const Filename: string): int64;
  159. {$IFDEF Windows}
  160. var
  161. R: TSearchRec;
  162. begin
  163. if SysUtils.FindFirst(FileName, faAnyFile, R) = 0 then
  164. begin
  165. Result := R.Size;
  166. SysUtils.FindClose(R);
  167. end
  168. else
  169. Result := -1;
  170. end;
  171. {$ELSE}
  172. var
  173. st: baseunix.stat;
  174. begin
  175. if not fpstat(pointer(FileName),st{%H-})>=0 then
  176. exit(-1);
  177. Result := st.st_size;
  178. end;
  179. {$ENDIF}
  180. function FindDefaultExecutablePath(const Executable: string;
  181. const BaseDir: string): string;
  182. var
  183. Env: string;
  184. begin
  185. if FilenameIsAbsolute(Executable) then begin
  186. Result:=Executable;
  187. if FileExists(Result) then exit;
  188. {$IFDEF Windows}
  189. if ExtractFileExt(Result)='' then begin
  190. Result:=Result+'.exe';
  191. if FileExists(Result) then exit;
  192. end;
  193. {$ENDIF}
  194. end else begin
  195. Env:=GetEnvironmentVariable('PATH');
  196. Result:=SearchFileInPath(Executable, BaseDir, Env, PathSeparator, sffFindProgramInPath);
  197. if Result<>'' then exit;
  198. {$IFDEF Windows}
  199. if ExtractFileExt(Executable)='' then begin
  200. Result:=SearchFileInPath(Executable+'.exe', BaseDir, Env, PathSeparator, sffFindProgramInPath);
  201. if Result<>'' then exit;
  202. end;
  203. {$ENDIF}
  204. end;
  205. Result:='';
  206. end;
  207. function SearchFileInPath(const Filename, BasePath: string; SearchPath: string;
  208. const Delimiter: string; Flags: TSearchFileInPathFlags): string;
  209. var
  210. p, StartPos, l, QuoteStart: integer;
  211. CurPath, Base: string;
  212. begin
  213. if (Filename='') then begin
  214. Result:='';
  215. exit;
  216. end;
  217. // check if filename absolute
  218. if FilenameIsAbsolute(Filename) then begin
  219. if FileExists(Filename) then begin
  220. Result:=ExpandFilename(Filename);
  221. exit;
  222. end else begin
  223. Result:='';
  224. exit;
  225. end;
  226. end;
  227. Base:=AppendPathDelim(ExpandFileName(BasePath));
  228. // search in current directory
  229. if (not (sffDontSearchInBasePath in Flags)) then begin
  230. Result:=ExpandFilename(Base+Filename);
  231. if FileExists(Result) then
  232. exit;
  233. end;
  234. // search in search path
  235. StartPos:=1;
  236. l:=length(SearchPath);
  237. while StartPos<=l do begin
  238. p:=StartPos;
  239. while (p<=l) and (pos(SearchPath[p],Delimiter)<1) do
  240. begin
  241. if (SearchPath[p]='"') and (sffDequoteSearchPath in Flags) then
  242. begin
  243. // For example: Windows allows set path=C:\"a;b c"\d;%path%
  244. QuoteStart:=p;
  245. repeat
  246. inc(p);
  247. until (p>l) or (SearchPath[p]='"');
  248. if p<=l then
  249. begin
  250. system.delete(SearchPath,p,1);
  251. system.delete(SearchPath,QuoteStart,1);
  252. dec(l,2);
  253. dec(p,2);
  254. end;
  255. end;
  256. inc(p);
  257. end;
  258. CurPath:=copy(SearchPath,StartPos,p-StartPos);
  259. CurPath:=ExpandFileName(CurPath);
  260. StartPos:=p+1;
  261. if CurPath='' then continue;
  262. if not FilenameIsAbsolute(CurPath) then
  263. CurPath:=Base+CurPath;
  264. Result:=ExpandFilename(AppendPathDelim(CurPath)+Filename);
  265. if not FileExists(Result) then
  266. continue;
  267. if (sffFile in Flags) and DirectoryExists(Result) then
  268. continue;
  269. if (sffExecutable in Flags) and not FileIsExecutable(Result) then
  270. continue;
  271. exit;
  272. end;
  273. Result:='';
  274. end;
  275. function ForceDirectory(DirectoryName: string): boolean;
  276. var
  277. i: integer;
  278. Dir: string;
  279. begin
  280. DirectoryName:=AppendPathDelim(DirectoryName);
  281. i:=1;
  282. while i<=length(DirectoryName) do begin
  283. if DirectoryName[i] in AllowDirectorySeparators then begin
  284. // optimize paths like \foo\\bar\\foobar
  285. while (i<length(DirectoryName)) and (DirectoryName[i+1] in AllowDirectorySeparators) do
  286. Delete(DirectoryName,i+1,1);
  287. Dir:=copy(DirectoryName,1,i-1);
  288. if (Dir<>'') and not DirectoryExists(Dir) then begin
  289. Result:=CreateDir(Dir);
  290. if not Result then exit;
  291. end;
  292. end;
  293. inc(i);
  294. end;
  295. Result:=true;
  296. end;
  297. function DeleteDirectory(const DirectoryName: string; OnlyChildren: boolean): boolean;
  298. const
  299. //Don't follow symlinks on *nix, just delete them
  300. DeleteMask = faAnyFile {$ifdef unix} or faSymLink{%H-} {$endif unix};
  301. var
  302. FileInfo: TSearchRec;
  303. CurSrcDir: String;
  304. CurFilename: String;
  305. begin
  306. Result:=false;
  307. CurSrcDir:=AppendPathDelim(ExpandFileName(DirectoryName));
  308. if FindFirst(CurSrcDir+AllFilesMask,DeleteMask,FileInfo)=0 then begin
  309. try
  310. repeat
  311. // check if special file
  312. if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='') then
  313. continue;
  314. CurFilename:=CurSrcDir+FileInfo.Name;
  315. if ((FileInfo.Attr and faDirectory)>0)
  316. {$ifdef unix} and ((FileInfo.Attr and faSymLink{%H-})=0) {$endif unix} then begin
  317. if not DeleteDirectory(CurFilename,false) then exit;
  318. end else begin
  319. if not DeleteFile(CurFilename) then exit;
  320. end;
  321. until FindNext(FileInfo)<>0;
  322. finally
  323. FindClose(FileInfo);
  324. end;
  325. end;
  326. if (not OnlyChildren) and (not RemoveDir(CurSrcDir)) then exit;
  327. Result:=true;
  328. end;
  329. function CopyFile(const SrcFilename, DestFilename: string; Flags: TCopyFileFlags
  330. ): boolean;
  331. var
  332. SrcHandle: THandle;
  333. DestHandle: THandle;
  334. Buffer: array[1..4096] of byte;
  335. ReadCount, WriteCount, TryCount: LongInt;
  336. begin
  337. Result := False;
  338. // check overwrite
  339. if (not (cffOverwriteFile in Flags)) and FileExists(DestFileName) then begin
  340. if cffExceptionOnError in Flags then
  341. raise EWriteError.Create('Destination file already exists: '+DestFilename);
  342. exit;
  343. end;
  344. // check directory
  345. if (cffCreateDestDirectory in Flags)
  346. and (not DirectoryExists(ExtractFilePath(DestFileName)))
  347. and (not ForceDirectories(ExtractFilePath(DestFileName))) then begin
  348. if cffExceptionOnError in Flags then
  349. raise EWriteError.Create('Unable to create directory: '+ExtractFilePath(DestFileName));
  350. exit;
  351. end;
  352. TryCount := 0;
  353. While TryCount <> 3 Do Begin
  354. SrcHandle := FileOpen(SrcFilename, fmOpenRead or fmShareDenyWrite);
  355. if SrcHandle = feInvalidHandle then Begin
  356. Inc(TryCount);
  357. Sleep(10);
  358. End
  359. Else Begin
  360. TryCount := 0;
  361. Break;
  362. End;
  363. End;
  364. If TryCount > 0 Then
  365. begin
  366. if cffExceptionOnError in Flags then
  367. raise EFOpenError.CreateFmt({SFOpenError}'Unable to open file "%s"', [SrcFilename])
  368. else
  369. exit;
  370. end;
  371. try
  372. DestHandle := FileCreate(DestFileName);
  373. if DestHandle = feInvalidHandle then
  374. begin
  375. if cffExceptionOnError in Flags then
  376. raise EFCreateError.CreateFmt({SFCreateError}'Unable to create file "%s"',[DestFileName])
  377. else
  378. Exit;
  379. end;
  380. try
  381. repeat
  382. ReadCount:=FileRead(SrcHandle,Buffer[1],High(Buffer));
  383. if ReadCount<=0 then break;
  384. WriteCount:=FileWrite(DestHandle,Buffer[1],ReadCount);
  385. if WriteCount<ReadCount then
  386. begin
  387. if cffExceptionOnError in Flags then
  388. raise EWriteError.CreateFmt({SFCreateError}'Unable to write to file "%s"',[DestFileName])
  389. else
  390. Exit;
  391. end;
  392. until false;
  393. finally
  394. FileClose(DestHandle);
  395. end;
  396. if (cffPreserveTime in Flags) then
  397. FileSetDate(DestFilename, FileGetDate(SrcHandle));
  398. Result := True;
  399. finally
  400. FileClose(SrcHandle);
  401. end;
  402. end;
  403. function CopyDirTree(SrcDir, DestDir: string; Flags: TCopyFileFlags): boolean;
  404. var
  405. FileInfo: TRawByteSearchRec;
  406. SrcFilename, DestFilename: String;
  407. begin
  408. Result:=false;
  409. if not DirectoryExists(SrcDir) then begin
  410. if cffExceptionOnError in Flags then
  411. raise EFOpenError.Create('Source directory not found: '+SrcDir);
  412. exit;
  413. end;
  414. if not DirectoryExists(DestDir) then begin
  415. if not (cffCreateDestDirectory in Flags) then begin
  416. if cffExceptionOnError in Flags then
  417. raise EFOpenError.Create('Destination directory not found: '+DestDir);
  418. exit;
  419. end;
  420. if not CreateDir(DestDir) then begin
  421. if cffExceptionOnError in Flags then
  422. raise EFOpenError.Create('Unable to create directory: '+DestDir);
  423. exit;
  424. end;
  425. end;
  426. SrcDir:=AppendPathDelim(SrcDir);
  427. DestDir:=AppendPathDelim(DestDir);
  428. if FindFirst(SrcDir+AllFilesMask,faAnyFile,FileInfo)=0 then begin
  429. try
  430. repeat
  431. // check if special file
  432. if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='') then
  433. continue;
  434. {$ifdef unix}
  435. if FileInfo.Attr and faSymLink{%H-}>0 then continue;
  436. {$endif unix}
  437. SrcFilename:=SrcDir+FileInfo.Name;
  438. DestFilename:=DestDir+FileInfo.Name;
  439. if FileInfo.Attr and faDirectory>0 then begin
  440. CopyDirTree(SrcFilename,DestFilename,Flags+[cffCreateDestDirectory]);
  441. end else begin
  442. if not CopyFile(SrcFilename, DestFilename, Flags) then
  443. exit;
  444. end;
  445. until FindNext(FileInfo)<>0;
  446. finally
  447. FindClose(FileInfo);
  448. end;
  449. end;
  450. Result:=true;
  451. end;
  452. initialization
  453. SetMultiByteConversionCodePage(CP_UTF8);
  454. // SetMultiByteFileSystemCodePage(CP_UTF8); not needed, this is the default under Windows
  455. SetMultiByteRTLFileSystemCodePage(CP_UTF8);
  456. end.