cfileutl.pas 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306
  1. {
  2. Copyright (c) 1998-2002 by Florian Klaempfl and Peter Vreman
  3. This module provides some basic file/dir handling utils and classes
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15. ****************************************************************************
  16. }
  17. unit cfileutl;
  18. {$i fpcdefs.inc}
  19. {$define usedircache}
  20. interface
  21. uses
  22. {$ifdef hasunix}
  23. Baseunix,unix,
  24. {$endif hasunix}
  25. {$ifdef win32}
  26. Windows,
  27. {$endif win32}
  28. {$if defined(go32v2) or defined(watcom)}
  29. Dos,
  30. {$endif}
  31. {$IFNDEF USE_FAKE_SYSUTILS}
  32. SysUtils,
  33. {$ELSE}
  34. fksysutl,
  35. {$ENDIF}
  36. GlobType,
  37. CUtils,CClasses,
  38. Systems;
  39. const
  40. { On case sensitive file systems, you have 9 lookups per used unit, }
  41. { including the system unit, in the current directory }
  42. MinSearchesBeforeCache = 20;
  43. type
  44. TCachedDirectory = class(TFPHashObject)
  45. private
  46. FDirectoryEntries : TFPHashList;
  47. FSearchCount: longint;
  48. procedure FreeDirectoryEntries;
  49. function GetItemAttr(const AName: TCmdStr): byte;
  50. function TryUseCache: boolean;
  51. procedure ForceUseCache;
  52. procedure Reload;
  53. public
  54. constructor Create(AList:TFPHashObjectList;const AName:TCmdStr);
  55. destructor destroy;override;
  56. function FileExists(const AName:TCmdStr):boolean;
  57. function FileExistsCaseAware(const path, fn: TCmdStr; out FoundName: TCmdStr):boolean;
  58. function DirectoryExists(const AName:TCmdStr):boolean;
  59. property DirectoryEntries:TFPHashList read FDirectoryEntries;
  60. end;
  61. TCachedSearchRec = record
  62. Name : TCmdStr;
  63. Attr : byte;
  64. Pattern : TCmdStr;
  65. CachedDir : TCachedDirectory;
  66. EntryIndex : longint;
  67. end;
  68. PCachedDirectoryEntry = ^TCachedDirectoryEntry;
  69. TCachedDirectoryEntry = record
  70. RealName: TCmdStr;
  71. Attr : longint;
  72. end;
  73. TDirectoryCache = class
  74. private
  75. FDirectories : TFPHashObjectList;
  76. function GetDirectory(const ADir:TCmdStr):TCachedDirectory;
  77. public
  78. constructor Create;
  79. destructor destroy;override;
  80. function FileExists(const AName:TCmdStr):boolean;
  81. function FileExistsCaseAware(const path, fn: TCmdStr; out FoundName: TCmdStr):boolean;
  82. function DirectoryExists(const AName:TCmdStr):boolean;
  83. function FindFirst(const APattern:TCmdStr;var Res:TCachedSearchRec):boolean;
  84. function FindNext(var Res:TCachedSearchRec):boolean;
  85. function FindClose(var Res:TCachedSearchRec):boolean;
  86. end;
  87. TSearchPathList = class(TCmdStrList)
  88. procedure AddPath(s:TCmdStr;addfirst:boolean);overload;
  89. procedure AddPath(SrcPath,s:TCmdStr;addfirst:boolean);overload;
  90. procedure AddList(list:TSearchPathList;addfirst:boolean);
  91. function FindFile(const f : TCmdStr;allowcache:boolean;var foundfile:TCmdStr):boolean;
  92. end;
  93. function bstoslash(const s : TCmdStr) : TCmdStr;
  94. {Gives the absolute path to the current directory}
  95. function GetCurrentDir:TCmdStr;
  96. {Gives the relative path to the current directory,
  97. with a trailing dir separator. E. g. on unix ./ }
  98. function CurDirRelPath(systeminfo: tsysteminfo): TCmdStr;
  99. function path_absolute(const s : TCmdStr) : boolean;
  100. Function PathExists (const F : TCmdStr;allowcache:boolean) : Boolean;
  101. Function FileExists (const F : TCmdStr;allowcache:boolean) : Boolean;
  102. function FileExistsNonCase(const path,fn:TCmdStr;allowcache:boolean;var foundfile:TCmdStr):boolean;
  103. Function RemoveDir(d:TCmdStr):boolean;
  104. Function FixPath(const s:TCmdStr;allowdot:boolean):TCmdStr;
  105. function FixFileName(const s:TCmdStr):TCmdStr;
  106. function TargetFixPath(s:TCmdStr;allowdot:boolean):TCmdStr;
  107. function TargetFixFileName(const s:TCmdStr):TCmdStr;
  108. procedure SplitBinCmd(const s:TCmdStr;var bstr: TCmdStr;var cstr:TCmdStr);
  109. function FindFile(const f : TCmdStr; const path : TCmdStr;allowcache:boolean;var foundfile:TCmdStr):boolean;
  110. { function FindFilePchar(const f : TCmdStr;path : pchar;allowcache:boolean;var foundfile:TCmdStr):boolean;}
  111. function FindExe(const bin:TCmdStr;allowcache:boolean;var foundfile:TCmdStr):boolean;
  112. function GetShortName(const n:TCmdStr):TCmdStr;
  113. procedure InitFileUtils;
  114. procedure DoneFileUtils;
  115. { * Since native Amiga commands can't handle Unix-style relative paths used by the compiler,
  116. and some GNU tools, Unix2AmigaPath is needed to handle such situations (KB) * }
  117. {$IF DEFINED(MORPHOS) OR DEFINED(AMIGA)}
  118. { * PATHCONV is implemented in the Amiga/MorphOS system unit * }
  119. {$WARNING TODO Amiga: implement PathConv() in System unit, which works with AnsiString}
  120. function Unix2AmigaPath(path: ShortString): ShortString; external name 'PATHCONV';
  121. {$ELSE}
  122. function Unix2AmigaPath(path: String): String;{$IFDEF USEINLINE}inline;{$ENDIF}
  123. {$ENDIF}
  124. implementation
  125. uses
  126. Comphook,
  127. Globals;
  128. {$undef AllFilesMaskIsInRTL}
  129. {$if (FPC_VERSION > 2)}
  130. {$define AllFilesMaskIsInRTL}
  131. {$endif FPC_VERSION}
  132. {$if (FPC_VERSION = 2) and (FPC_RELEASE > 2)}
  133. {$define AllFilesMaskIsInRTL}
  134. {$endif}
  135. {$if (FPC_VERSION = 2) and (FPC_RELEASE = 2) and (FPC_PATCH > 0)}
  136. {$define AllFilesMaskIsInRTL}
  137. {$endif}
  138. {$ifndef AllFilesMaskIsInRTL}
  139. {$if defined(go32v2) or defined(watcom)}
  140. const
  141. AllFilesMask = '*.*';
  142. {$else}
  143. const
  144. AllFilesMask = '*';
  145. {$endif not (go32v2 or watcom)}
  146. {$endif not AllFilesMaskIsInRTL}
  147. var
  148. DirCache : TDirectoryCache;
  149. {$IF NOT (DEFINED(MORPHOS) OR DEFINED(AMIGA))}
  150. { Stub function for Unix2Amiga Path conversion functionality, only available in
  151. Amiga/MorphOS RTL. I'm open for better solutions. (KB) }
  152. function Unix2AmigaPath(path: String): String;{$IFDEF USEINLINE}inline;{$ENDIF}
  153. begin
  154. Unix2AmigaPath:=path;
  155. end;
  156. {$ENDIF}
  157. {****************************************************************************
  158. TCachedDirectory
  159. ****************************************************************************}
  160. constructor TCachedDirectory.create(AList:TFPHashObjectList;const AName:TCmdStr);
  161. begin
  162. inherited create(AList,AName);
  163. FDirectoryEntries:=TFPHashList.Create;
  164. end;
  165. destructor TCachedDirectory.destroy;
  166. begin
  167. FreeDirectoryEntries;
  168. FDirectoryEntries.Free;
  169. inherited destroy;
  170. end;
  171. function TCachedDirectory.TryUseCache:boolean;
  172. begin
  173. Result:=true;
  174. if (FSearchCount > MinSearchesBeforeCache) then
  175. exit;
  176. if (FSearchCount = MinSearchesBeforeCache) then
  177. begin
  178. inc(FSearchCount);
  179. Reload;
  180. exit;
  181. end;
  182. inc(FSearchCount);
  183. Result:=false;
  184. end;
  185. procedure TCachedDirectory.ForceUseCache;
  186. begin
  187. if (FSearchCount<=MinSearchesBeforeCache) then
  188. begin
  189. FSearchCount:=MinSearchesBeforeCache+1;
  190. Reload;
  191. end;
  192. end;
  193. procedure TCachedDirectory.FreeDirectoryEntries;
  194. var
  195. i: Integer;
  196. begin
  197. if not(tf_files_case_aware in source_info.flags) then
  198. exit;
  199. for i := 0 to DirectoryEntries.Count-1 do
  200. dispose(PCachedDirectoryEntry(DirectoryEntries[i]));
  201. end;
  202. function TCachedDirectory.GetItemAttr(const AName: TCmdStr): byte;
  203. var
  204. entry: PCachedDirectoryEntry;
  205. begin
  206. if not(tf_files_case_sensitive in source_info.flags) then
  207. if (tf_files_case_aware in source_info.flags) then
  208. begin
  209. entry:=PCachedDirectoryEntry(DirectoryEntries.Find(Lower(AName)));
  210. if assigned(entry) then
  211. Result:=entry^.Attr
  212. else
  213. Result:=0;
  214. end
  215. else
  216. Result:=PtrUInt(DirectoryEntries.Find(Lower(AName)))
  217. else
  218. Result:=PtrUInt(DirectoryEntries.Find(AName));
  219. end;
  220. procedure TCachedDirectory.Reload;
  221. var
  222. dir : TSearchRec;
  223. entry : PCachedDirectoryEntry;
  224. begin
  225. FreeDirectoryEntries;
  226. DirectoryEntries.Clear;
  227. if findfirst(IncludeTrailingPathDelimiter(Name)+AllFilesMask,faAnyFile or faDirectory,dir) = 0 then
  228. begin
  229. repeat
  230. if ((dir.attr and faDirectory)<>faDirectory) or
  231. ((dir.Name<>'.') and
  232. (dir.Name<>'..')) then
  233. begin
  234. { Force Archive bit so the attribute always has a value. This is needed
  235. to be able to see the difference in the directoryentries lookup if a file
  236. exists or not }
  237. Dir.Attr:=Dir.Attr or faArchive;
  238. if not(tf_files_case_sensitive in source_info.flags) then
  239. if (tf_files_case_aware in source_info.flags) then
  240. begin
  241. new(entry);
  242. entry^.RealName:=Dir.Name;
  243. entry^.Attr:=Dir.Attr;
  244. DirectoryEntries.Add(Lower(Dir.Name),entry)
  245. end
  246. else
  247. DirectoryEntries.Add(Lower(Dir.Name),Pointer(Ptrint(Dir.Attr)))
  248. else
  249. DirectoryEntries.Add(Dir.Name,Pointer(Ptrint(Dir.Attr)));
  250. end;
  251. until findnext(dir) <> 0;
  252. end;
  253. findclose(dir);
  254. end;
  255. function TCachedDirectory.FileExists(const AName:TCmdStr):boolean;
  256. var
  257. Attr : Longint;
  258. begin
  259. if not TryUseCache then
  260. begin
  261. { prepend directory name again }
  262. result:=cfileutl.FileExists(Name+AName,false);
  263. exit;
  264. end;
  265. Attr:=GetItemAttr(AName);
  266. if Attr<>0 then
  267. Result:=((Attr and faDirectory)=0)
  268. else
  269. Result:=false;
  270. end;
  271. function TCachedDirectory.FileExistsCaseAware(const path, fn: TCmdStr; out FoundName: TCmdStr):boolean;
  272. var
  273. entry : PCachedDirectoryEntry;
  274. begin
  275. if (tf_files_case_aware in source_info.flags) then
  276. begin
  277. if not TryUseCache then
  278. begin
  279. Result:=FileExistsNonCase(path,fn,false,FoundName);
  280. exit;
  281. end;
  282. entry:=PCachedDirectoryEntry(DirectoryEntries.Find(Lower(ExtractFileName(fn))));
  283. if assigned(entry) and
  284. (entry^.Attr<>0) and
  285. ((entry^.Attr and faDirectory) = 0) then
  286. begin
  287. FoundName:=ExtractFilePath(path+fn)+entry^.RealName;
  288. Result:=true
  289. end
  290. else
  291. Result:=false;
  292. end
  293. else
  294. { should not be called in this case, use plain FileExists }
  295. Result:=False;
  296. end;
  297. function TCachedDirectory.DirectoryExists(const AName:TCmdStr):boolean;
  298. var
  299. Attr : Longint;
  300. begin
  301. if not TryUseCache then
  302. begin
  303. Result:=PathExists(Name+AName,false);
  304. exit;
  305. end;
  306. Attr:=GetItemAttr(AName);
  307. if Attr<>0 then
  308. Result:=((Attr and faDirectory)=faDirectory)
  309. else
  310. Result:=false;
  311. end;
  312. {****************************************************************************
  313. TDirectoryCache
  314. ****************************************************************************}
  315. constructor TDirectoryCache.create;
  316. begin
  317. inherited create;
  318. FDirectories:=TFPHashObjectList.Create(true);
  319. end;
  320. destructor TDirectoryCache.destroy;
  321. begin
  322. FDirectories.Free;
  323. inherited destroy;
  324. end;
  325. function TDirectoryCache.GetDirectory(const ADir:TCmdStr):TCachedDirectory;
  326. var
  327. CachedDir : TCachedDirectory;
  328. DirName : TCmdStr;
  329. begin
  330. if ADir='' then
  331. DirName:='.'+source_info.DirSep
  332. else
  333. DirName:=ADir;
  334. CachedDir:=TCachedDirectory(FDirectories.Find(DirName));
  335. if not assigned(CachedDir) then
  336. CachedDir:=TCachedDirectory.Create(FDirectories,DirName);
  337. Result:=CachedDir;
  338. end;
  339. function TDirectoryCache.FileExists(const AName:TCmdStr):boolean;
  340. var
  341. CachedDir : TCachedDirectory;
  342. begin
  343. Result:=false;
  344. CachedDir:=GetDirectory(ExtractFilePath(AName));
  345. if assigned(CachedDir) then
  346. Result:=CachedDir.FileExists(ExtractFileName(AName));
  347. end;
  348. function TDirectoryCache.FileExistsCaseAware(const path, fn: TCmdStr; out FoundName: TCmdStr):boolean;
  349. var
  350. CachedDir : TCachedDirectory;
  351. begin
  352. Result:=false;
  353. CachedDir:=GetDirectory(ExtractFilePath(path+fn));
  354. if assigned(CachedDir) then
  355. Result:=CachedDir.FileExistsCaseAware(path,fn,FoundName);
  356. end;
  357. function TDirectoryCache.DirectoryExists(const AName:TCmdStr):boolean;
  358. var
  359. CachedDir : TCachedDirectory;
  360. begin
  361. Result:=false;
  362. CachedDir:=GetDirectory(ExtractFilePath(AName));
  363. if assigned(CachedDir) then
  364. Result:=CachedDir.DirectoryExists(ExtractFileName(AName));
  365. end;
  366. function TDirectoryCache.FindFirst(const APattern:TCmdStr;var Res:TCachedSearchRec):boolean;
  367. begin
  368. Res.Pattern:=ExtractFileName(APattern);
  369. Res.CachedDir:=GetDirectory(ExtractFilePath(APattern));
  370. Res.CachedDir.ForceUseCache;
  371. Res.EntryIndex:=0;
  372. if assigned(Res.CachedDir) then
  373. Result:=FindNext(Res)
  374. else
  375. Result:=false;
  376. end;
  377. function TDirectoryCache.FindNext(var Res:TCachedSearchRec):boolean;
  378. var
  379. entry: PCachedDirectoryEntry;
  380. begin
  381. if Res.EntryIndex<Res.CachedDir.DirectoryEntries.Count then
  382. begin
  383. if (tf_files_case_aware in source_info.flags) then
  384. begin
  385. entry:=Res.CachedDir.DirectoryEntries[Res.EntryIndex];
  386. Res.Name:=entry^.RealName;
  387. Res.Attr:=entry^.Attr;
  388. end
  389. else
  390. begin
  391. Res.Name:=Res.CachedDir.DirectoryEntries.NameOfIndex(Res.EntryIndex);
  392. Res.Attr:=PtrUInt(Res.CachedDir.DirectoryEntries[Res.EntryIndex]);
  393. end;
  394. inc(Res.EntryIndex);
  395. Result:=true;
  396. end
  397. else
  398. Result:=false;
  399. end;
  400. function TDirectoryCache.FindClose(var Res:TCachedSearchRec):boolean;
  401. begin
  402. { nothing todo }
  403. result:=true;
  404. end;
  405. {****************************************************************************
  406. Utils
  407. ****************************************************************************}
  408. function bstoslash(const s : TCmdStr) : TCmdStr;
  409. {
  410. return TCmdStr s with all \ changed into /
  411. }
  412. var
  413. i : longint;
  414. begin
  415. setlength(bstoslash,length(s));
  416. for i:=1to length(s) do
  417. if s[i]='\' then
  418. bstoslash[i]:='/'
  419. else
  420. bstoslash[i]:=s[i];
  421. end;
  422. {Gives the absolute path to the current directory}
  423. var
  424. CachedCurrentDir : TCmdStr;
  425. function GetCurrentDir:TCmdStr;
  426. begin
  427. if CachedCurrentDir='' then
  428. begin
  429. GetDir(0,CachedCurrentDir);
  430. CachedCurrentDir:=FixPath(CachedCurrentDir,false);
  431. end;
  432. result:=CachedCurrentDir;
  433. end;
  434. {Gives the relative path to the current directory,
  435. with a trailing dir separator. E. g. on unix ./ }
  436. function CurDirRelPath(systeminfo: tsysteminfo): TCmdStr;
  437. begin
  438. if systeminfo.system <> system_powerpc_macos then
  439. CurDirRelPath:= '.'+systeminfo.DirSep
  440. else
  441. CurDirRelPath:= ':'
  442. end;
  443. function path_absolute(const s : TCmdStr) : boolean;
  444. {
  445. is path s an absolute path?
  446. }
  447. begin
  448. result:=false;
  449. {$if defined(unix)}
  450. if (length(s)>0) and (s[1] in AllowDirectorySeparators) then
  451. result:=true;
  452. {$elseif defined(amiga) or defined(morphos)}
  453. if ((length(s)>0) and (s[1] in AllowDirectorySeparators)) or (Pos(':',s) = length(s)) then
  454. result:=true;
  455. {$elseif defined(macos)}
  456. if IsMacFullPath(s) then
  457. result:=true;
  458. {$elseif defined(netware)}
  459. if (Pos (DriveSeparator, S) <> 0) or
  460. ((Length (S) > 0) and (S [1] in AllowDirectorySeparators)) then
  461. result:=true;
  462. {$elseif defined(win32) or defined(win64) or defined(go32v2) or defined(os2) or defined(watcom)}
  463. if ((length(s)>0) and (s[1] in AllowDirectorySeparators)) or
  464. (* The following check for non-empty AllowDriveSeparators assumes that all
  465. other platforms supporting drives and not handled as exceptions above
  466. should work with DOS-like paths, i.e. use absolute paths with one letter
  467. for drive followed by path separator *)
  468. ((length(s)>2) and (s[2] in AllowDriveSeparators) and (s[3] in AllowDirectorySeparators)) then
  469. result:=true;
  470. {$else}
  471. if ((length(s)>0) and (s[1] in AllowDirectorySeparators)) or
  472. (* The following check for non-empty AllowDriveSeparators assumes that all
  473. other platforms supporting drives and not handled as exceptions above
  474. should work with DOS-like paths, i.e. use absolute paths with one letter
  475. for drive followed by path separator *)
  476. ((AllowDriveSeparators <> []) and (length(s)>2) and (s[2] in AllowDriveSeparators) and (s[3] in AllowDirectorySeparators)) then
  477. result:=true;
  478. {$endif unix}
  479. end;
  480. Function FileExists ( Const F : TCmdStr;allowcache:boolean) : Boolean;
  481. begin
  482. {$ifdef usedircache}
  483. if allowcache then
  484. Result:=DirCache.FileExists(F)
  485. else
  486. {$endif usedircache}
  487. Result:=SysUtils.FileExists(F);
  488. if do_checkverbosity(V_Tried) then
  489. begin
  490. if Result then
  491. do_comment(V_Tried,'Searching file '+F+'... found')
  492. else
  493. do_comment(V_Tried,'Searching file '+F+'... not found');
  494. end;
  495. end;
  496. function FileExistsNonCase(const path,fn:TCmdStr;allowcache:boolean;var foundfile:TCmdStr):boolean;
  497. var
  498. fn2 : TCmdStr;
  499. begin
  500. result:=false;
  501. if tf_files_case_sensitive in source_info.flags then
  502. begin
  503. {
  504. Search order for case sensitive systems:
  505. 1. NormalCase
  506. 2. lowercase
  507. 3. UPPERCASE
  508. }
  509. FoundFile:=path+fn;
  510. If FileExists(FoundFile,allowcache) then
  511. begin
  512. result:=true;
  513. exit;
  514. end;
  515. fn2:=Lower(fn);
  516. if fn2<>fn then
  517. begin
  518. FoundFile:=path+fn2;
  519. If FileExists(FoundFile,allowcache) then
  520. begin
  521. result:=true;
  522. exit;
  523. end;
  524. end;
  525. fn2:=Upper(fn);
  526. if fn2<>fn then
  527. begin
  528. FoundFile:=path+fn2;
  529. If FileExists(FoundFile,allowcache) then
  530. begin
  531. result:=true;
  532. exit;
  533. end;
  534. end;
  535. end
  536. else
  537. if tf_files_case_aware in source_info.flags then
  538. begin
  539. {
  540. Search order for case aware systems:
  541. 1. NormalCase
  542. }
  543. {$ifdef usedircache}
  544. if allowcache then
  545. begin
  546. result:=DirCache.FileExistsCaseAware(path,fn,fn2);
  547. if result then
  548. begin
  549. FoundFile:=fn2;
  550. exit;
  551. end;
  552. end
  553. else
  554. {$endif usedircache}
  555. begin
  556. FoundFile:=path+fn;
  557. If FileExists(FoundFile,allowcache) then
  558. begin
  559. { don't know the real name in this case }
  560. result:=true;
  561. exit;
  562. end;
  563. end;
  564. end
  565. else
  566. begin
  567. { None case sensitive only lowercase }
  568. FoundFile:=path+Lower(fn);
  569. If FileExists(FoundFile,allowcache) then
  570. begin
  571. result:=true;
  572. exit;
  573. end;
  574. end;
  575. { Set foundfile to something usefull }
  576. FoundFile:=fn;
  577. end;
  578. Function PathExists (const F : TCmdStr;allowcache:boolean) : Boolean;
  579. Var
  580. i: longint;
  581. hs : TCmdStr;
  582. begin
  583. if F = '' then
  584. begin
  585. result := true;
  586. exit;
  587. end;
  588. hs := ExpandFileName(F);
  589. I := Pos (DriveSeparator, hs);
  590. if (hs [Length (hs)] = DirectorySeparator) and
  591. (((I = 0) and (Length (hs) > 1)) or (I <> Length (hs) - 1)) then
  592. Delete (hs, Length (hs), 1);
  593. {$ifdef usedircache}
  594. if allowcache then
  595. Result:=DirCache.DirectoryExists(hs)
  596. else
  597. {$endif usedircache}
  598. Result:=SysUtils.DirectoryExists(hs);
  599. end;
  600. Function RemoveDir(d:TCmdStr):boolean;
  601. begin
  602. if d[length(d)]=source_info.DirSep then
  603. Delete(d,length(d),1);
  604. {$I-}
  605. rmdir(d);
  606. {$I+}
  607. RemoveDir:=(ioresult=0);
  608. end;
  609. Function FixPath(const s:TCmdStr;allowdot:boolean):TCmdStr;
  610. var
  611. i, L : longint;
  612. P: PChar;
  613. begin
  614. Result := s;
  615. L := Length(Result);
  616. if L=0 then
  617. exit;
  618. { Fix separator }
  619. P := @Result[1];
  620. for i:=0 to L-1 do
  621. begin
  622. if p^ in ['/','\'] then
  623. p^:=source_info.DirSep;
  624. inc(p);
  625. end;
  626. { Fix ending / }
  627. if (L>0) and (Result[L]<>source_info.DirSep) and
  628. (Result[L]<>DriveSeparator) then
  629. Result:=Result+source_info.DirSep; { !still results in temp AnsiString }
  630. { Remove ./ }
  631. if (not allowdot) and ((Length(Result)=2) and (Result[1]='.') and (Result[2] = source_info.DirSep)) then
  632. begin
  633. Result:='';
  634. Exit;
  635. end;
  636. { return }
  637. if not ((tf_files_case_aware in source_info.flags) or
  638. (tf_files_case_sensitive in source_info.flags)) then
  639. Result := lower(Result);
  640. end;
  641. {Actually the version in macutils.pp could be used,
  642. but that would not work for crosscompiling, so this is a slightly modified
  643. version of it.}
  644. function TranslatePathToMac (const path: TCmdStr; mpw: Boolean): TCmdStr;
  645. function GetVolumeIdentifier: TCmdStr;
  646. begin
  647. GetVolumeIdentifier := '{Boot}'
  648. (*
  649. if mpw then
  650. GetVolumeIdentifier := '{Boot}'
  651. else
  652. GetVolumeIdentifier := macosBootVolumeName;
  653. *)
  654. end;
  655. var
  656. slashPos, oldpos, newpos, oldlen, maxpos: Longint;
  657. begin
  658. oldpos := 1;
  659. slashPos := Pos('/', path);
  660. if (slashPos <> 0) then {its a unix path}
  661. begin
  662. if slashPos = 1 then
  663. begin {its a full path}
  664. oldpos := 2;
  665. TranslatePathToMac := GetVolumeIdentifier;
  666. end
  667. else {its a partial path}
  668. TranslatePathToMac := ':';
  669. end
  670. else
  671. begin
  672. slashPos := Pos('\', path);
  673. if (slashPos <> 0) then {its a dos path}
  674. begin
  675. if slashPos = 1 then
  676. begin {its a full path, without drive letter}
  677. oldpos := 2;
  678. TranslatePathToMac := GetVolumeIdentifier;
  679. end
  680. else if (Length(path) >= 2) and (path[2] = ':') then {its a full path, with drive letter}
  681. begin
  682. oldpos := 4;
  683. TranslatePathToMac := GetVolumeIdentifier;
  684. end
  685. else {its a partial path}
  686. TranslatePathToMac := ':';
  687. end;
  688. end;
  689. if (slashPos <> 0) then {its a unix or dos path}
  690. begin
  691. {Translate "/../" to "::" , "/./" to ":" and "/" to ":" }
  692. newpos := Length(TranslatePathToMac);
  693. oldlen := Length(path);
  694. SetLength(TranslatePathToMac, newpos + oldlen); {It will be no longer than what is already}
  695. {prepended plus length of path.}
  696. maxpos := Length(TranslatePathToMac); {Get real maxpos, can be short if String is ShortString}
  697. {There is never a slash in the beginning, because either it was an absolute path, and then the}
  698. {drive and slash was removed, or it was a relative path without a preceding slash.}
  699. while oldpos <= oldlen do
  700. begin
  701. {Check if special dirs, ./ or ../ }
  702. if path[oldPos] = '.' then
  703. if (oldpos + 1 <= oldlen) and (path[oldPos + 1] = '.') then
  704. begin
  705. if (oldpos + 2 > oldlen) or (path[oldPos + 2] in ['/', '\']) then
  706. begin
  707. {It is "../" or ".." translates to ":" }
  708. if newPos = maxPos then
  709. begin {Shouldn't actually happen, but..}
  710. Exit('');
  711. end;
  712. newPos := newPos + 1;
  713. TranslatePathToMac[newPos] := ':';
  714. oldPos := oldPos + 3;
  715. continue; {Start over again}
  716. end;
  717. end
  718. else if (oldpos + 1 > oldlen) or (path[oldPos + 1] in ['/', '\']) then
  719. begin
  720. {It is "./" or "." ignor it }
  721. oldPos := oldPos + 2;
  722. continue; {Start over again}
  723. end;
  724. {Collect file or dir name}
  725. while (oldpos <= oldlen) and not (path[oldPos] in ['/', '\']) do
  726. begin
  727. if newPos = maxPos then
  728. begin {Shouldn't actually happen, but..}
  729. Exit('');
  730. end;
  731. newPos := newPos + 1;
  732. TranslatePathToMac[newPos] := path[oldPos];
  733. oldPos := oldPos + 1;
  734. end;
  735. {When we come here there is either a slash or we are at the end.}
  736. if (oldpos <= oldlen) then
  737. begin
  738. if newPos = maxPos then
  739. begin {Shouldn't actually happen, but..}
  740. Exit('');
  741. end;
  742. newPos := newPos + 1;
  743. TranslatePathToMac[newPos] := ':';
  744. oldPos := oldPos + 1;
  745. end;
  746. end;
  747. SetLength(TranslatePathToMac, newpos);
  748. end
  749. else if (path = '.') then
  750. TranslatePathToMac := ':'
  751. else if (path = '..') then
  752. TranslatePathToMac := '::'
  753. else
  754. TranslatePathToMac := path; {its a mac path}
  755. end;
  756. function FixFileName(const s:TCmdStr):TCmdStr;
  757. var
  758. i : longint;
  759. begin
  760. if source_info.system = system_powerpc_MACOS then
  761. FixFileName:= TranslatePathToMac(s, true)
  762. else
  763. if (tf_files_case_aware in source_info.flags) or
  764. (tf_files_case_sensitive in source_info.flags) then
  765. begin
  766. setlength(FixFileName,length(s));
  767. for i:=1 to length(s) do
  768. begin
  769. case s[i] of
  770. '/','\' :
  771. FixFileName[i]:=source_info.dirsep;
  772. else
  773. FixFileName[i]:=s[i];
  774. end;
  775. end;
  776. end
  777. else
  778. begin
  779. setlength(FixFileName,length(s));
  780. for i:=1 to length(s) do
  781. begin
  782. case s[i] of
  783. '/','\' :
  784. FixFileName[i]:=source_info.dirsep;
  785. 'A'..'Z' :
  786. FixFileName[i]:=char(byte(s[i])+32);
  787. else
  788. FixFileName[i]:=s[i];
  789. end;
  790. end;
  791. end;
  792. end;
  793. Function TargetFixPath(s:TCmdStr;allowdot:boolean):TCmdStr;
  794. var
  795. i : longint;
  796. begin
  797. { Fix separator }
  798. for i:=1 to length(s) do
  799. if s[i] in ['/','\'] then
  800. s[i]:=target_info.DirSep;
  801. { Fix ending / }
  802. if (length(s)>0) and (s[length(s)]<>target_info.DirSep) and
  803. (s[length(s)]<>':') then
  804. s:=s+target_info.DirSep;
  805. { Remove ./ }
  806. if (not allowdot) and (s='.'+target_info.DirSep) then
  807. s:='';
  808. { return }
  809. if (tf_files_case_aware in target_info.flags) or
  810. (tf_files_case_sensitive in target_info.flags) then
  811. TargetFixPath:=s
  812. else
  813. TargetFixPath:=Lower(s);
  814. end;
  815. function TargetFixFileName(const s:TCmdStr):TCmdStr;
  816. var
  817. i : longint;
  818. begin
  819. if target_info.system = system_powerpc_MACOS then
  820. TargetFixFileName:= TranslatePathToMac(s, true)
  821. else
  822. if (tf_files_case_aware in target_info.flags) or
  823. (tf_files_case_sensitive in target_info.flags) then
  824. begin
  825. setlength(TargetFixFileName,length(s));
  826. for i:=1 to length(s) do
  827. begin
  828. case s[i] of
  829. '/','\' :
  830. TargetFixFileName[i]:=target_info.dirsep;
  831. else
  832. TargetFixFileName[i]:=s[i];
  833. end;
  834. end;
  835. end
  836. else
  837. begin
  838. setlength(TargetFixFileName,length(s));
  839. for i:=1 to length(s) do
  840. begin
  841. case s[i] of
  842. '/','\' :
  843. TargetFixFileName[i]:=target_info.dirsep;
  844. 'A'..'Z' :
  845. TargetFixFileName[i]:=char(byte(s[i])+32);
  846. else
  847. TargetFixFileName[i]:=s[i];
  848. end;
  849. end;
  850. end;
  851. end;
  852. procedure SplitBinCmd(const s:TCmdStr;var bstr:TCmdStr;var cstr:TCmdStr);
  853. var
  854. i : longint;
  855. begin
  856. i:=pos(' ',s);
  857. if i>0 then
  858. begin
  859. bstr:=Copy(s,1,i-1);
  860. cstr:=Copy(s,i+1,length(s)-i);
  861. end
  862. else
  863. begin
  864. bstr:=s;
  865. cstr:='';
  866. end;
  867. end;
  868. procedure TSearchPathList.AddPath(s:TCmdStr;addfirst:boolean);
  869. begin
  870. AddPath('',s,AddFirst);
  871. end;
  872. procedure TSearchPathList.AddPath(SrcPath,s:TCmdStr;addfirst:boolean);
  873. var
  874. staridx,
  875. i,j : longint;
  876. prefix,
  877. suffix,
  878. CurrentDir,
  879. currPath : TCmdStr;
  880. subdirfound : boolean;
  881. {$ifdef usedircache}
  882. dir : TCachedSearchRec;
  883. {$else usedircache}
  884. dir : TSearchRec;
  885. {$endif usedircache}
  886. hp : TCmdStrListItem;
  887. procedure WarnNonExistingPath(const path : TCmdStr);
  888. begin
  889. if do_checkverbosity(V_Tried) then
  890. do_comment(V_Tried,'Path "'+path+'" not found');
  891. end;
  892. procedure AddCurrPath;
  893. begin
  894. if addfirst then
  895. begin
  896. Remove(currPath);
  897. Insert(currPath);
  898. end
  899. else
  900. begin
  901. { Check if already in path, then we don't add it }
  902. hp:=Find(currPath);
  903. if not assigned(hp) then
  904. Concat(currPath);
  905. end;
  906. end;
  907. begin
  908. if s='' then
  909. exit;
  910. { Support default macro's }
  911. DefaultReplacements(s);
  912. {$warnings off}
  913. if PathSeparator <> ';' then
  914. for i:=1 to length(s) do
  915. if s[i]=PathSeparator then
  916. s[i]:=';';
  917. {$warnings on}
  918. { get current dir }
  919. CurrentDir:=GetCurrentDir;
  920. repeat
  921. { get currpath }
  922. if addfirst then
  923. begin
  924. j:=length(s);
  925. while (j>0) and (s[j]<>';') do
  926. dec(j);
  927. currPath:= TrimSpace(Copy(s,j+1,length(s)-j));
  928. if j=0 then
  929. s:=''
  930. else
  931. System.Delete(s,j,length(s)-j+1);
  932. end
  933. else
  934. begin
  935. j:=Pos(';',s);
  936. if j=0 then
  937. j:=255;
  938. currPath:= TrimSpace(Copy(s,1,j-1));
  939. System.Delete(s,1,j);
  940. end;
  941. { fix pathname }
  942. DePascalQuote(currPath);
  943. currPath:=SrcPath+FixPath(currPath,false);
  944. if currPath='' then
  945. currPath:= CurDirRelPath(source_info)
  946. else
  947. begin
  948. currPath:=FixPath(ExpandFileName(currpath),false);
  949. if (CurrentDir<>'') and (Copy(currPath,1,length(CurrentDir))=CurrentDir) then
  950. begin
  951. {$if defined(amiga) and defined(morphos)}
  952. currPath:= CurrentDir+Copy(currPath,length(CurrentDir)+1,255);
  953. {$else}
  954. currPath:= CurDirRelPath(source_info)+Copy(currPath,length(CurrentDir)+1,255);
  955. {$endif}
  956. end;
  957. end;
  958. { wildcard adding ? }
  959. staridx:=pos('*',currpath);
  960. if staridx>0 then
  961. begin
  962. prefix:=ExtractFilePath(Copy(currpath,1,staridx));
  963. suffix:=Copy(currpath,staridx+1,length(currpath));
  964. subdirfound:=false;
  965. {$ifdef usedircache}
  966. if DirCache.FindFirst(Prefix+AllFilesMask,dir) then
  967. begin
  968. repeat
  969. if (dir.attr and faDirectory)<>0 then
  970. begin
  971. subdirfound:=true;
  972. currpath:=prefix+dir.name+suffix;
  973. if (suffix='') or PathExists(currpath,true) then
  974. begin
  975. hp:=Find(currPath);
  976. if not assigned(hp) then
  977. AddCurrPath;
  978. end;
  979. end;
  980. until not DirCache.FindNext(dir);
  981. end;
  982. DirCache.FindClose(dir);
  983. {$else usedircache}
  984. if findfirst(prefix+AllFilesMask,faDirectory,dir) = 0 then
  985. begin
  986. repeat
  987. if (dir.name<>'.') and
  988. (dir.name<>'..') and
  989. ((dir.attr and faDirectory)<>0) then
  990. begin
  991. subdirfound:=true;
  992. currpath:=prefix+dir.name+suffix;
  993. if (suffix='') or PathExists(currpath,false) then
  994. begin
  995. hp:=Find(currPath);
  996. if not assigned(hp) then
  997. AddCurrPath;
  998. end;
  999. end;
  1000. until findnext(dir) <> 0;
  1001. end;
  1002. FindClose(dir);
  1003. {$endif usedircache}
  1004. if not subdirfound then
  1005. WarnNonExistingPath(currpath);
  1006. end
  1007. else
  1008. begin
  1009. if PathExists(currpath,true) then
  1010. AddCurrPath
  1011. else
  1012. WarnNonExistingPath(currpath);
  1013. end;
  1014. until (s='');
  1015. end;
  1016. procedure TSearchPathList.AddList(list:TSearchPathList;addfirst:boolean);
  1017. var
  1018. s : TCmdStr;
  1019. hl : TSearchPathList;
  1020. hp,hp2 : TCmdStrListItem;
  1021. begin
  1022. if list.empty then
  1023. exit;
  1024. { create temp and reverse the list }
  1025. if addfirst then
  1026. begin
  1027. hl:=TSearchPathList.Create;
  1028. hp:=TCmdStrListItem(list.first);
  1029. while assigned(hp) do
  1030. begin
  1031. hl.insert(hp.Str);
  1032. hp:=TCmdStrListItem(hp.next);
  1033. end;
  1034. while not hl.empty do
  1035. begin
  1036. s:=hl.GetFirst;
  1037. Remove(s);
  1038. Insert(s);
  1039. end;
  1040. hl.Free;
  1041. end
  1042. else
  1043. begin
  1044. hp:=TCmdStrListItem(list.first);
  1045. while assigned(hp) do
  1046. begin
  1047. hp2:=Find(hp.Str);
  1048. { Check if already in path, then we don't add it }
  1049. if not assigned(hp2) then
  1050. Concat(hp.Str);
  1051. hp:=TCmdStrListItem(hp.next);
  1052. end;
  1053. end;
  1054. end;
  1055. function TSearchPathList.FindFile(const f :TCmdStr;allowcache:boolean;var foundfile:TCmdStr):boolean;
  1056. Var
  1057. p : TCmdStrListItem;
  1058. begin
  1059. FindFile:=false;
  1060. p:=TCmdStrListItem(first);
  1061. while assigned(p) do
  1062. begin
  1063. result:=FileExistsNonCase(p.Str,f,allowcache,FoundFile);
  1064. if result then
  1065. exit;
  1066. p:=TCmdStrListItem(p.next);
  1067. end;
  1068. { Return original filename if not found }
  1069. FoundFile:=f;
  1070. end;
  1071. function FindFile(const f : TCmdStr; const path : TCmdStr;allowcache:boolean;var foundfile:TCmdStr):boolean;
  1072. Var
  1073. StartPos, EndPos, L: LongInt;
  1074. begin
  1075. Result:=False;
  1076. StartPos := 1;
  1077. L := Length(Path);
  1078. repeat
  1079. EndPos := StartPos;
  1080. while (EndPos <= L) and ((Path[EndPos] <> PathSeparator) and (Path[EndPos] <> ';')) do
  1081. Inc(EndPos);
  1082. Result := FileExistsNonCase(FixPath(Copy(Path, StartPos, EndPos-StartPos), False), f, allowcache, FoundFile);
  1083. if Result then
  1084. Exit;
  1085. StartPos := EndPos + 1;
  1086. until StartPos > L;
  1087. FoundFile:=f;
  1088. end;
  1089. {
  1090. function FindFilePchar(const f : TCmdStr;path : pchar;allowcache:boolean;var foundfile:TCmdStr):boolean;
  1091. Var
  1092. singlepathstring : TCmdStr;
  1093. startpc,pc : pchar;
  1094. begin
  1095. FindFilePchar:=false;
  1096. if Assigned (Path) then
  1097. begin
  1098. pc:=path;
  1099. repeat
  1100. startpc:=pc;
  1101. while (pc^<>PathSeparator) and (pc^<>';') and (pc^<>#0) do
  1102. inc(pc);
  1103. SetLength(singlepathstring, pc-startpc);
  1104. move(startpc^,singlepathstring[1],pc-startpc);
  1105. singlepathstring:=FixPath(ExpandFileName(singlepathstring),false);
  1106. result:=FileExistsNonCase(singlepathstring,f,allowcache,FoundFile);
  1107. if result then
  1108. exit;
  1109. if (pc^=#0) then
  1110. break;
  1111. inc(pc);
  1112. until false;
  1113. end;
  1114. foundfile:=f;
  1115. end;
  1116. }
  1117. function FindExe(const bin:TCmdStr;allowcache:boolean;var foundfile:TCmdStr):boolean;
  1118. var
  1119. Path : TCmdStr;
  1120. found : boolean;
  1121. begin
  1122. found:=FindFile(FixFileName(ChangeFileExt(bin,source_info.exeext)),'.;'+exepath,allowcache,foundfile);
  1123. if not found then
  1124. begin
  1125. {$ifdef macos}
  1126. Path:=GetEnvironmentVariable('Commands');
  1127. {$else}
  1128. Path:=GetEnvironmentVariable('PATH');
  1129. {$endif}
  1130. found:=FindFile(FixFileName(ChangeFileExt(bin,source_info.exeext)),Path,allowcache,foundfile);
  1131. end;
  1132. FindExe:=found;
  1133. end;
  1134. function GetShortName(const n:TCmdStr):TCmdStr;
  1135. {$ifdef win32}
  1136. var
  1137. hs,hs2 : TCmdStr;
  1138. i : longint;
  1139. {$endif}
  1140. {$if defined(go32v2) or defined(watcom)}
  1141. var
  1142. hs : shortstring;
  1143. {$endif}
  1144. begin
  1145. GetShortName:=n;
  1146. {$ifdef win32}
  1147. hs:=n+#0;
  1148. { may become longer in case of e.g. ".a" -> "a~1" or so }
  1149. setlength(hs2,length(hs)*2);
  1150. i:=Windows.GetShortPathName(@hs[1],@hs2[1],length(hs)*2);
  1151. if (i>0) and (i<=length(hs)*2) then
  1152. begin
  1153. setlength(hs2,strlen(@hs2[1]));
  1154. GetShortName:=hs2;
  1155. end;
  1156. {$endif}
  1157. {$if defined(go32v2) or defined(watcom)}
  1158. hs:=n;
  1159. if Dos.GetShortName(hs) then
  1160. GetShortName:=hs;
  1161. {$endif}
  1162. end;
  1163. {****************************************************************************
  1164. Init / Done
  1165. ****************************************************************************}
  1166. procedure InitFileUtils;
  1167. begin
  1168. DirCache:=TDirectoryCache.Create;
  1169. end;
  1170. procedure DoneFileUtils;
  1171. begin
  1172. DirCache.Free;
  1173. end;
  1174. end.