123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- {
- This file is part of the Free Pascal run time library.
- Copyright (c) 2021 by the Free Pascal development team.
- Helper RTL functions for The WebAssembly System Interface (WASI).
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- {$IFNDEF FPC_DOTTEDUNITS}
- unit wasiutil;
- {$ENDIF FPC_DOTTEDUNITS}
- {$mode objfpc}
- interface
- {$IFDEF FPC_DOTTEDUNITS}
- uses
- WASIApi.WASIApi;
- {$ELSE FPC_DOTTEDUNITS}
- uses
- wasiapi;
- {$ENDIF FPC_DOTTEDUNITS}
- type
- PWasiSearchRec = ^TWasiSearchRec;
- TWasiSearchRec = record
- SearchPos : UInt64; {directory position}
- SearchNum : LongInt; {to track which search this is}
- DirFD : __wasi_fd_t; {directory fd handle for reading directory}
- SearchType : Byte; {0=normal, 1=open will close, 2=only 1 file}
- SearchAttr : Byte; {attribute we are searching for}
- Attr : Byte; {attribute of found file}
- Time : __wasi_timestamp_t; {last modify date of found file}
- Size : __wasi_filesize_t; {file size of found file}
- Name : RawByteString; {name of found file}
- SearchSpec : RawByteString; {search pattern}
- NamePos : Word; {end of path, start of name position}
- End;
- function ConvertToFdRelativePath(path: RawByteString; out fd: LongInt; out relfd_path: RawByteString): Word; external name 'FPC_WASI_CONVERTTOFDRELATIVEPATH';
- 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';
- function FNMatch(const Pattern,Name:rawbytestring):Boolean;
- function WasiFindFirst(const Path: RawByteString; Attr: Word; var f: TWasiSearchRec): longint;
- function WasiFindNext(var f: TWasiSearchRec): longint;
- procedure WasiFindClose(var f: TWasiSearchRec);
- Function UniversalToEpoch(year,month,day,hour,minute,second:Word):int64;
- Function LocalToEpoch(year,month,day,hour,minute,second:Word):int64;
- Procedure EpochToUniversal(epoch:int64;var year,month,day,hour,minute,second:Word);
- Procedure EpochToLocal(epoch:int64;var year,month,day,hour,minute,second:Word);
- implementation
- const
- {Bitmasks for file attribute}
- readonly = $01;
- hidden = $02;
- sysfile = $04;
- volumeid = $08;
- directory = $10;
- archive = $20;
- anyfile = $3F;
- Const
- RtlFindSize = 15;
- Type
- RtlFindRecType = Record
- DirFD : LongInt;
- SearchNum,
- LastUsed : LongInt;
- End;
- Var
- RtlFindRecs : Array[1..RtlFindSize] of RtlFindRecType;
- CurrSearchNum : LongInt;
- Function FNMatch(const Pattern,Name:rawbytestring):Boolean;
- Var
- LenPat,LenName : longint;
- Function DoFNMatch(i,j:longint):Boolean;
- Var
- Found : boolean;
- Begin
- Found:=true;
- While Found and (i<=LenPat) Do
- Begin
- Case Pattern[i] of
- '?' : Found:=(j<=LenName);
- '*' : Begin
- {find the next character in pattern, different of ? and *}
- while Found do
- begin
- inc(i);
- if i>LenPat then Break;
- case Pattern[i] of
- '*' : ;
- '?' : begin
- if j>LenName then begin DoFNMatch:=false; Exit; end;
- inc(j);
- end;
- else
- Found:=false;
- end;
- end;
- Assert((i>LenPat) or ( (Pattern[i]<>'*') and (Pattern[i]<>'?') ));
- {Now, find in name the character which i points to, if the * or ?
- wasn't the last character in the pattern, else, use up all the
- chars in name}
- Found:=false;
- if (i<=LenPat) then
- begin
- repeat
- {find a letter (not only first !) which maches pattern[i]}
- while (j<=LenName) and (name[j]<>pattern[i]) do
- inc (j);
- if (j<LenName) then
- begin
- if DoFnMatch(i+1,j+1) then
- begin
- i:=LenPat;
- j:=LenName;{we can stop}
- Found:=true;
- Break;
- end else
- inc(j);{We didn't find one, need to look further}
- end else
- if j=LenName then
- begin
- Found:=true;
- Break;
- end;
- { This 'until' condition must be j>LenName, not j>=LenName.
- That's because when we 'need to look further' and
- j = LenName then loop must not terminate. }
- until (j>LenName);
- end else
- begin
- j:=LenName;{we can stop}
- Found:=true;
- end;
- end;
- else {not a wildcard character in pattern}
- Found:=(j<=LenName) and (pattern[i]=name[j]);
- end;
- inc(i);
- inc(j);
- end;
- DoFnMatch:=Found and (j>LenName);
- end;
- Begin {start FNMatch}
- LenPat:=Length(Pattern);
- LenName:=Length(Name);
- FNMatch:=DoFNMatch(1,1);
- End;
- Procedure WasiFindClose(Var f: TWasiSearchRec);
- {
- Closes dirfd if it is open
- }
- Var
- res: __wasi_errno_t;
- i : longint;
- Begin
- if f.SearchType=0 then
- begin
- i:=1;
- repeat
- if (RtlFindRecs[i].SearchNum=f.SearchNum) then
- break;
- inc(i);
- until (i>RtlFindSize);
- If i<=RtlFindSize Then
- Begin
- RtlFindRecs[i].SearchNum:=0;
- if f.dirfd<>-1 then
- repeat
- res:=__wasi_fd_close(f.dirfd);
- until (res=__WASI_ERRNO_SUCCESS) or (res<>__WASI_ERRNO_INTR);
- End;
- end;
- f.dirfd:=-1;
- End;
- Function FindGetFileInfo(const s:rawbytestring;var f:TWasiSearchRec):boolean;
- var
- st : __wasi_filestat_t;
- fd : __wasi_fd_t;
- pr : RawByteString;
- Info : record
- FMode: LongInt;
- FSize: __wasi_filesize_t;
- FMTime: __wasi_timestamp_t;
- end;
- begin
- FindGetFileInfo:=false;
- if ConvertToFdRelativePath(s,fd,pr)<>0 then
- exit;
- { todo: __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW??? }
- if __wasi_path_filestat_get(fd,0,PAnsiChar(pr),Length(pr),@st)<>__WASI_ERRNO_SUCCESS then
- exit;
- info.FSize:=st.size;
- info.FMTime:=st.mtim;
- if st.filetype=__WASI_FILETYPE_DIRECTORY then
- info.fmode:=$10
- else
- info.fmode:=$0;
- {if (st.st_mode and STAT_IWUSR)=0 then
- info.fmode:=info.fmode or 1;}
- if s[f.NamePos+1]='.' then
- info.fmode:=info.fmode or $2;
- If ((Info.FMode and Not(f.searchattr))=0) Then
- Begin
- f.Name:=Copy(s,f.NamePos+1);
- f.Attr:=Info.FMode;
- f.Size:=Info.FSize;
- f.Time:=Info.FMTime;
- FindGetFileInfo:=true;
- End;
- end;
- Function FindLastUsed: Longint;
- {
- Find unused or least recently used dirpointer slot in findrecs array
- }
- Var
- BestMatch,i : Longint;
- Found : Boolean;
- Begin
- BestMatch:=1;
- i:=1;
- Found:=False;
- While (i <= RtlFindSize) And (Not Found) Do
- Begin
- If (RtlFindRecs[i].SearchNum = 0) Then
- Begin
- BestMatch := i;
- Found := True;
- End
- Else
- Begin
- If RtlFindRecs[i].LastUsed > RtlFindRecs[BestMatch].LastUsed Then
- BestMatch := i;
- End;
- Inc(i);
- End;
- FindLastUsed := BestMatch;
- End;
- function WasiFindNext(var f: TWasiSearchRec): longint;
- {
- re-opens dir if not already in array and calls FindWorkProc
- }
- Var
- fd,ourfd: __wasi_fd_t;
- pr: RawByteString;
- res: __wasi_errno_t;
- DirName : RawByteString;
- i,
- ArrayPos : Longint;
- FName,
- SName : RawByteString;
- Found,
- Finished : boolean;
- Buf: array [0..SizeOf(__wasi_dirent_t)+256-1] of Byte;
- BufUsed: __wasi_size_t;
- Begin
- If f.SearchType=0 Then
- Begin
- ArrayPos:=0;
- For i:=1 to RtlFindSize Do
- Begin
- If RtlFindRecs[i].SearchNum = f.SearchNum Then
- ArrayPos:=i;
- Inc(RtlFindRecs[i].LastUsed);
- End;
- If ArrayPos=0 Then
- Begin
- If f.NamePos = 0 Then
- DirName:='./'
- Else
- DirName:=Copy(f.SearchSpec,1,f.NamePos);
- if ConvertToFdRelativePath(DirName,fd,pr)=0 then
- begin
- repeat
- res:=__wasi_path_open(fd,
- 0,
- PAnsiChar(pr),
- length(pr),
- __WASI_OFLAGS_DIRECTORY,
- __WASI_RIGHTS_FD_READDIR,
- __WASI_RIGHTS_FD_READDIR,
- 0,
- @ourfd);
- until (res=__WASI_ERRNO_SUCCESS) or (res<>__WASI_ERRNO_INTR);
- If res=__WASI_ERRNO_SUCCESS Then
- begin
- f.DirFD := ourfd;
- ArrayPos:=FindLastUsed;
- If RtlFindRecs[ArrayPos].SearchNum > 0 Then
- repeat
- res:=__wasi_fd_close(RtlFindRecs[arraypos].DirFD);
- until (res=__WASI_ERRNO_SUCCESS) or (res<>__WASI_ERRNO_INTR);
- RtlFindRecs[ArrayPos].SearchNum := f.SearchNum;
- RtlFindRecs[ArrayPos].DirFD := f.DirFD;
- end
- else
- f.DirFD:=-1;
- end
- else
- f.DirFD:=-1;
- End;
- if ArrayPos>0 then
- RtlFindRecs[ArrayPos].LastUsed:=0;
- end;
- {Main loop}
- SName:=Copy(f.SearchSpec,f.NamePos+1);
- Found:=False;
- Finished:=(f.DirFD=-1);
- While Not Finished Do
- Begin
- res:=__wasi_fd_readdir(f.DirFD,
- @buf,
- SizeOf(buf),
- f.searchpos,
- @bufused);
- if (res<>__WASI_ERRNO_SUCCESS) or (bufused<=SizeOf(__wasi_dirent_t)) then
- FName:=''
- else
- begin
- SetLength(FName,P__wasi_dirent_t(@buf)^.d_namlen);
- Move(buf[SizeOf(__wasi_dirent_t)],FName[1],Length(FName));
- f.searchpos:=P__wasi_dirent_t(@buf)^.d_next;
- end;
- If FName='' Then
- Finished:=True
- Else
- Begin
- If FNMatch(SName,FName) Then
- Begin
- Found:=FindGetFileInfo(Copy(f.SearchSpec,1,f.NamePos)+FName,f);
- if Found then
- Finished:=true;
- End;
- End;
- End;
- {Shutdown}
- If Found Then
- result:=0
- Else
- Begin
- WasiFindClose(f);
- result:=18;
- End;
- End;
- function WasiFindFirst(const Path: RawByteString; Attr: Word; var f: TWasiSearchRec): longint;
- {
- opens dir and calls FindWorkProc
- }
- Begin
- fillchar(f,sizeof(f),0);
- if Path='' then
- begin
- result:=3;
- exit;
- end;
- {Create Info}
- f.SearchSpec := Path;
- {We always also search for readonly and archive, regardless of Attr:}
- f.SearchAttr := Attr or archive or readonly;
- f.SearchPos := 0;
- f.NamePos := Length(f.SearchSpec);
- while (f.NamePos>0) and not (f.SearchSpec[f.NamePos] in AllowDirectorySeparators) do
- dec(f.NamePos);
- {Wildcards?}
- if (Pos('?',Path)=0) and (Pos('*',Path)=0) then
- begin
- if FindGetFileInfo(Path,f) then
- result:=0
- else
- begin
- { According to tdos2 test it should return 18
- if ErrNo=Sys_ENOENT then
- result:=3
- else }
- result:=18;
- end;
- f.DirFD:=-1;
- f.SearchType:=1;
- f.searchnum:=-1;
- end
- else
- {Find Entry}
- begin
- Inc(CurrSearchNum);
- f.SearchNum:=CurrSearchNum;
- f.SearchType:=0;
- result:=WasiFindNext(f);
- end;
- End;
- Function UniversalToEpoch(year,month,day,hour,minute,second:Word):int64;
- const
- days_in_month: array [boolean, 1..12] of Byte =
- ((31,28,31,30,31,30,31,31,30,31,30,31),
- (31,29,31,30,31,30,31,31,30,31,30,31));
- days_before_month: array [boolean, 1..12] of Word =
- ((0,
- 0+31,
- 0+31+28,
- 0+31+28+31,
- 0+31+28+31+30,
- 0+31+28+31+30+31,
- 0+31+28+31+30+31+30,
- 0+31+28+31+30+31+30+31,
- 0+31+28+31+30+31+30+31+31,
- 0+31+28+31+30+31+30+31+31+30,
- 0+31+28+31+30+31+30+31+31+30+31,
- 0+31+28+31+30+31+30+31+31+30+31+30),
- (0,
- 0+31,
- 0+31+29,
- 0+31+29+31,
- 0+31+29+31+30,
- 0+31+29+31+30+31,
- 0+31+29+31+30+31+30,
- 0+31+29+31+30+31+30+31,
- 0+31+29+31+30+31+30+31+31,
- 0+31+29+31+30+31+30+31+31+30,
- 0+31+29+31+30+31+30+31+31+30+31,
- 0+31+29+31+30+31+30+31+31+30+31+30));
- var
- leap: Boolean;
- days_in_year: LongInt;
- y,m: LongInt;
- begin
- if (year<1970) or (month<1) or (month>12) or (day<1) or (day>31) or
- (hour>=24) or (minute>=60) or (second>=60) then
- begin
- result:=-1;
- exit;
- end;
- leap:=((year mod 4)=0) and (((year mod 100)<>0) or ((year mod 400)=0));
- if day>days_in_month[leap,month] then
- begin
- result:=-1;
- exit;
- end;
- result:=0;
- for y:=1970 to year-1 do
- if ((y mod 4)=0) and (((y mod 100)<>0) or ((y mod 400)=0)) then
- Inc(result,366)
- else
- Inc(result,365);
- Inc(result,days_before_month[leap,month]);
- Inc(result,day-1);
- result:=(((result*24+hour)*60+minute)*60)+second;
- end;
- Function LocalToEpoch(year,month,day,hour,minute,second:Word):int64;
- begin
- { todo: convert UTC to local time, as soon as we can get the local timezone
- from WASI: https://github.com/WebAssembly/WASI/issues/239 }
- result:=UniversalToEpoch(year,month,day,hour,minute,second);
- end;
- Procedure EpochToUniversal(epoch:int64;var year,month,day,hour,minute,second:Word);
- const
- days_in_month: array [boolean, 1..12] of Byte =
- ((31,28,31,30,31,30,31,31,30,31,30,31),
- (31,29,31,30,31,30,31,31,30,31,30,31));
- var
- leap: Boolean;
- days_in_year: LongInt;
- begin
- if epoch<0 then
- begin
- year:=0;
- month:=0;
- day:=0;
- hour:=0;
- minute:=0;
- second:=0;
- exit;
- end;
- second:=epoch mod 60;
- epoch:=epoch div 60;
- minute:=epoch mod 60;
- epoch:=epoch div 60;
- hour:=epoch mod 24;
- epoch:=epoch div 24;
- year:=1970;
- leap:=false;
- days_in_year:=365;
- while epoch>=days_in_year do
- begin
- Dec(epoch,days_in_year);
- Inc(year);
- leap:=((year mod 4)=0) and (((year mod 100)<>0) or ((year mod 400)=0));
- if leap then
- days_in_year:=366
- else
- days_in_year:=365;
- end;
- month:=1;
- Inc(epoch);
- while epoch>days_in_month[leap,month] do
- begin
- Dec(epoch,days_in_month[leap,month]);
- Inc(month);
- end;
- day:=Word(epoch);
- end;
- Procedure EpochToLocal(epoch:int64;var year,month,day,hour,minute,second:Word);
- begin
- { todo: convert UTC to local time, as soon as we can get the local timezone
- from WASI: https://github.com/WebAssembly/WASI/issues/239 }
- EpochToUniversal(epoch,year,month,day,hour,minute,second);
- end;
- end.
|