Browse Source

* Add FindFirst/FindNext support

Michaël Van Canneyt 1 year ago
parent
commit
5d3abdcc5d

+ 0 - 8
demo/wasienv/filesystem/filesystemhost.lpi

@@ -43,14 +43,6 @@
           <Item0 Name="PasJSIsProjectHTMLFile" Value="1"/>
         </CustomData>
       </Unit>
-      <Unit>
-        <Filename Value="wasitypes.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
-      <Unit>
-        <Filename Value="wasizenfs.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
     </Units>
   </ProjectOptions>
   <CompilerOptions>

+ 6 - 2
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');

+ 33 - 1
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);

+ 47 - 12
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)<BufLen) and (Res=WASI_ESUCCESS) do
+        begin
+        NameArray:=UTF8TextEncoder.encode(Dirent.name);
+        NameLen:=NameArray.byteLength;
+        Ptr:=SetMemInfoUInt64(Ptr,Dirent.Next);
+        Ptr:=SetMemInfoUInt64(Ptr,Dirent.ino);
+        Ptr:=SetMemInfoInt32(Ptr,NameLen);
+        Ptr:=SetMemInfoInt32(Ptr,DirentMap[Dirent.EntryType]);
+        if SetUTF8StringInMem(Ptr,BufLen-18,Dirent.Name)<>-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;

+ 20 - 1
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.

+ 109 - 19
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

+ 3 - 0
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;