diff --git a/demo/wasienv/filesystem/filesystemhost.lpi b/demo/wasienv/filesystem/filesystemhost.lpi index 9365036..a049d98 100644 --- a/demo/wasienv/filesystem/filesystemhost.lpi +++ b/demo/wasienv/filesystem/filesystemhost.lpi @@ -43,14 +43,6 @@ - - - - - - - - diff --git a/demo/wasienv/filesystem/filesystemhost.lpr b/demo/wasienv/filesystem/filesystemhost.lpr index afadc8a..f13a670 100644 --- a/demo/wasienv/filesystem/filesystemhost.lpr +++ b/demo/wasienv/filesystem/filesystemhost.lpr @@ -23,8 +23,8 @@ end; procedure TMyApplication.RunWasm; begin - // Writeln('Enabling logging'); - // WasiEnvironment.LogAPI:=True; +// Writeln('Enabling logging'); +// WasiEnvironment.LogAPI:=True; await(tjsobject, ZenFS.configure( new( ['mounts', new([ @@ -33,6 +33,10 @@ begin ]) ) ); + if not ZenFS.existsSync('/tmp') then + begin + ZenFS.mkdirSync('/tmp',777); + end; FS:=TWASIZenFS.Create; WasiEnvironment.FS:=FS; StartWebAssembly('fsdemo.wasm'); diff --git a/demo/wasienv/filesystem/fsdemo.lpr b/demo/wasienv/filesystem/fsdemo.lpr index 6241ebd..af57315 100644 --- a/demo/wasienv/filesystem/fsdemo.lpr +++ b/demo/wasienv/filesystem/fsdemo.lpr @@ -10,6 +10,34 @@ Const {$Endif} OurFile = OurDir+'/test.txt'; +Procedure ShowDir(aDir : String); + +var + Info : TSearchRec; + aFileCount : Integer; + TotalSize : Int64; + S : String; +begin + TotalSize:=0; + aFileCount:=0; + If FindFirst(aDir+'*',faAnyFile,Info)=0 then + try + Repeat + S:=Info.Name; + if (Info.Attr and faDirectory)<>0 then + S:=S+'/'; + if (Info.Attr and faSymLink)<>0 then + S:=S+'@'; + Writeln(FormatDateTime('yyyy-mm-dd"T"hh:nn:ss',Info.TimeStamp),' ',Info.Size:10,' '+S); + TotalSize:=TotalSize+Info.Size; + inc(aFileCount); + until FindNext(Info)<>0; + finally + FindClose(Info) + end; + Writeln('Total: ',TotalSize,' bytes in ',aFileCount,' files'); +end; + var HasDir : Boolean; HasFile: Boolean; @@ -17,7 +45,6 @@ var aSize,FD,byteCount : Integer; begin - S:='Hello, WebAssembly World!'; HasDir:=DirectoryExists(OurDir); if HasDir then Writeln('Directory already exists: ',OurDir) @@ -25,6 +52,8 @@ begin Writeln('Created new directory: ',OurDir) else Writeln('Failed to create directory: ',OurDir); + Writeln('Contents of root:'); + ShowDir('/'); HasFile:=FileExists(OurFile); If HasFile then Writeln('File exists: ',OurFile) @@ -37,12 +66,15 @@ begin else begin Writeln('Got fileHandle: ',FD); + S:='Hello, WebAssembly World!'; ByteCount:=FileWrite(FD,S[1],Length(S)); Writeln('Wrote ',byteCount,' bytes to file. Expected: ',Length(S)); FileClose(FD); Writeln('Closed file'); end; end; + Writeln('Contents of ',OurDir,':'); + ShowDir(ourdir+'/'); If FileExists(OurFile) then begin Writeln('Opening file: ',OurFile); diff --git a/packages/wasi/src/wasienv.pas b/packages/wasi/src/wasienv.pas index 54e1c0e..8dcea0c 100644 --- a/packages/wasi/src/wasienv.pas +++ b/packages/wasi/src/wasienv.pas @@ -143,7 +143,7 @@ type function fd_prestat_get(fd: NativeInt; bufPtr: TWasmMemoryLocation) : NativeInt; virtual; function fd_pwrite(fd, iovs, iovsLen, offset, nwritten : NativeInt) : NativeInt;virtual; function fd_read(fd: NativeInt; iovs : TWasmMemoryLocation; iovsLen: NativeInt; nread : TWasmMemoryLocation) : NativeInt; virtual; - function fd_readdir(fd, bufPtr, bufLen, cookie, bufusedPtr : NativeInt) : NativeInt; virtual; + function fd_readdir(fd : NativeInt; bufPtr: TWasmMemoryLocation; bufLen, cookie: NativeInt; bufusedPtr : TWasmMemoryLocation) : NativeInt; virtual; function fd_renumber(afrom,ato : NativeInt) : NativeInt; virtual; function fd_seek(fd, offset, whence : NativeInt; newOffsetPtr : TWasmMemoryLocation) : NativeInt; virtual; function fd_sync(fd : NativeInt) : NativeInt; virtual; @@ -1434,7 +1434,6 @@ begin if LogAPI then DoLog('TPas2JSWASIEnvironment.fd_seek(%d,%d,%d,[%x])',[fd,offset,whence,newOffsetPtr]); {$ENDIF} - console.log('Unimplemented: TPas2JSWASIEnvironment.fd_seek'); if not Assigned(FS) then Result:=WASI_ENOSYS else @@ -1633,15 +1632,51 @@ begin Result:=TJSUint8Array.New(0); end; -function TPas2JSWASIEnvironment.fd_readdir(fd, bufPtr, bufLen, cookie, - bufusedPtr: NativeInt): NativeInt; +function TPas2JSWASIEnvironment.fd_readdir(fd: NativeInt; bufPtr: TWasmMemoryLocation; bufLen, cookie: NativeInt; + bufusedPtr: TWasmMemoryLocation): NativeInt; + +var + Dirent : TWasiFSDirent; + NameArray : TJSUint8Array; + NameLen : integer; + Ptr : TWasmMemoryLocation; + Res,Used : Integer; + + begin {$IFNDEF NO_WASI_DEBUG} if LogAPI then DoLog('TPas2JSWASIEnvironment.fd_readdir(%d,[%x],%d,%d,[%x])',[fd,bufPtr,buflen,cookie,bufusedptr]); {$ENDIF} - console.log('Unimplemented: TPas2JSWASIEnvironment.fd_readdir'); - Result:= WASI_ENOSYS; + if not Assigned(FS) then + Result:=WASI_ENOSYS + else + try + Res:=FS.ReadDir(FD,AsIntNumber(Cookie),Dirent); + Result:=WASI_ESUCCESS; + Ptr:=BufPtr; + While ((Ptr-BufPtr)-1 then + begin + Ptr:=Ptr+NameLen; + Cookie:=Dirent.Next; + Res:=FS.ReadDir(FD,AsIntNumber(Cookie),Dirent) + end + else + Res:=WASI_ENOMEM; + end; + SetMemInfoInt32(bufusedPtr,Ptr-BufPtr); + except + On E : Exception do + Result:=ErrorToCode(E); + end; end; function TPas2JSWASIEnvironment.fd_renumber(afrom, ato: NativeInt): NativeInt; @@ -1811,12 +1846,12 @@ begin Loc:=BufPtr; Loc:=SetMemInfoInt64(Loc,Info.dev); Loc:=SetMemInfoUInt64(Loc,Info.Ino); - Loc:=SetMemInfoInt8(Loc,Info.filetype); + Loc:=SetMemInfoUInt64(Loc,Info.filetype); Loc:=SetMemInfoUInt64(Loc,Info.nLink); Loc:=SetMemInfoUInt64(Loc,Info.size); - Loc:=SetMemInfoUInt64(Loc,Info.atim); - Loc:=SetMemInfoUInt64(Loc,Info.mtim); - Loc:=SetMemInfoUInt64(Loc,Info.ctim); + Loc:=SetMemInfoUInt64(Loc,Info.atim*1000*1000); + Loc:=SetMemInfoUInt64(Loc,Info.mtim*1000*1000); + Loc:=SetMemInfoUInt64(Loc,Info.ctim*1000*1000); end; function TPas2JSWASIEnvironment.path_filestat_get(fd, flags: NativeInt; @@ -2064,7 +2099,7 @@ Var begin view:=getModuleMemoryDataView(); view.setint16(aLoc,aValue, IsLittleEndian); - Result:=aValue+SizeInt16; + Result:=aLoc+SizeInt16; end; function TPas2JSWASIEnvironment.SetMemInfoInt32(aLoc: TWasmMemoryLocation; @@ -2076,7 +2111,7 @@ Var begin view:=getModuleMemoryDataView(); view.setInt32(aLoc,aValue,IsLittleEndian); - Result:=aValue+SizeInt32; + Result:=aLoc+SizeInt32; end; function TPas2JSWASIEnvironment.SetMemInfoInt64(aLoc: TWasmMemoryLocation; diff --git a/packages/wasi/src/wasitypes.pas b/packages/wasi/src/wasitypes.pas index 52296aa..4868fec 100644 --- a/packages/wasi/src/wasitypes.pas +++ b/packages/wasi/src/wasitypes.pas @@ -34,7 +34,7 @@ Type function fd_prestat_get(fd: NativeInt; bufPtr: TWasmMemoryLocation) : NativeInt; function fd_pwrite(fd, iovs, iovsLen, offset, nwritten : NativeInt) : NativeInt; function fd_read(fd: NativeInt; iovs : TWasmMemoryLocation; iovsLen: NativeInt; nread : TWasmMemoryLocation) : NativeInt; - function fd_readdir(fd, bufPtr, bufLen, cookie, bufusedPtr : NativeInt) : NativeInt; + function fd_readdir(fd : NativeInt; bufPtr: TWasmMemoryLocation; bufLen, cookie: NativeInt; bufusedPtr : TWasmMemoryLocation) : NativeInt; function fd_renumber(afrom,ato : NativeInt) : NativeInt; function fd_seek(fd, offset, whence : NativeInt; newOffsetPtr : TWasmMemoryLocation) : NativeInt; function fd_sync(fd : NativeInt) : NativeInt; @@ -646,6 +646,13 @@ type end; TWasiPreStat = __wasi_prestat_t; + TDirentType = (dtUnknown,dtFile,dtDirectory,dtSymlink,dtSocket,dtBlockDevice,dtCharacterDevice,dtFIFO); + TWasiFSDirent = record + ino: NativeInt; + name : String; + EntryType: TDirentType; + next : NativeInt; + end; { EWasiFSError } @@ -673,9 +680,21 @@ type function DataSync(FD : Integer) : NativeInt; function Seek(FD : integer; Offset : Integer; Whence : TSeekWhence; out NewPos : Integer) : NativeInt; Function Read(FD : Integer; Data : TJSUint8Array; AtPos : Integer; Out BytesRead : Integer) : NativeInt; + function ReadDir(FD: Integer; Cookie: NativeInt; out DirEnt: TWasiFSDirent): NativeInt; Function GetPrestat(FD: Integer) : String; end; +Const + DirentMap : Array [TDirentType] of Integer = + (__WASI_FILETYPE_UNKNOWN, + __WASI_FILETYPE_REGULAR_FILE, + __WASI_FILETYPE_DIRECTORY, + __WASI_FILETYPE_SYMBOLIC_LINK, + __WASI_FILETYPE_SOCKET_STREAM, + __WASI_FILETYPE_BLOCK_DEVICE, + __WASI_FILETYPE_CHARACTER_DEVICE, + __WASI_FILETYPE_UNKNOWN); + implementation end. diff --git a/packages/wasi/src/wasizenfs.pas b/packages/wasi/src/wasizenfs.pas index d03a25a..d7ceaa8 100644 --- a/packages/wasi/src/wasizenfs.pas +++ b/packages/wasi/src/wasizenfs.pas @@ -5,21 +5,26 @@ unit wasizenfs; interface uses - JS, libzenfs, Wasitypes; + SysUtils, JS, libzenfs, Wasitypes; Type + EWASIZenFS = class(Exception); { TWASIZenFS } - TWASIZenFS = class (TObject,IWasiFS) private FRoot : TZenFSDir; + FDirMap : TJSMap; protected + function AllocateDirFD(aDir: TZenFSDirentArray): Integer; + function IsDirEnt(FD : Integer) : TZenFSDirentArray; + Procedure RemoveDirent(FD : integer); function PrependFD(FD: Integer; aPath: String): string; class function ExceptToError(E : TJSObject) : Integer; class function ZenFSDateToWasiTimeStamp(aDate: TJSDate): Nativeint; class function ZenStatToWasiStat(ZSTat: TZenFSStats): TWasiFileStat; Public + constructor create; Function MkDirAt(FD : Integer; const aPath : String) : NativeInt; Function RmDirAt(FD : Integer; const aPath : String) : NativeInt; function StatAt(FD : Integer; const aPath : String; var stat: TWasiFileStat) : NativeInt; @@ -38,6 +43,7 @@ Type function DataSync(FD : Integer) : NativeInt; function Seek(FD : integer; Offset : Integer; Whence : TSeekWhence; out NewPos : Integer) : NativeInt; Function Read(FD : Integer; Data : TJSUint8Array; AtPos : Integer; Out BytesRead : Integer) : NativeInt; + function ReadDir(FD: Integer; Cookie: NativeInt; out DirEnt: TWasiFSDirent): NativeInt; Function GetPrestat(FD: Integer) : String; end; @@ -48,6 +54,33 @@ const { TWASIZenFS } +function TWASIZenFS.AllocateDirFD(aDir : TZenFSDirentArray): Integer; +var + I : integer; + +begin + I:=4; + While (I<100) and (FDirMap.has(i)) do + Inc(I); + if I=100 then + Raise EWASIZenFS.Create('Too many directories'); + FDirMap.&set(I,aDir); + Result:=I; +end; + +function TWASIZenFS.IsDirEnt(FD: Integer): TZenFSDirentArray; +begin + if FDirMap.has(FD) then + Result:=TZenFSDirentArray(FDirMap.get(FD)) + else + Result:=Nil; +end; + +procedure TWASIZenFS.RemoveDirent(FD: integer); +begin + FDirMap.delete(FD); +end; + function TWASIZenFS.PrependFD(FD: Integer; aPath: String) : string; begin @@ -116,6 +149,11 @@ begin Result.ctim:=ZenFSDateToWasiTimeStamp(ZStat.cTime); end; +constructor TWASIZenFS.create; +begin + FDirMap:=TJSMap.new; +end; + function TWASIZenFS.StatAt(FD: Integer; const aPath: String; var stat: TWasiFileStat): NativeInt; @@ -231,9 +269,16 @@ end; function TWASIZenFS.Close(FD : Integer): NativeInt; + begin try - ZenFS.closeSync(fd); + if IsDirEnt(FD)<>Nil then + begin + RemoveDirent(FD); + Result:=WASI_ESUCCESS; + end + else + ZenFS.closeSync(fd); Result:=resOK; except on E : TJSObject do @@ -344,6 +389,38 @@ begin end; end; +function TWASIZenFS.ReadDir(FD: Integer; Cookie : NativeInt; out DirEnt: TWasiFSDirent): NativeInt; + +var + DirEnts : TZenFSDirentArray; + ZDirEntry : TZenFSDirent; + +begin + DirEnts:=IsDirEnt(FD); + if Not Assigned(DirEnts) then + Exit(WASI_EBADF); + if (Cookie<0) or (Cookie>=Length(Dirents)) then + Exit(WASI_ENOENT); + ZDirEntry:=Dirents[Cookie]; + DirEnt.name:=ZDirEntry.path; + if ZDirEntry.isFile() then + Dirent.EntryType:=dtFile + else if ZDirEntry.isDirectory() then + Dirent.EntryType:=dtDirectory + else if ZDirEntry.isSymbolicLink() then + Dirent.EntryType:=dtSymlink + else if ZDirEntry.isFIFO() then + Dirent.EntryType:=dtFIFO + else if ZDirEntry.isBlockDevice() then + Dirent.EntryType:=dtBlockDevice + else if ZDirEntry.isCharacterDevice() then + Dirent.EntryType:=dtCharacterDevice + else if ZDirEntry.isSocket() then + Dirent.EntryType:=dtSocket; + Dirent.Next:=Cookie+1; + Result:=ResOK; +end; + function TWASIZenFS.GetPrestat(FD: Integer): String; begin if (FD=3) then @@ -376,6 +453,8 @@ var lFlags : String; Rights : NativeInt; Reading,Writing : Boolean; + Dir : TZenFSDirentArray; + Opts : TZenFSReadDirOptions; Function HasFlag(aFlag : Integer) : Boolean; begin @@ -397,26 +476,37 @@ var begin if (fdFlags<>0) and (fsFlags<>0) then ; lPath:=PrependFD(FD,aPath); - Rights:=AsIntNumber(fsRightsBase); - Writing:=HasFlag(__WASI_OFLAGS_CREAT) or HasRight(__WASI_RIGHTS_FD_WRITE); - Reading:=HasRight(__WASI_RIGHTS_FD_READ); - if Writing then + if Not HasFlag(__WASI_OFLAGS_DIRECTORY) then begin - if HasFlag(__WASI_OFLAGS_TRUNC) then - lFLags:='w' + Rights:=AsIntNumber(fsRightsBase); + Writing:=HasFlag(__WASI_OFLAGS_CREAT) or HasRight(__WASI_RIGHTS_FD_WRITE); + Reading:=HasRight(__WASI_RIGHTS_FD_READ); + if Writing then + begin + if HasFlag(__WASI_OFLAGS_TRUNC) then + lFLags:='w' + else + lFLags:='a'; + if HasFlag(__WASI_OFLAGS_EXCL) then + lFLags:=lFLags+'x'; + if Reading then + lFLags:=lFLags+'+'; + end else - lFLags:='a'; - if HasFlag(__WASI_OFLAGS_EXCL) then - lFLags:=lFLags+'x'; - if Reading then - lFLags:=lFLags+'+'; - end - else - begin - lFlags:='r'; + begin + lFlags:='r'; + end; end; try - OpenFD:=ZenFS.openSync(lPath,lFlags); + if HasFlag(__WASI_OFLAGS_DIRECTORY) then + begin + Opts:=TZenFSReadDirOptions.New; + Opts.withFileTypes:=True; + Dir:=ZenFS.readdirSyncDirent(lpath,Opts); + OpenFD:=AllocateDirFD(Dir); + end + else + OpenFD:=ZenFS.openSync(lPath,lFlags); Result:=resOK; except on E : TJSObject do diff --git a/packages/zenfs/src/libzenfs.pas b/packages/zenfs/src/libzenfs.pas index 1e973ec..4427291 100644 --- a/packages/zenfs/src/libzenfs.pas +++ b/packages/zenfs/src/libzenfs.pas @@ -162,6 +162,7 @@ Type function isSocket() : Boolean; property path : string read FPath; end; + TZenFSDirentArray = Array of TZenFSDirent; TJSDirentCallback = reference to procedure(aDirent : TZenFSDirent); TJSDirCloseCallback = reference to procedure; @@ -741,6 +742,8 @@ Type Function readdirSync(path : String; options : TZenFSReadDirOptions): TStringDynArray; overload; Function readdirSync(path : String): TStringDynArray; overload; + Function readdirSyncDirent(path : String; options : TZenFSReadDirOptions): TZenFSDirentArray; external name 'readdirSync'; + Procedure readlink(path : String; options : String; callback : TStringCallBack); overload; Procedure readlink(path : String; callback : TStringCallBack); overload;