moon-wasi

Capability-aware WASI Preview 1 utilities for MoonBit

wasi
wasm
filesystem
capability
cli
moon add Ag108/moon-wasi@0.1.0
Download zip
Author
Version
0.1.0
License
Apache-2.0
Last updated
16 days ago
Downloads
3

Dependencies

README

#MoonBit WASI 增强库

一个面向 MoonBit 命令行程序的 capability-aware WASI Preview 1 增强库,提供安全路径、 可靠文件系统操作、原子写入和常用系统能力。

Ag108/moon-wasi 建立在 peter-jerry-ye/wasi 的底层 ABI 绑定之上,目标后端为 MoonBit wasm,当前版本为 0.1.0。本项目是原创增强库,不是其他语言项目的移植。

#特性

  • 🛡️ 安全路径GuestPath 规范化 guest 相对路径,拒绝绝对路径、NUL、反斜杠和 越过 capability 根的父级跳转
  • 📁 增强文件系统:支持完整读写、追加、seek/tell、metadata、目录遍历、复制、 rename、硬链接和符号链接
  • ⚛️ 原子写入:使用同目录 exclusive 临时文件、完整同步和 rename,失败时自动清理
  • 🔒 Capability 模型:只访问 WASI 运行时显式预开放的目录,不接触或推导宿主路径
  • 🧭 确定性遍历:分页读取大目录,并以稳定词法顺序返回目录项和深度优先遍历结果
  • 🧰 系统能力:封装参数、环境变量、stdio、时钟、睡眠、安全随机数和 descriptor poll
  • 🧩 上下文错误:统一保留操作名、guest 路径和原始 WASI Errno
  • 端到端验证:同时提供 MoonBit 单元测试和真实 Wasmtime 集成测试

#Demo

诊断程序可以显示 WASI 宿主授予的进程和系统能力:

MoonBit WASI diagnostics arguments: ["main.wasm"] environment entries: 0 preopens: [{ guest_path: ".", fd: 3 }] monotonic nanoseconds: 594900 random sample: <Bytes: [...]>

原子写入与目录树示例:

wrote target/moon-wasi-example/demo.txt: enabled=true target/moon-wasi-example demo.txt

#快速开始

#安装依赖

确保已经安装:

  • MoonBit 工具链
  • 支持 wasi_snapshot_preview1 的运行时,示例使用 Wasmtime

从源码开发本仓库:

moon install moon check --deny-warn

从 mooncakes.io 安装:

moon add Ag108/moon-wasi

#配置包

在应用包的 moon.pkg 中按需导入:

import {
"Ag108/moon-wasi/fs" @wasi_fs,
"Ag108/moon-wasi/path",
"Ag108/moon-wasi/sys" @wasi_sys,
"moonbitlang/core/debug",
}

pkgtype(kind: "executable")
supported_targets = "wasm"

#运行项目

构建并运行原子配置示例:

moon build --target wasm --release examples/atomic-config wasmtime run --dir .::. \ _build/wasm/release/build/examples/atomic-config/atomic-config.wasm \ target/moon-wasi-example/config.txt "enabled=true"

运行系统能力诊断:

moon build --target wasm --release cmd/main wasmtime run --dir .::. _build/wasm/release/build/cmd/main/main.wasm

输出确定性目录树:

moon build --target wasm --release examples/tree wasmtime run --dir .::. \ _build/wasm/release/build/examples/tree/tree.wasm target

#使用方法

  1. 使用 GuestPath::new 验证来自参数、环境变量或配置的 guest 路径
  2. 使用 create_dir_all 创建父目录
  3. 调用 read_textwrite_textcopy_treeatomic_write_text 等高层 API
  4. 捕获 WasiError,记录操作、guest 路径和底层 errno
  5. 启动 wasm 时只预开放应用实际需要的宿主目录

下面的程序安全地创建父目录并原子更新 UTF-8 配置:

///|
fn main {
try {
let path = @path.GuestPath::new("state/config.txt")
match path.parent() {
Some(parent) => @wasi_fs.create_dir_all(parent)
None => ()
}
@wasi_fs.atomic_write_text(path, "enabled=true\n")
@wasi_sys.print(@wasi_fs.read_text(path))
} catch {
error => {
@wasi_sys.eprint("failed: \{@debug.to_string(error)}\n") catch {
_ => ()
}
@wasi_sys.exit(1)
}
}
}

#技术架构

#核心依赖库

库名版本用途
peter-jerry-ye/wasi0.25.0提供 WASI Preview 1 ABI、类型和 syscall 绑定

本项目不重复维护 ABI 常量和内联 Wasm,而是在底层绑定上实现应用级安全语义。

#包结构

用途
Ag108/moon-wasiWasiErrorOperation 和通用错误上下文
Ag108/moon-wasi/pathGuestPath 验证、规范化、join、parent 和包含关系
Ag108/moon-wasi/fs文件、目录、metadata、遍历、复制、链接和原子写入
Ag108/moon-wasi/sys进程信息、stdio、时钟、随机数和 poll

#实现原理

  1. 路径验证:在进入 capability 敏感 API 前完成纯词法规范化和越界检查
  2. Capability 解析:将 guest 路径解析为预开放 descriptor 与相对路径
  3. 可靠 I/O:循环处理部分读写,并在高层操作的成功和失败路径关闭自建 descriptor
  4. 目录遍历:通过 fd_readdir cookie 分页读取,再按名称稳定排序
  5. 复制保护:使用 device/inode 识别硬链接别名,避免把文件复制到自身导致截断
  6. 原子发布:先写入 exclusive 临时文件并同步,再 rename;无覆盖创建使用硬链接
  7. 系统封装:将 process、clock、random 和 poll_oneoff 转换为 MoonBit 类型与错误

#错误和资源所有权

宿主错误使用 WasiFailure(operation, path, errno),调用前校验错误使用 InvalidInput(operation, reason)message() 可生成适合命令行日志的说明。

fs.open 返回拥有 descriptor 的 File,调用者必须恰好调用一次 close() read_textwrite_textcopy_file 等高层 API 会自动关闭它们创建的 descriptor。

exists 是将所有不可访问状态折叠为 false 的便利接口;需要区分“不存在”和宿主 错误时应使用 try_exists

#配置

#目录 Capability

文件系统能力由运行时授予,而不是在 MoonBit 代码中申请:

# 将当前宿主目录映射为 guest 的相对根目录“.” wasmtime run --dir .::. app.wasm # 同时传入环境变量 wasmtime run --dir .::. --env APP_MODE=production app.wasm

建议只预开放应用需要的最小目录。GuestPath 只表示相对 guest 路径,因此 0.1 版本建议 将主要 preopen 映射为 .

#测试运行时

测试脚本默认从 PATH 查找 wasmtime。需要指定其他位置时:

MOON_WASI_WASMTIME=/path/to/wasmtime bash scripts/test-wasi.sh

#开发

#项目结构

. ├── cmd/main/ # WASI 系统能力诊断程序 ├── path/ # 安全 guest 路径 ├── fs/ # 文件系统增强层 ├── sys/ # 进程与系统能力 ├── integration/fs/ # Wasmtime 文件系统集成测试 ├── integration/sys/ # Wasmtime 系统能力集成测试 ├── examples/tree/ # 确定性目录树示例 ├── examples/atomic-config/ # 原子配置写入示例 ├── docs/ # 架构与兼容性说明 ├── scripts/test-wasi.sh # 本地完整验收入口 ├── moon.mod # 模块元数据和依赖 └── README.mbt.md

#代码特点

  • 块式组织:MoonBit block 使用 ///| 分隔,方便独立审查和重构
  • 类型安全:公开文件系统边界接受 GuestPath,而不是未经验证的宿主字符串
  • 稳定输出:目录结果不依赖宿主 readdir 顺序
  • 失败清理:descriptor 和原子临时文件覆盖成功与异常退出路径
  • 显式策略:目录复制不隐式跟随符号链接,避免循环和 capability 意外
  • 接口审计:提交生成的 .mbti 文件,用于追踪所有公开 API 变化

#测试

运行完整验收:

bash scripts/test-wasi.sh

脚本会运行接口生成、格式化、严格检查、单元测试、全部 release 构建,以及 integration/fsintegration/sys 两个真实 Wasmtime 集成程序。CI 使用相同命令, 但拆分为独立步骤,以便直接定位失败环节。

也可以分开运行:

moon info moon fmt moon check --deny-warn moon test --target wasm moon package --frozen --list

MoonBit wasm 单元测试宿主没有实例化全部路径类 WASI import,因此纯逻辑由 moon test 验证,真实文件系统调用由独立 wasm 集成程序验证。

#兼容性

项目0.1 支持状态
MoonBit wasm 后端支持
WASI Preview 1 / wasi_snapshot_preview1支持
Wasmtime 47已验证
Windows + Wasmtime已验证;符号链接受宿主权限影响
MoonBit jswasm-gcnative不支持
WASI Preview 2/3 component model不支持

已知限制:

  • 不提供网络、通用异步运行时、Preview 2/3 或宿主路径访问
  • atomic_write 无法通过 Preview 1 可移植地同步父目录,掉电语义受宿主文件系统影响
  • atomic_write(..., replace=false) 依赖宿主授予 path_link capability
  • copy_tree 不会隐式复制或跟随符号链接
  • Windows 创建符号链接可能需要开发者模式、管理员权限或额外宿主支持

#项目文档

#许可证

Apache-2.0 License,完整文本见 LICENSE

#贡献

欢迎提交 Issue 和 Pull Request。提交前请运行 bash scripts/test-wasi.sh,并阅读 贡献指南

#
WasiError

pub(all) suberror WasiError {
WasiFailure(Operation, String?,
Errno
)
InvalidInput(Operation, String)
} derive(Eq,
Debug
)

A contextual error raised by the high-level API.

#
WasiError::errno

Return the original WASI errno, when the failure came from the host.

#
WasiError::message

fn WasiError::message(self : WasiError) -> String

Format a concise error message suitable for logs and command-line tools.

#
WasiError::operation

fn WasiError::operation(self : WasiError) -> Operation

Return the operation that failed.

#
WasiError::path

fn WasiError::path(self : WasiError) -> String?

Return the path associated with an error, when one exists.

#
Operation

pub(all) enum Operation {
Arguments
Environment
Clock
Random
Poll
Open
Close
Read
Write
Seek
Sync
Metadata
ReadDirectory
CreateDirectory
Remove
Rename
Link
Copy
AtomicWrite
} derive(Eq,
Debug
)

A high-level operation that can fail at a WASI boundary.

#
Operation::name

fn Operation::name(self : Operation) -> String

Return the stable, human-readable name of an operation.

#
wasi_error

fn wasi_error(operation : Operation, errno :
Errno
, path? : String) -> WasiError

Construct an error while retaining the original WASI errno.