iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0

前言:那條規矩有名字

昨天讓 wasmtime 跑了三次,資料夾是宿主一條一條遞進去的,沒遞就不存在。這條規矩不是 wasmtime 自己想的,它有名字:WASI,WebAssembly System Interface。2019 年 3 月 Mozilla 公布它,Lin Clark 那篇文章寫得很直白:預設一個模組拿不到任何檔案描述子;這個想法來自 CloudABI 跟 Capsicum 那一路 capability 系統。WASI 到今天出過三版,照底座分是兩代:0.1 架在核心模組上,0.2 起架在 component model 上——2026 年 6 月的 0.3 也屬於這一代,多的是 async,之後講。今天不寫新程式,昨天那支讀檔的小程式一個字不改再編一次,兩代各一顆,並排看它們怎麼把同一條規矩寫進 import 表。

Clark 那篇:https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/

第一步:同一支程式,編兩次

Rust 替兩代各準備了一個編譯目標,同一份程式編兩次,得到兩顆:第一代 85,563 B,第二代 115,407 B,檔名分別帶 p1、p2。兩顆都照昨天的方式跑,先不給資料夾,再給:

$ wasmtime run notes-p1.wasm
cannot open /notes.txt: No such file or directory (os error 44)
$ wasmtime run --dir notes/data::/ notes-p1.wasm
buy milk
read the WASI spec
$ wasmtime run notes-p2.wasm
cannot open /notes.txt: No such file or directory (os error 44)
$ wasmtime run --dir notes/data::/ notes-p2.wasm
buy milk
read the WASI spec

一模一樣的行為,一模一樣的訊息。規矩沒有變過:POSIX 的做法是任何程式碼都能對任何路徑開口,開不開得成看跑它的人是誰;WASI 反過來,你手上得先有一個資料夾的描述子,才能在那個資料夾裡開檔,而描述子只有宿主給得了。瀏覽器裡那套規矩搬到伺服器上還成立,原因也在這:瀏覽器跑的是別人的程式碼,雲端多租戶跑的也是別人的程式碼,威脅模型幾乎一樣。兩代改的是怎麼說,不是規矩是什麼。

第二步:第一代怎麼說

把第一代那顆的 import 表印出來,只看 import 那幾行:

$ wasm-tools print notes-p1.wasm | grep '(import'
  (import "wasi_snapshot_preview1" "environ_get" (func $__imported_wasi_snapshot_preview1_environ_get (;0;) (type 5)))
  (import "wasi_snapshot_preview1" "environ_sizes_get" (func $__imported_wasi_snapshot_preview1_environ_sizes_get (;1;) (type 5)))
  (import "wasi_snapshot_preview1" "fd_close" (func $__imported_wasi_snapshot_preview1_fd_close (;2;) (type 6)))
  (import "wasi_snapshot_preview1" "fd_fdstat_get" (func $__imported_wasi_snapshot_preview1_fd_fdstat_get (;3;) (type 5)))
  (import "wasi_snapshot_preview1" "fd_filestat_get" (func $__imported_wasi_snapshot_preview1_fd_filestat_get (;4;) (type 5)))
  (import "wasi_snapshot_preview1" "fd_prestat_get" (func $__imported_wasi_snapshot_preview1_fd_prestat_get (;5;) (type 5)))
  (import "wasi_snapshot_preview1" "fd_prestat_dir_name" (func $__imported_wasi_snapshot_preview1_fd_prestat_dir_name (;6;) (type 4)))
  (import "wasi_snapshot_preview1" "fd_read" (func $__imported_wasi_snapshot_preview1_fd_read (;7;) (type 7)))
  (import "wasi_snapshot_preview1" "fd_write" (func $__imported_wasi_snapshot_preview1_fd_write (;8;) (type 7)))
  (import "wasi_snapshot_preview1" "path_open" (func $__imported_wasi_snapshot_preview1_path_open (;9;) (type 8)))
  (import "wasi_snapshot_preview1" "proc_exit" (func $__imported_wasi_snapshot_preview1_proc_exit (;10;) (type 1)))

十一支函式,全部掛在同一個模組名底下,跟 import 表那天那支印訊息的函式是同一副面孔:一個模組名、幾支函式、參數全是數字。開檔案那支叫 path_open,有九個參數:資料夾是一個整數,路徑是記憶體裡的位置加長度,旗標也是整數;開出來的檔案還是一個整數,寫回記憶體,回傳值只是錯誤碼。宿主給了哪些資料夾,這一代得一個一個問:先問這個號碼是不是一個資料夾,是的話再問它叫什麼,名字抄進記憶體。這是 2019 年的第一代,Preview 1:把 POSIX 那套檔案描述子直接搬過來,因為那時候 wasm 只有核心模組,模組跟外面能交換的只有數字跟一塊記憶體。看得出它用了檔案系統,看不出型別,看不出哪個參數是資料夾。

第三步:第二代怎麼說

把第二代那顆的介面印出來,昨天看過整張清單,今天只翻到管資料夾的那一個介面:

  interface preopens {
    use types.{descriptor};

    get-directories: func() -> list<tuple<descriptor, string>>;
  }

同一件事——宿主給了哪些資料夾——第二代是一支函式,一次回一張表,每一筆是一個 descriptor 配一個字串;描述子在這一代是 resource,有型別、有自己的方法,不再是整數。昨天把放筆記的資料夾遞進去,做的就是往這張表填一筆:描述子指到那個資料夾,字串是程式眼裡的路徑,也就是根目錄。十六個 import 分四包,名字自己會說話。這是 2024 年 1 月定案的 WASI 0.2,小組投票那天的公告寫著:WASI 從此正式架在 component model 上——昨天說那顆多了一層,就是這一層,之後講。代價也看得到:同一支程式,85,563 B 對 115,407 B。中間五年兩個節點各一句:2019 年 11 月 Mozilla、Fastly、Intel、Red Hat 成立 Bytecode Alliance,wasmtime 從那天起是它家的;2019 年 12 月 WebAssembly 核心規格成為 W3C 建議標準。權限來源標準化之後,同一顆檔在照標準實作的宿主上,被拒絕跟被放行的方式才會一樣。這個時代還證明了一件事:一個請求一個 instance,明天親手做。

結語

一句帶走:離開瀏覽器不是加了什麼,是換了宿主;WASI 把「權限從哪裡來」標準化——第一代用整數說,第二代用型別說,規矩沒變。


上一篇
Day 08|同一顆檔換宿主
系列文
Tool Use Is All You Need:30 天用 WebAssembly 試圖控制 Agent 的手腳9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言