wasiutil.pp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2021 by the Free Pascal development team.
  4. Helper RTL functions for The WebAssembly System Interface (WASI).
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit wasiutil;
  12. {$mode objfpc}
  13. interface
  14. uses
  15. wasiapi;
  16. type
  17. PWasiSearchRec = ^TWasiSearchRec;
  18. TWasiSearchRec = record
  19. SearchPos : UInt64; {directory position}
  20. SearchNum : LongInt; {to track which search this is}
  21. DirFD : __wasi_fd_t; {directory fd handle for reading directory}
  22. SearchType : Byte; {0=normal, 1=open will close, 2=only 1 file}
  23. SearchAttr : Byte; {attribute we are searching for}
  24. Attr : Byte; {attribute of found file}
  25. Time : __wasi_timestamp_t; {last modify date of found file}
  26. Size : __wasi_filesize_t; {file size of found file}
  27. Name : RawByteString; {name of found file}
  28. SearchSpec : RawByteString; {search pattern}
  29. NamePos : Word; {end of path, start of name position}
  30. End;
  31. function ConvertToFdRelativePath(path: RawByteString; out fd: LongInt; out relfd_path: RawByteString): Word; external name 'FPC_WASI_CONVERTTOFDRELATIVEPATH';
  32. function fpc_wasi_path_readlink_ansistring(fd: __wasi_fd_t; const path: PChar; path_len: size_t; out link: rawbytestring): __wasi_errno_t; external name 'FPC_WASI_PATH_READLINK_ANSISTRING';
  33. function FNMatch(const Pattern,Name:rawbytestring):Boolean;
  34. function WasiFindFirst(const Path: RawByteString; Attr: Word; var f: TWasiSearchRec): longint;
  35. function WasiFindNext(var f: TWasiSearchRec): longint;
  36. procedure WasiFindClose(var f: TWasiSearchRec);
  37. implementation
  38. const
  39. {Bitmasks for file attribute}
  40. readonly = $01;
  41. hidden = $02;
  42. sysfile = $04;
  43. volumeid = $08;
  44. directory = $10;
  45. archive = $20;
  46. anyfile = $3F;
  47. Const
  48. RtlFindSize = 15;
  49. Type
  50. RtlFindRecType = Record
  51. DirFD : LongInt;
  52. SearchNum,
  53. LastUsed : LongInt;
  54. End;
  55. Var
  56. RtlFindRecs : Array[1..RtlFindSize] of RtlFindRecType;
  57. CurrSearchNum : LongInt;
  58. Function FNMatch(const Pattern,Name:rawbytestring):Boolean;
  59. Var
  60. LenPat,LenName : longint;
  61. Function DoFNMatch(i,j:longint):Boolean;
  62. Var
  63. Found : boolean;
  64. Begin
  65. Found:=true;
  66. While Found and (i<=LenPat) Do
  67. Begin
  68. Case Pattern[i] of
  69. '?' : Found:=(j<=LenName);
  70. '*' : Begin
  71. {find the next character in pattern, different of ? and *}
  72. while Found do
  73. begin
  74. inc(i);
  75. if i>LenPat then Break;
  76. case Pattern[i] of
  77. '*' : ;
  78. '?' : begin
  79. if j>LenName then begin DoFNMatch:=false; Exit; end;
  80. inc(j);
  81. end;
  82. else
  83. Found:=false;
  84. end;
  85. end;
  86. Assert((i>LenPat) or ( (Pattern[i]<>'*') and (Pattern[i]<>'?') ));
  87. {Now, find in name the character which i points to, if the * or ?
  88. wasn't the last character in the pattern, else, use up all the
  89. chars in name}
  90. Found:=false;
  91. if (i<=LenPat) then
  92. begin
  93. repeat
  94. {find a letter (not only first !) which maches pattern[i]}
  95. while (j<=LenName) and (name[j]<>pattern[i]) do
  96. inc (j);
  97. if (j<LenName) then
  98. begin
  99. if DoFnMatch(i+1,j+1) then
  100. begin
  101. i:=LenPat;
  102. j:=LenName;{we can stop}
  103. Found:=true;
  104. Break;
  105. end else
  106. inc(j);{We didn't find one, need to look further}
  107. end else
  108. if j=LenName then
  109. begin
  110. Found:=true;
  111. Break;
  112. end;
  113. { This 'until' condition must be j>LenName, not j>=LenName.
  114. That's because when we 'need to look further' and
  115. j = LenName then loop must not terminate. }
  116. until (j>LenName);
  117. end else
  118. begin
  119. j:=LenName;{we can stop}
  120. Found:=true;
  121. end;
  122. end;
  123. else {not a wildcard character in pattern}
  124. Found:=(j<=LenName) and (pattern[i]=name[j]);
  125. end;
  126. inc(i);
  127. inc(j);
  128. end;
  129. DoFnMatch:=Found and (j>LenName);
  130. end;
  131. Begin {start FNMatch}
  132. LenPat:=Length(Pattern);
  133. LenName:=Length(Name);
  134. FNMatch:=DoFNMatch(1,1);
  135. End;
  136. Procedure WasiFindClose(Var f: TWasiSearchRec);
  137. {
  138. Closes dirfd if it is open
  139. }
  140. Var
  141. res: __wasi_errno_t;
  142. i : longint;
  143. Begin
  144. if f.SearchType=0 then
  145. begin
  146. i:=1;
  147. repeat
  148. if (RtlFindRecs[i].SearchNum=f.SearchNum) then
  149. break;
  150. inc(i);
  151. until (i>RtlFindSize);
  152. If i<=RtlFindSize Then
  153. Begin
  154. RtlFindRecs[i].SearchNum:=0;
  155. if f.dirfd<>-1 then
  156. repeat
  157. res:=__wasi_fd_close(f.dirfd);
  158. until (res=__WASI_ERRNO_SUCCESS) or (res<>__WASI_ERRNO_INTR);
  159. End;
  160. end;
  161. f.dirfd:=-1;
  162. End;
  163. Function FindGetFileInfo(const s:rawbytestring;var f:TWasiSearchRec):boolean;
  164. var
  165. st : __wasi_filestat_t;
  166. fd : __wasi_fd_t;
  167. pr : RawByteString;
  168. Info : record
  169. FMode: LongInt;
  170. FSize: __wasi_filesize_t;
  171. FMTime: __wasi_timestamp_t;
  172. end;
  173. begin
  174. FindGetFileInfo:=false;
  175. if ConvertToFdRelativePath(s,fd,pr)<>0 then
  176. exit;
  177. { todo: __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW??? }
  178. if __wasi_path_filestat_get(fd,0,PChar(pr),Length(pr),@st)<>__WASI_ERRNO_SUCCESS then
  179. exit;
  180. info.FSize:=st.size;
  181. info.FMTime:=st.mtim;
  182. if st.filetype=__WASI_FILETYPE_DIRECTORY then
  183. info.fmode:=$10
  184. else
  185. info.fmode:=$0;
  186. {if (st.st_mode and STAT_IWUSR)=0 then
  187. info.fmode:=info.fmode or 1;}
  188. if s[f.NamePos+1]='.' then
  189. info.fmode:=info.fmode or $2;
  190. If ((Info.FMode and Not(f.searchattr))=0) Then
  191. Begin
  192. f.Name:=Copy(s,f.NamePos+1);
  193. f.Attr:=Info.FMode;
  194. f.Size:=Info.FSize;
  195. f.Time:=Info.FMTime;
  196. FindGetFileInfo:=true;
  197. End;
  198. end;
  199. Function FindLastUsed: Longint;
  200. {
  201. Find unused or least recently used dirpointer slot in findrecs array
  202. }
  203. Var
  204. BestMatch,i : Longint;
  205. Found : Boolean;
  206. Begin
  207. BestMatch:=1;
  208. i:=1;
  209. Found:=False;
  210. While (i <= RtlFindSize) And (Not Found) Do
  211. Begin
  212. If (RtlFindRecs[i].SearchNum = 0) Then
  213. Begin
  214. BestMatch := i;
  215. Found := True;
  216. End
  217. Else
  218. Begin
  219. If RtlFindRecs[i].LastUsed > RtlFindRecs[BestMatch].LastUsed Then
  220. BestMatch := i;
  221. End;
  222. Inc(i);
  223. End;
  224. FindLastUsed := BestMatch;
  225. End;
  226. function WasiFindNext(var f: TWasiSearchRec): longint;
  227. {
  228. re-opens dir if not already in array and calls FindWorkProc
  229. }
  230. Var
  231. fd,ourfd: __wasi_fd_t;
  232. pr: RawByteString;
  233. res: __wasi_errno_t;
  234. DirName : RawByteString;
  235. i,
  236. ArrayPos : Longint;
  237. FName,
  238. SName : RawByteString;
  239. Found,
  240. Finished : boolean;
  241. Buf: array [0..SizeOf(__wasi_dirent_t)+256-1] of Byte;
  242. BufUsed: __wasi_size_t;
  243. Begin
  244. If f.SearchType=0 Then
  245. Begin
  246. ArrayPos:=0;
  247. For i:=1 to RtlFindSize Do
  248. Begin
  249. If RtlFindRecs[i].SearchNum = f.SearchNum Then
  250. ArrayPos:=i;
  251. Inc(RtlFindRecs[i].LastUsed);
  252. End;
  253. If ArrayPos=0 Then
  254. Begin
  255. If f.NamePos = 0 Then
  256. DirName:='./'
  257. Else
  258. DirName:=Copy(f.SearchSpec,1,f.NamePos);
  259. if ConvertToFdRelativePath(DirName,fd,pr)=0 then
  260. begin
  261. repeat
  262. res:=__wasi_path_open(fd,
  263. 0,
  264. PChar(pr),
  265. length(pr),
  266. __WASI_OFLAGS_DIRECTORY,
  267. __WASI_RIGHTS_FD_READDIR,
  268. __WASI_RIGHTS_FD_READDIR,
  269. 0,
  270. @ourfd);
  271. until (res=__WASI_ERRNO_SUCCESS) or (res<>__WASI_ERRNO_INTR);
  272. If res=__WASI_ERRNO_SUCCESS Then
  273. begin
  274. f.DirFD := ourfd;
  275. ArrayPos:=FindLastUsed;
  276. If RtlFindRecs[ArrayPos].SearchNum > 0 Then
  277. repeat
  278. res:=__wasi_fd_close(RtlFindRecs[arraypos].DirFD);
  279. until (res=__WASI_ERRNO_SUCCESS) or (res<>__WASI_ERRNO_INTR);
  280. RtlFindRecs[ArrayPos].SearchNum := f.SearchNum;
  281. RtlFindRecs[ArrayPos].DirFD := f.DirFD;
  282. end
  283. else
  284. f.DirFD:=-1;
  285. end
  286. else
  287. f.DirFD:=-1;
  288. End;
  289. if ArrayPos>0 then
  290. RtlFindRecs[ArrayPos].LastUsed:=0;
  291. end;
  292. {Main loop}
  293. SName:=Copy(f.SearchSpec,f.NamePos+1);
  294. Found:=False;
  295. Finished:=(f.DirFD=-1);
  296. While Not Finished Do
  297. Begin
  298. res:=__wasi_fd_readdir(f.DirFD,
  299. @buf,
  300. SizeOf(buf),
  301. f.searchpos,
  302. @bufused);
  303. if (res<>__WASI_ERRNO_SUCCESS) or (bufused<=SizeOf(__wasi_dirent_t)) then
  304. FName:=''
  305. else
  306. begin
  307. SetLength(FName,P__wasi_dirent_t(@buf)^.d_namlen);
  308. Move(buf[SizeOf(__wasi_dirent_t)],FName[1],Length(FName));
  309. f.searchpos:=P__wasi_dirent_t(@buf)^.d_next;
  310. end;
  311. If FName='' Then
  312. Finished:=True
  313. Else
  314. Begin
  315. If FNMatch(SName,FName) Then
  316. Begin
  317. Found:=FindGetFileInfo(Copy(f.SearchSpec,1,f.NamePos)+FName,f);
  318. if Found then
  319. Finished:=true;
  320. End;
  321. End;
  322. End;
  323. {Shutdown}
  324. If Found Then
  325. result:=0
  326. Else
  327. Begin
  328. WasiFindClose(f);
  329. result:=18;
  330. End;
  331. End;
  332. function WasiFindFirst(const Path: RawByteString; Attr: Word; var f: TWasiSearchRec): longint;
  333. {
  334. opens dir and calls FindWorkProc
  335. }
  336. Begin
  337. fillchar(f,sizeof(f),0);
  338. if Path='' then
  339. begin
  340. result:=3;
  341. exit;
  342. end;
  343. {Create Info}
  344. f.SearchSpec := Path;
  345. {We always also search for readonly and archive, regardless of Attr:}
  346. f.SearchAttr := Attr or archive or readonly;
  347. f.SearchPos := 0;
  348. f.NamePos := Length(f.SearchSpec);
  349. while (f.NamePos>0) and not (f.SearchSpec[f.NamePos] in AllowDirectorySeparators) do
  350. dec(f.NamePos);
  351. {Wildcards?}
  352. if (Pos('?',Path)=0) and (Pos('*',Path)=0) then
  353. begin
  354. if FindGetFileInfo(Path,f) then
  355. result:=0
  356. else
  357. begin
  358. { According to tdos2 test it should return 18
  359. if ErrNo=Sys_ENOENT then
  360. result:=3
  361. else }
  362. result:=18;
  363. end;
  364. f.DirFD:=-1;
  365. f.SearchType:=1;
  366. f.searchnum:=-1;
  367. end
  368. else
  369. {Find Entry}
  370. begin
  371. Inc(CurrSearchNum);
  372. f.SearchNum:=CurrSearchNum;
  373. f.SearchType:=0;
  374. result:=WasiFindNext(f);
  375. end;
  376. End;
  377. end.