Rust のライブラリ
paludarium crate を使うと、Rust からゲストの プログラムを動かせます。使う型は、内部の crate からこの crate に再公開してあります。
sh
cargo add paludariumプログラムを動かす
rust
use std::sync::Arc;
use paludarium::{Config, ExitStatus, NativeHost, Session};
fn main() -> Result<(), paludarium::Error> {
let program = std::fs::read("hello").expect("read the guest program");
let config = Config::new("/hello", vec![b"hello".to_vec()])
.with_env("LANG", "C.UTF-8")
.with_file("/hello", program);
let session = Session::new(config, Arc::new(NativeHost::new()))?;
match session.run()? {
ExitStatus::Exited(code) => println!("exited with {code}"),
ExitStatus::Signaled(signal) => println!("ended by signal {signal}"),
other => println!("{other:?}"), // ExitStatus is #[non_exhaustive]
}
Ok(())
}Config::new(program, args)には、プログラムのゲストの中での絶対パスと、argv[0]を含むargvを渡します。Linux と同じく、パス、引数、環境変数は バイト列です。with_file(path, bytes)は、起動の前にゲストの仮想ファイルシステムへファイルを 置きます(権限は0755)。ほかの権限にするときはwith_file_modeを使います。with_env(key, value)は環境変数を足します。Configには、公開のフィールドもあります。program、args、env(newとwith_envが設定するもの)、filesとfile_modes(with_fileとwith_file_modeが足すもの。パスと中身、またはパスと権限の組です)、cwd(最初の作業ディレクトリをSome(path)で指定します。既定のNoneは/です)、mounts(ホストのディレクトリ。 下を参照)、tty(Some(TerminalInfo::new(columns, rows))にすると、ゲストの標準の ストリームがその大きさの端末になります。既定のNoneでは、端末はHostに任せます)、jit(使うのは WebAssembly 版だけです。ネイティブ版には JIT がありません)、limits、networkです。limitsはLimitsで、既定では上限がありません。Limits::default().with_tasks(n)は、ゲストが持てるタスクをn個までにします(タスクはスレッドと、回収されるまでの プロセスで、cgroup のpids.maxと同じ数え方です。超えるcloneやforkはEAGAINで失敗します)。with_open_files(n)は、Linux のfs.nr_openと同じようにRLIMIT_NOFILEの天井をnにするので、それ以上ファイルを開くとEMFILEになります。with_memory(n)は、cgroup のmemory.maxと同じように、ゲストが使えるメモリをnバイトまでにします。超える書き込みをしたプロセスは、OOM killer と同じようにSIGKILLで終わり、収まらないexecveはENOMEMで失敗します(何を 数えるかはフィールドのドキュメントにあります)。これらはフィールドのtasks、open_files、memoryを設定し、Noneは上限なしです。networkの既定はNoneで、ゲストはネットワークを使えません。with_network(policy)は、これをNetworkPolicyにします。NetworkPolicyはNetworkPolicy::new()から作り、行き先ごとにallow(entry)を呼びます (github.com:443、*.example.org、127.0.0.1、[::1]:8080)。読めない項目には、allowがInvalidRuleを返します。ゲストは、方針が許す行き先へHostを通して TCP で 接続し、許した名前を引けます(ネットワーク)。Session::newは、何かを動かす前に設定を確かめます。run()はゲストを最後まで 動かします。NativeHostはHostトレイトのネイティブ版の実装で、ゲストの 標準入出力をホストのプロセスにつなぎます。ExitStatus::command_exit_code()は、コマンドが返す終了コードです。終了コードか、 128 + シグナルの番号になります。
ホストのディレクトリを見せる
rust
use paludarium::Mount;
let mut config = Config::new("/work/app", vec![b"app".to_vec()]);
config.mounts.push(Mount { host: "/srv/data".into(), guest: b"/work".to_vec() });ゲストに見えるのは、マウントしたディレクトリだけです。パスはマウントしたディレクトリの 中で解決され、.. でもシンボリックリンクでも、その外には出られません。
ゲストを止める、シグナルを送る
Session は、別のスレッドと共有できます。
kill()はゲストに止まるよう求めます。インタプリタが次に確かめたとき (DEFAULT_BUDGET= 100,000 命令ごと)に、SIGKILLで終わります。signal(number)は、ゲストのプロセスに Linux のシグナルを送ります。ゲストの外から 送るので、PID 名前空間の外の送り主と同じく、si_codeはSI_USER、si_pidは 0 になります。is_stopped()は、ゲストがSIGSTOPなどのジョブ制御で止まっているかどうかを 返します。- 動かした後、
unimplemented_syscalls()は、ゲストが呼んでENOSYSが返った システムコールを小さい順に返します(コマンドの--report-enosysが表示するもの)。termination_detail()は、CPU の例外でゲストが終わったとき、その止まり方 (ExitReason)を返します。
エラー
run() は、ゲストが終わったときやシグナルで止まったときは Ok(ExitStatus) を返し、 エミュレータの問題のときだけ Err(Error) を返します。Error::kind は次のどれかです。
ErrorKind | 意味 |
|---|---|
Unimplemented | paludarium がまだ対応していないものを、ゲストが使った |
Internal | エミュレータの中の食い違い |
InvalidProgram | プログラムを読み込めない |
Host | ホストの失敗(I/O のエラー、乱数が得られないなど) |
エラーは message を持ち、わかっていれば、命令のアドレス(rip。GuestAddr です)、 命令のバイト列(bytes)、syscall の番号(syscall)も持っています。paludarium が実装していない syscall はエラーにはなりません。古いカーネルと 同じように、ゲストに ENOSYS が返ります。