Browse Source

wasi: make `os.open` work with absolute paths

Laytan Laats 1 year ago
parent
commit
4e18e1b191
2 changed files with 115 additions and 3 deletions
  1. 113 1
      core/os/os_wasi.odin
  2. 2 2
      core/sys/wasm/wasi/wasi_api.odin

+ 113 - 1
core/os/os_wasi.odin

@@ -38,6 +38,112 @@ _alloc_command_line_arguments :: proc() -> (args: []string) {
 	return
 }
 
+// WASI works with "preopened" directories, the environment retrieves directories
+// (for example with `wasmtime --dir=. module.wasm`) and those given directories
+// are the only ones accessible by the application.
+//
+// So in order to facilitate the `os` API (absolute paths etc.) we keep a list
+// of the given directories and match them when needed (notably `os.open`).
+
+@(private)
+Preopen :: struct {
+	fd:     wasi.fd_t,
+	prefix: string,
+}
+@(private)
+preopens: [dynamic]Preopen
+
+@(private, init)
+init_preopens :: proc() {
+
+	strip_prefixes :: proc(path: string) -> string {
+		path := path
+		loop: for len(path) > 0 {
+			switch {
+			case path[0] == '/':
+				path = path[1:]
+			case len(path) > 2  && path[0] == '.' && path[1] == '/':
+				path = path[2:]
+			case len(path) == 1 && path[0] == '.':
+				path = path[1:]
+			case:
+				break loop
+			}
+		}
+		return path
+	}
+
+	loop: for fd := wasi.fd_t(3); ; fd += 1 {
+		desc, err := wasi.fd_prestat_get(fd)
+		#partial switch err {
+		case .BADF: break loop
+		case:       panic("fd_prestat_get returned an unexpected error")
+		case .SUCCESS:
+		}
+
+		switch desc.tag {
+		case .DIR:
+			buf := make([]byte, desc.dir.pr_name_len) or_else panic("could not allocate memory for filesystem preopens")
+			if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS {
+				panic("could not get filesystem preopen dir name")
+			}
+			append(&preopens, Preopen{fd, strip_prefixes(string(buf))})
+		}
+	}
+}
+
+wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
+
+	prefix_matches :: proc(prefix, path: string) -> bool {
+		// Empty is valid for any relative path.
+		if len(prefix) == 0 && len(path) > 0 && path[0] == '/' {
+			return true
+		}
+
+		if len(path) < len(prefix) {
+			return false
+		}
+
+		if path[:len(prefix)] != prefix {
+			return false
+		}
+
+		// Only match on full components.
+		i := len(prefix)
+		for i > 0 && prefix[i-1] == '/' {
+			i -= 1
+		}
+		return path[i] == '/'
+	}
+
+	path := path
+	for len(path) > 0 && path[0] == '/' {
+		path = path[1:]
+	}
+
+	match: Preopen
+	#reverse for preopen in preopens {
+		if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) {
+			match = preopen
+		}
+	}
+
+	if match.fd == 0 {
+		return 0, "", false
+	}
+
+	relative := path[len(match.prefix):]
+	for len(relative) > 0 && relative[0] == '/' {
+		relative = relative[1:]
+	}
+
+	if len(relative) == 0 {
+		relative = "."
+	}
+
+	return match.fd, relative, true
+}
+
 write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	iovs := wasi.ciovec_t(data)
 	n, err := wasi.fd_write(wasi.fd_t(fd), {iovs})
@@ -87,7 +193,13 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn
 	if mode & O_SYNC == O_SYNC {
 		fdflags += {.SYNC}
 	}
-	fd, err := wasi.path_open(wasi.fd_t(current_dir),{.SYMLINK_FOLLOW},path,oflags,rights,{},fdflags)
+
+	dir_fd, relative, ok := wasi_match_preopen(path)
+	if !ok {
+		return INVALID_HANDLE, Errno(wasi.errno_t.BADF)
+	}
+
+	fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags)
 	return Handle(fd), Errno(err)
 }
 close :: proc(fd: Handle) -> Errno {

+ 2 - 2
core/sys/wasm/wasi/wasi_api.odin

@@ -962,7 +962,7 @@ prestat_dir_t :: struct {
 }
 
 prestat_t :: struct {
-	tag: u8,
+	tag: preopentype_t,
 	using u: struct {
 		dir: prestat_dir_t,
 	},
@@ -1158,7 +1158,7 @@ foreign wasi {
 		/**
 		 * A buffer into which to write the preopened directory name.
 		 */
-		path: string,
+		path: []byte,
 	) -> errno_t ---
 	/**
 	 * Create a directory.