wasiutil.pp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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. {$IFNDEF FPC_DOTTEDUNITS}
  12. unit wasiutil;
  13. {$ENDIF FPC_DOTTEDUNITS}
  14. {$mode objfpc}
  15. interface
  16. {$IFDEF FPC_DOTTEDUNITS}
  17. uses
  18. WASIApi.WASIApi;
  19. {$ELSE FPC_DOTTEDUNITS}
  20. uses
  21. wasiapi;
  22. {$ENDIF FPC_DOTTEDUNITS}
  23. type
  24. PWasiSearchRec = ^TWasiSearchRec;
  25. TWasiSearchRec = record
  26. SearchPos : UInt64; {directory position}
  27. SearchNum : LongInt; {to track which search this is}
  28. DirFD : __wasi_fd_t; {directory fd handle for reading directory}
  29. SearchType : Byte; {0=normal, 1=open will close, 2=only 1 file}
  30. SearchAttr : Byte; {attribute we are searching for}
  31. Attr : Byte; {attribute of found file}
  32. Time : __wasi_timestamp_t; {last modify date of found file}
  33. Size : __wasi_filesize_t; {file size of found file}
  34. Name : RawByteString; {name of found file}
  35. SearchSpec : RawByteString; {search pattern}
  36. NamePos : Word; {end of path, start of name position}
  37. End;
  38. var
  39. // Provide this so the time can be corrected for TZ info.
  40. // Once WASI has some means of setting it, it can be used to initialize this variable.
  41. UTCTimeOffset : Integer; // in minutes;
  42. function ConvertToFdRelativePath(path: RawByteString; out fd: LongInt; out relfd_path: RawByteString): Word; external name 'FPC_WASI_CONVERTTOFDRELATIVEPATH';
  43. function fpc_wasi_path_readlink_ansistring(fd: __wasi_fd_t; const path: PAnsiChar; path_len: size_t; out link: rawbytestring): __wasi_errno_t; external name 'FPC_WASI_PATH_READLINK_ANSISTRING';
  44. function FNMatch(const Pattern,Name:rawbytestring):Boolean;
  45. function WasiFindFirst(const Path: RawByteString; Attr: Word; var f: TWasiSearchRec): longint;
  46. function WasiFindNext(var f: TWasiSearchRec): longint;
  47. procedure WasiFindClose(var f: TWasiSearchRec);
  48. Function UniversalToEpoch(year,month,day,hour,minute,second:Word):int64;
  49. Function LocalToEpoch(year,month,day,hour,minute,second:Word):int64;
  50. Procedure EpochToUniversal(epoch:int64;var year,month,day,hour,minute,second:Word);
  51. Procedure EpochToLocal(epoch:int64;var year,month,day,hour,minute,second:Word);
  52. implementation
  53. const
  54. {Bitmasks for file attribute}
  55. readonly = $01;
  56. hidden = $02;
  57. sysfile = $04;
  58. volumeid = $08;
  59. directory = $10;
  60. archive = $20;
  61. anyfile = $3F;
  62. Const
  63. RtlFindSize = 15;
  64. Type
  65. RtlFindRecType = Record
  66. DirFD : LongInt;
  67. SearchNum,
  68. LastUsed : LongInt;
  69. End;
  70. Var
  71. RtlFindRecs : Array[1..RtlFindSize] of RtlFindRecType;
  72. CurrSearchNum : LongInt;
  73. Function FNMatch(const Pattern,Name:rawbytestring):Boolean;
  74. Var
  75. LenPat,LenName : longint;
  76. Function DoFNMatch(i,j:longint):Boolean;
  77. Var
  78. Found : boolean;
  79. Begin
  80. Found:=true;
  81. While Found and (i<=LenPat) Do
  82. Begin
  83. Case Pattern[i] of
  84. '?' : Found:=(j<=LenName);
  85. '*' : Begin
  86. {find the next character in pattern, different of ? and *}
  87. while Found do
  88. begin
  89. inc(i);
  90. if i>LenPat then Break;
  91. case Pattern[i] of
  92. '*' : ;
  93. '?' : begin
  94. if j>LenName then begin DoFNMatch:=false; Exit; end;
  95. inc(j);
  96. end;
  97. else
  98. Found:=false;
  99. end;
  100. end;
  101. Assert((i>LenPat) or ( (Pattern[i]<>'*') and (Pattern[i]<>'?') ));
  102. {Now, find in name the character which i points to, if the * or ?
  103. wasn't the last character in the pattern, else, use up all the
  104. chars in name}
  105. Found:=false;
  106. if (i<=LenPat) then
  107. begin
  108. repeat
  109. {find a letter (not only first !) which maches pattern[i]}
  110. while (j<=LenName) and (name[j]<>pattern[i]) do
  111. inc (j);
  112. if (j<LenName) then
  113. begin
  114. if DoFnMatch(i+1,j+1) then
  115. begin
  116. i:=LenPat;
  117. j:=LenName;{we can stop}
  118. Found:=true;
  119. Break;
  120. end else
  121. inc(j);{We didn't find one, need to look further}
  122. end else
  123. if j=LenName then
  124. begin
  125. Found:=true;
  126. Break;
  127. end;
  128. { This 'until' condition must be j>LenName, not j>=LenName.
  129. That's because when we 'need to look further' and
  130. j = LenName then loop must not terminate. }
  131. until (j>LenName);
  132. end else
  133. begin
  134. j:=LenName;{we can stop}
  135. Found:=true;
  136. end;
  137. end;
  138. else {not a wildcard character in pattern}
  139. Found:=(j<=LenName) and (pattern[i]=name[j]);
  140. end;
  141. inc(i);
  142. inc(j);
  143. end;
  144. DoFnMatch:=Found and (j>LenName);
  145. end;
  146. Begin {start FNMatch}
  147. LenPat:=Length(Pattern);
  148. LenName:=Length(Name);
  149. FNMatch:=DoFNMatch(1,1);
  150. End;
  151. Procedure WasiFindClose(Var f: TWasiSearchRec);
  152. {
  153. Closes dirfd if it is open
  154. }
  155. Var
  156. res: __wasi_errno_t;
  157. i : longint;
  158. Begin
  159. if f.SearchType=0 then
  160. begin
  161. i:=1;
  162. repeat
  163. if (RtlFindRecs[i].SearchNum=f.SearchNum) then
  164. break;
  165. inc(i);
  166. until (i>RtlFindSize);
  167. If i<=RtlFindSize Then
  168. Begin
  169. RtlFindRecs[i].SearchNum:=0;
  170. if f.dirfd<>-1 then
  171. repeat
  172. res:=__wasi_fd_close(f.dirfd);
  173. until (res=__WASI_ERRNO_SUCCESS) or (res<>__WASI_ERRNO_INTR);
  174. End;
  175. end;
  176. f.dirfd:=-1;
  177. End;
  178. Function FindGetFileInfo(const s:rawbytestring;var f:TWasiSearchRec):boolean;
  179. var
  180. st : __wasi_filestat_t;
  181. fd : __wasi_fd_t;
  182. pr : RawByteString;
  183. Info : record
  184. FMode: LongInt;
  185. FSize: __wasi_filesize_t;
  186. FMTime: __wasi_timestamp_t;
  187. end;
  188. begin
  189. FindGetFileInfo:=false;
  190. if ConvertToFdRelativePath(s,fd,pr)<>0 then
  191. exit;
  192. { todo: __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW??? }
  193. if __wasi_path_filestat_get(fd,0,PAnsiChar(pr),Length(pr),@st)<>__WASI_ERRNO_SUCCESS then
  194. exit;
  195. info.FSize:=st.size;
  196. info.FMTime:=st.mtim;
  197. if st.filetype=__WASI_FILETYPE_DIRECTORY then
  198. info.fmode:=$10
  199. else
  200. info.fmode:=$0;
  201. {if (st.st_mode and STAT_IWUSR)=0 then
  202. info.fmode:=info.fmode or 1;}
  203. if s[f.NamePos+1]='.' then
  204. info.fmode:=info.fmode or $2;
  205. If ((Info.FMode and Not(f.searchattr))=0) Then
  206. Begin
  207. f.Name:=Copy(s,f.NamePos+1);
  208. f.Attr:=Info.FMode;
  209. f.Size:=Info.FSize;
  210. f.Time:=Info.FMTime;
  211. FindGetFileInfo:=true;
  212. End;
  213. end;
  214. Function FindLastUsed: Longint;
  215. {
  216. Find unused or least recently used dirpointer slot in findrecs array
  217. }
  218. Var
  219. BestMatch,i : Longint;
  220. Found : Boolean;
  221. Begin
  222. BestMatch:=1;
  223. i:=1;
  224. Found:=False;
  225. While (i <= RtlFindSize) And (Not Found) Do
  226. Begin
  227. If (RtlFindRecs[i].SearchNum = 0) Then
  228. Begin
  229. BestMatch := i;
  230. Found := True;
  231. End
  232. Else
  233. Begin
  234. If RtlFindRecs[i].LastUsed > RtlFindRecs[BestMatch].LastUsed Then
  235. BestMatch := i;
  236. End;
  237. Inc(i);
  238. End;
  239. FindLastUsed := BestMatch;
  240. End;
  241. function WasiFindNext(var f: TWasiSearchRec): longint;
  242. {
  243. re-opens dir if not already in array and calls FindWorkProc
  244. }
  245. Var
  246. fd,ourfd: __wasi_fd_t;
  247. pr: RawByteString;
  248. res: __wasi_errno_t;
  249. DirName : RawByteString;
  250. i,
  251. ArrayPos : Longint;
  252. FName,
  253. SName : RawByteString;
  254. Found,
  255. Finished : boolean;
  256. Buf: array [0..SizeOf(__wasi_dirent_t)+256-1] of Byte;
  257. BufUsed: __wasi_size_t;
  258. Begin
  259. If f.SearchType=0 Then
  260. Begin
  261. ArrayPos:=0;
  262. For i:=1 to RtlFindSize Do
  263. Begin
  264. If RtlFindRecs[i].SearchNum = f.SearchNum Then
  265. ArrayPos:=i;
  266. Inc(RtlFindRecs[i].LastUsed);
  267. End;
  268. If ArrayPos=0 Then
  269. Begin
  270. If f.NamePos = 0 Then
  271. DirName:='./'
  272. Else
  273. DirName:=Copy(f.SearchSpec,1,f.NamePos);
  274. if ConvertToFdRelativePath(DirName,fd,pr)=0 then
  275. begin
  276. { WasmTime doesn't like opening an empty path }
  277. if pr='' then
  278. pr:='.';
  279. repeat
  280. res:=__wasi_path_open(fd,
  281. 0,
  282. PAnsiChar(pr),
  283. length(pr),
  284. __WASI_OFLAGS_DIRECTORY,
  285. __WASI_RIGHTS_FD_READDIR,
  286. __WASI_RIGHTS_FD_READDIR,
  287. 0,
  288. @ourfd);
  289. until (res=__WASI_ERRNO_SUCCESS) or (res<>__WASI_ERRNO_INTR);
  290. If res=__WASI_ERRNO_SUCCESS Then
  291. begin
  292. f.DirFD := ourfd;
  293. ArrayPos:=FindLastUsed;
  294. If RtlFindRecs[ArrayPos].SearchNum > 0 Then
  295. repeat
  296. res:=__wasi_fd_close(RtlFindRecs[arraypos].DirFD);
  297. until (res=__WASI_ERRNO_SUCCESS) or (res<>__WASI_ERRNO_INTR);
  298. RtlFindRecs[ArrayPos].SearchNum := f.SearchNum;
  299. RtlFindRecs[ArrayPos].DirFD := f.DirFD;
  300. end
  301. else
  302. f.DirFD:=-1;
  303. end
  304. else
  305. f.DirFD:=-1;
  306. End;
  307. if ArrayPos>0 then
  308. RtlFindRecs[ArrayPos].LastUsed:=0;
  309. end;
  310. {Main loop}
  311. SName:=Copy(f.SearchSpec,f.NamePos+1);
  312. Found:=False;
  313. Finished:=(f.DirFD=-1);
  314. While Not Finished Do
  315. Begin
  316. res:=__wasi_fd_readdir(f.DirFD,
  317. @buf,
  318. SizeOf(buf),
  319. f.searchpos,
  320. @bufused);
  321. if (res<>__WASI_ERRNO_SUCCESS) or (bufused<=SizeOf(__wasi_dirent_t)) then
  322. FName:=''
  323. else
  324. begin
  325. SetLength(FName,P__wasi_dirent_t(@buf)^.d_namlen);
  326. Move(buf[SizeOf(__wasi_dirent_t)],FName[1],Length(FName));
  327. f.searchpos:=P__wasi_dirent_t(@buf)^.d_next;
  328. end;
  329. If FName='' Then
  330. Finished:=True
  331. Else
  332. Begin
  333. If FNMatch(SName,FName) Then
  334. Begin
  335. Found:=FindGetFileInfo(Copy(f.SearchSpec,1,f.NamePos)+FName,f);
  336. if Found then
  337. Finished:=true;
  338. End;
  339. End;
  340. End;
  341. {Shutdown}
  342. If Found Then
  343. result:=0
  344. Else
  345. Begin
  346. WasiFindClose(f);
  347. result:=18;
  348. End;
  349. End;
  350. function WasiFindFirst(const Path: RawByteString; Attr: Word; var f: TWasiSearchRec): longint;
  351. {
  352. opens dir and calls FindWorkProc
  353. }
  354. Begin
  355. fillchar(f,sizeof(f),0);
  356. if Path='' then
  357. begin
  358. result:=3;
  359. exit;
  360. end;
  361. {Create Info}
  362. f.SearchSpec := Path;
  363. {We always also search for readonly and archive, regardless of Attr:}
  364. f.SearchAttr := Attr or archive or readonly;
  365. f.SearchPos := 0;
  366. f.NamePos := Length(f.SearchSpec);
  367. while (f.NamePos>0) and not (f.SearchSpec[f.NamePos] in AllowDirectorySeparators) do
  368. dec(f.NamePos);
  369. {Wildcards?}
  370. if (Pos('?',Path)=0) and (Pos('*',Path)=0) then
  371. begin
  372. if FindGetFileInfo(Path,f) then
  373. result:=0
  374. else
  375. begin
  376. { According to tdos2 test it should return 18
  377. if ErrNo=Sys_ENOENT then
  378. result:=3
  379. else }
  380. result:=18;
  381. end;
  382. f.DirFD:=-1;
  383. f.SearchType:=1;
  384. f.searchnum:=-1;
  385. end
  386. else
  387. {Find Entry}
  388. begin
  389. Inc(CurrSearchNum);
  390. f.SearchNum:=CurrSearchNum;
  391. f.SearchType:=0;
  392. result:=WasiFindNext(f);
  393. end;
  394. End;
  395. Function UniversalToEpoch(year,month,day,hour,minute,second:Word):int64;
  396. const
  397. days_in_month: array [boolean, 1..12] of Byte =
  398. ((31,28,31,30,31,30,31,31,30,31,30,31),
  399. (31,29,31,30,31,30,31,31,30,31,30,31));
  400. days_before_month: array [boolean, 1..12] of Word =
  401. ((0,
  402. 0+31,
  403. 0+31+28,
  404. 0+31+28+31,
  405. 0+31+28+31+30,
  406. 0+31+28+31+30+31,
  407. 0+31+28+31+30+31+30,
  408. 0+31+28+31+30+31+30+31,
  409. 0+31+28+31+30+31+30+31+31,
  410. 0+31+28+31+30+31+30+31+31+30,
  411. 0+31+28+31+30+31+30+31+31+30+31,
  412. 0+31+28+31+30+31+30+31+31+30+31+30),
  413. (0,
  414. 0+31,
  415. 0+31+29,
  416. 0+31+29+31,
  417. 0+31+29+31+30,
  418. 0+31+29+31+30+31,
  419. 0+31+29+31+30+31+30,
  420. 0+31+29+31+30+31+30+31,
  421. 0+31+29+31+30+31+30+31+31,
  422. 0+31+29+31+30+31+30+31+31+30,
  423. 0+31+29+31+30+31+30+31+31+30+31,
  424. 0+31+29+31+30+31+30+31+31+30+31+30));
  425. var
  426. leap: Boolean;
  427. days_in_year: LongInt;
  428. y,m: LongInt;
  429. begin
  430. if (year<1970) or (month<1) or (month>12) or (day<1) or (day>31) or
  431. (hour>=24) or (minute>=60) or (second>=60) then
  432. begin
  433. result:=-1;
  434. exit;
  435. end;
  436. leap:=((year mod 4)=0) and (((year mod 100)<>0) or ((year mod 400)=0));
  437. if day>days_in_month[leap,month] then
  438. begin
  439. result:=-1;
  440. exit;
  441. end;
  442. result:=0;
  443. for y:=1970 to year-1 do
  444. if ((y mod 4)=0) and (((y mod 100)<>0) or ((y mod 400)=0)) then
  445. Inc(result,366)
  446. else
  447. Inc(result,365);
  448. Inc(result,days_before_month[leap,month]);
  449. Inc(result,day-1);
  450. result:=(((result*24+hour)*60+minute)*60)+second;
  451. end;
  452. Function LocalToEpoch(year,month,day,hour,minute,second:Word):int64;
  453. begin
  454. { todo: convert UTC to local time, as soon as we can get the local timezone
  455. from WASI: https://github.com/WebAssembly/WASI/issues/239 }
  456. result:=UniversalToEpoch(year,month,day,hour,minute,second);
  457. Result:=Result-(UTCTimeOffset*60);
  458. end;
  459. Procedure EpochToUniversal(epoch:int64;var year,month,day,hour,minute,second:Word);
  460. const
  461. days_in_month: array [boolean, 1..12] of Byte =
  462. ((31,28,31,30,31,30,31,31,30,31,30,31),
  463. (31,29,31,30,31,30,31,31,30,31,30,31));
  464. var
  465. leap: Boolean;
  466. days_in_year: LongInt;
  467. begin
  468. if epoch<0 then
  469. begin
  470. year:=0;
  471. month:=0;
  472. day:=0;
  473. hour:=0;
  474. minute:=0;
  475. second:=0;
  476. exit;
  477. end;
  478. second:=epoch mod 60;
  479. epoch:=epoch div 60;
  480. minute:=epoch mod 60;
  481. epoch:=epoch div 60;
  482. hour:=epoch mod 24;
  483. epoch:=epoch div 24;
  484. year:=1970;
  485. leap:=false;
  486. days_in_year:=365;
  487. while epoch>=days_in_year do
  488. begin
  489. Dec(epoch,days_in_year);
  490. Inc(year);
  491. leap:=((year mod 4)=0) and (((year mod 100)<>0) or ((year mod 400)=0));
  492. if leap then
  493. days_in_year:=366
  494. else
  495. days_in_year:=365;
  496. end;
  497. month:=1;
  498. Inc(epoch);
  499. while epoch>days_in_month[leap,month] do
  500. begin
  501. Dec(epoch,days_in_month[leap,month]);
  502. Inc(month);
  503. end;
  504. day:=Word(epoch);
  505. end;
  506. Procedure EpochToLocal(epoch:int64;var year,month,day,hour,minute,second:Word);
  507. begin
  508. { todo: convert UTC to local time, as soon as we can get the local timezone
  509. from WASI: https://github.com/WebAssembly/WASI/issues/239 }
  510. epoch:=epoch+(UTCTimeOffset*60);
  511. EpochToUniversal(epoch,year,month,day,hour,minute,second);
  512. end;
  513. end.