wasiutil.pp 15 KB

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