wasiutil.pp 14 KB

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