WebAssembly / WASI

WebAssembly System Interface
Other
4.89k stars 256 forks source link

How to obtain working directory fd? #323

Closed yagehu closed 4 years ago

yagehu commented 4 years ago

I'm writing small WASI programs in text format to test the syscall support in various Wasm runtimes like Wasmtime and Wasmer. I'm trying to call path_open which I've imported as $__wasi_path_open. According to the docs and this, the first argument to the path_open syscall is an fd for "the working directory at which the resolution of the path starts." How can I obtain such an fd?

(module
  (type (;0;) (func (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
  (type (;1;) (func))
  (import "wasi_snapshot_preview" "path_open" (func $__wasi_path_open (type 0)))

  (func $_start (type 1) (export "_start")
    (call $__wasi_path_open
      (; The first argument is a dirfd ;)
      ???
    )
  )
)
caspervonb commented 4 years ago

You need to pick from the pre-opened directories.

yagehu commented 4 years ago

Sorry if these are dumb questions... But how do I access pre-opened directories? Is there a convention for how they get passed to a Wasm module by the runtimes? Can you provide a minimal example?

caspervonb commented 4 years ago

Iterate over fd_prestat_get until it returns BADFD. Its up to the runtime how the preopen table is managed, for wasmtime it's via the --mapdir and --dir flags.

yagehu commented 4 years ago

fd_prestat_get is exactly what I'm looking for! Thanks.

For posterity, a short text format program that uses fd_prestat_get:

;; Exit status codes:
;; 
;; - 8: Could not find a preopened fd.
(module
  (type (;0;) (func (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
  (type (;1;) (func))
  (type (;2;) (func (param i32 i32) (result i32)))
  (type (;3;) (func (param i32)))
  (type (;4;) (func (param i32) (result i32)))

  (import "wasi_snapshot_preview1" "fd_prestat_get"
    (func $__wasi_fd_prestat_get (type 2))
  )
  (import "wasi_snapshot_preview1" "path_open"
    (func $__wasi_path_open (type 0))
  )
  (import "wasi_snapshot_preview1" "proc_exit"
    (func $__wasi_proc_exit (type 3))
  )

  (func $_start (type 1)
    (local $dirfd i32)
    (local $errno i32)

    (call $getFirstGoodDirfd (i32.const 0))
    (if
      (i32.ne (local.tee $errno) (i32.const 0))
      (then
        (call $__wasi_proc_exit (i32.const 8))
      )
      (else)
    )

    (local.set $dirfd (i32.load (i32.const 0)))

    (call $__wasi_proc_exit (i32.const 0))
  )
  (func $getFirstGoodDirfd (type 4) (param $fd i32) (result i32)
    (local $dirfd i32)
    (local $errno i32)

    ;; Start with the lowest fd after stdin, stdout, and stderr.
    (local.set $dirfd (i32.const 3))

    (block ;; label = @1
      (block ;; label = @2
        (loop $loop ;; label = @3
          (call $__wasi_fd_prestat_get
            (local.get $dirfd)
            (i32.const 0)
          )

          ;; If errno is 0, we found a good dirfd. Break the loop.
          (br_if 2 (;@1;) (i32.eq (local.tee $errno) (i32.const 0)))

          ;; If errno is 8, we encountered `badf`. Return 8 indicating failure.
          (br_if 1 (;@2;) (i32.eq (local.get $errno) (i32.const 8)))

          ;; Increment dirfd.
          (local.set $dirfd (i32.add (local.get $dirfd) (i32.const 1)))

          (br $loop)
        )
      )

      (return (i32.const 8))
    )

    (i32.store (local.get $fd) (local.get $dirfd))
    i32.const 0
  )

  (memory (;0;) (export "memory") 1)

  (export "_start" (func $_start))
)
turbolent commented 2 years ago

Thank you so much for asking this question and the example @yagehu, and thank you for answering @caspervonb!

Is this documented anywhere? I looked at the API spec and was pretty confused.

I felt like there must be some sort of function which returns the preopened file descriptors, something like args/environ_sizes_get. It seems odd that repeated syscalls are necessary to iterate.