wasiutil.pp 15 KB

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