SteveSandersonMS / dotnet-wasi-sdk

Packages for building .NET projects as standalone WASI-compliant modules
519 stars 36 forks source link

Writing to file doesn't seem to work. #51

Closed brendandburns closed 1 year ago

brendandburns commented 1 year ago

My code is:

class DotnetWasm {
    public DotnetWasm() {}

    public void Print() {
        Console.WriteLine("Hello dotnet world: " + System.IO.Directory.GetCurrentDirectory());
    }

    public void Read() {
        foreach (var line in System.IO.File.ReadLines("/test.txt", new System.Text.UTF8Encoding())) {
            Console.WriteLine(line);
        }
    }

    public void Write() {
        using (var sw = System.IO.File.CreateText("/test.txt")) {
            sw.WriteLine("This is a test");
        }
    }

    static void Main(String[] args) {
        var dw = new DotnetWasm();
        dw.Print();
        dw.Read();
        dw.Write();
        System.IO.File.Copy("/test.txt", "/test-2.txt");
    }    
}

I'm running it in wasmtime with wasmtime --mapdir "/::." bin/Debug/net7.0/starter.wasm

If the file "test.txt" exists, then I see:

Hello dotnet world: /
This is a test.

Unhandled Exception:
System.EntryPointNotFoundException: SystemNative_PWrite
   at Interop.Sys.PWrite(SafeHandle fd, Byte* buffer, Int32 bufferSize, Int64 fileOffset)
   at System.IO.RandomAccess.WriteAtOffset(SafeFileHandle handle, ReadOnlySpan`1 buffer, Int64 fileOffset)
   at System.IO.Strategies.OSFileStreamStrategy.Write(ReadOnlySpan`1 buffer)
   at System.IO.Strategies.OSFileStreamStrategy.Write(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.Strategies.BufferedFileStreamStrategy.FlushWrite()
   at System.IO.Strategies.BufferedFileStreamStrategy.Flush(Boolean flushToDisk)
   at System.IO.FileStream.Flush(Boolean flushToDisk)
   at System.IO.FileStream.Flush()
   at System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder)
   at System.IO.StreamWriter.Dispose(Boolean disposing)
   at System.IO.TextWriter.Dispose()
   at DotnetWasm.Write()
   at DotnetWasm.Main(String[] args)

Unhandled Exception:
System.EntryPointNotFoundException: SystemNative_PWrite
   at Interop.Sys.PWrite(SafeHandle fd, Byte* buffer, Int32 bufferSize, Int64 fileOffset)
   at System.IO.RandomAccess.WriteAtOffset(SafeFileHandle handle, ReadOnlySpan`1 buffer, Int64 fileOffset)
   at System.IO.Strategies.OSFileStreamStrategy.Write(ReadOnlySpan`1 buffer)
   at System.IO.Strategies.OSFileStreamStrategy.Write(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.Strategies.BufferedFileStreamStrategy.FlushWrite()
   at System.IO.Strategies.BufferedFileStreamStrategy.Flush(Boolean flushToDisk)
   at System.IO.Strategies.BufferedFileStreamStrategy.Flush()
   at System.IO.Strategies.BufferedFileStreamStrategy.Dispose(Boolean disposing)
   at System.IO.Strategies.FileStreamStrategy.DisposeInternal(Boolean disposing)
   at System.IO.FileStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.IO.StreamWriter.CloseStreamFromDispose(Boolean disposing)
   at System.IO.StreamWriter.Dispose(Boolean disposing)
   at System.IO.TextWriter.Dispose()
   at DotnetWasm.Write()
   at DotnetWasm.Main(String[] args)

If the file test.txt doesn't exist I see:

Hello dotnet world: /

Unhandled Exception:
System.ObjectDisposedException: Safe handle has been closed.
Object name: 'SafeHandle'.
   at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success)
   at Interop.Sys.FStat(SafeHandle fd, FileStatus& output)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.FStatCheckIO(String path, FileStatus& status, Boolean& statusHasValue)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Init(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Int64& fileLength, UnixFileMode& filePermissions)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, UnixFileMode openPermissions, Int64& fileLength, UnixFileMode& filePermissions, Func`4 createOpenException)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode, Func`4 createOpenException)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.UnixFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategy(FileStream fileStream, String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean useAsync)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
   at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
   at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize)
   at System.IO.StreamReader..ctor(String path, Encoding encoding)
   at System.IO.ReadLinesIterator.CreateIterator(String path, Encoding encoding, StreamReader reader)
   at System.IO.ReadLinesIterator.CreateIterator(String path, Encoding encoding)
   at System.IO.File.ReadLines(String path, Encoding encoding)
   at DotnetWasm.Read()
   at DotnetWasm.Main(String[] args)
brendandburns commented 1 year ago

If I remove the dw.Read() in the main I get:

Hello dotnet world: /

Unhandled Exception:
System.ArgumentException: Stream was not writable.
   at System.IO.StreamWriter..ctor(Stream stream, Encoding encoding, Int32 bufferSize, Boolean leaveOpen)
   at System.IO.StreamWriter..ctor(String path, Boolean append, Encoding encoding, Int32 bufferSize)
   at System.IO.StreamWriter..ctor(String path, Boolean append)
   at System.IO.File.CreateText(String path)
   at DotnetWasm.Write()
   at DotnetWasm.Main(String[] args)
SteveSandersonMS commented 1 year ago

We chatted offline, but for anyone else reading this: the File.ReadLines and File.CreateText calls (and related APIs) are now fixed in the latest update just published to NuGet.

File.Copy remains broken because it relies on marshalling SafeHandle instances in a way that isn't supported yet in this SDK.

I'm going to close this issue because most of it is now dealt with, and the File.Copy API would be fixed as part of a much broader fix to marshalling which will likely only be done inside dotnet/runtime when this is made really supported.