Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/src/common.rs
3051 views
1
//! Common functionality shared between command implementations.
2
3
use clap::Parser;
4
use std::net::TcpListener;
5
use std::{fs::File, path::Path, time::Duration};
6
use wasmtime::{
7
Engine, Module, Precompiled, Result, StoreLimits, StoreLimitsBuilder, bail,
8
error::Context as _, format_err,
9
};
10
use wasmtime_cli_flags::{CommonOptions, opt::WasmtimeOptionValue};
11
use wasmtime_wasi::WasiCtxBuilder;
12
13
#[cfg(feature = "component-model")]
14
use wasmtime::component::Component;
15
16
/// Whether or not WASIp3 is enabled by default.
17
///
18
/// Currently this is disabled (the `&& false`), but that'll get removed in the
19
/// future.
20
pub const P3_DEFAULT: bool = cfg!(feature = "component-model-async") && false;
21
22
pub enum RunTarget {
23
Core(Module),
24
25
#[cfg(feature = "component-model")]
26
Component(Component),
27
}
28
29
impl RunTarget {
30
pub fn unwrap_core(&self) -> &Module {
31
match self {
32
RunTarget::Core(module) => module,
33
#[cfg(feature = "component-model")]
34
RunTarget::Component(_) => panic!("expected a core wasm module, not a component"),
35
}
36
}
37
38
#[cfg(feature = "component-model")]
39
pub fn unwrap_component(&self) -> &Component {
40
match self {
41
RunTarget::Component(c) => c,
42
RunTarget::Core(_) => panic!("expected a component, not a core wasm module"),
43
}
44
}
45
}
46
47
/// Common command line arguments for run commands.
48
#[derive(Parser)]
49
pub struct RunCommon {
50
#[command(flatten)]
51
pub common: CommonOptions,
52
53
/// Allow executing precompiled WebAssembly modules as `*.cwasm` files.
54
///
55
/// Note that this option is not safe to pass if the module being passed in
56
/// is arbitrary user input. Only `wasmtime`-precompiled modules generated
57
/// via the `wasmtime compile` command or equivalent should be passed as an
58
/// argument with this option specified.
59
#[arg(long = "allow-precompiled")]
60
pub allow_precompiled: bool,
61
62
/// Profiling strategy (valid options are: perfmap, jitdump, vtune, guest)
63
///
64
/// The perfmap, jitdump, and vtune profiling strategies integrate Wasmtime
65
/// with external profilers such as `perf`. The guest profiling strategy
66
/// enables in-process sampling and will write the captured profile to
67
/// `wasmtime-guest-profile.json` by default which can be viewed at
68
/// https://profiler.firefox.com/.
69
///
70
/// The `guest` option can be additionally configured as:
71
///
72
/// --profile=guest[,path[,interval]]
73
///
74
/// where `path` is where to write the profile and `interval` is the
75
/// duration between samples. When used with `--wasm-timeout` the timeout
76
/// will be rounded up to the nearest multiple of this interval.
77
#[arg(
78
long,
79
value_name = "STRATEGY",
80
value_parser = Profile::parse,
81
)]
82
pub profile: Option<Profile>,
83
84
/// Grant access of a host directory to a guest.
85
///
86
/// If specified as just `HOST_DIR` then the same directory name on the
87
/// host is made available within the guest. If specified as `HOST::GUEST`
88
/// then the `HOST` directory is opened and made available as the name
89
/// `GUEST` in the guest.
90
#[arg(long = "dir", value_name = "HOST_DIR[::GUEST_DIR]", value_parser = parse_dirs)]
91
pub dirs: Vec<(String, String)>,
92
93
/// Pass an environment variable to the program.
94
///
95
/// The `--env FOO=BAR` form will set the environment variable named `FOO`
96
/// to the value `BAR` for the guest program using WASI. The `--env FOO`
97
/// form will set the environment variable named `FOO` to the same value it
98
/// has in the calling process for the guest, or in other words it will
99
/// cause the environment variable `FOO` to be inherited.
100
#[arg(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)]
101
pub vars: Vec<(String, Option<String>)>,
102
}
103
104
fn parse_env_var(s: &str) -> Result<(String, Option<String>)> {
105
let mut parts = s.splitn(2, '=');
106
Ok((
107
parts.next().unwrap().to_string(),
108
parts.next().map(|s| s.to_string()),
109
))
110
}
111
112
fn parse_dirs(s: &str) -> Result<(String, String)> {
113
let mut parts = s.split("::");
114
let host = parts.next().unwrap();
115
let guest = match parts.next() {
116
Some(guest) => guest,
117
None => host,
118
};
119
Ok((host.into(), guest.into()))
120
}
121
122
impl RunCommon {
123
pub fn store_limits(&self) -> StoreLimits {
124
let mut limits = StoreLimitsBuilder::new();
125
if let Some(max) = self.common.wasm.max_memory_size {
126
limits = limits.memory_size(max);
127
}
128
if let Some(max) = self.common.wasm.max_table_elements {
129
limits = limits.table_elements(max);
130
}
131
if let Some(max) = self.common.wasm.max_instances {
132
limits = limits.instances(max);
133
}
134
if let Some(max) = self.common.wasm.max_tables {
135
limits = limits.tables(max);
136
}
137
if let Some(max) = self.common.wasm.max_memories {
138
limits = limits.memories(max);
139
}
140
if let Some(enable) = self.common.wasm.trap_on_grow_failure {
141
limits = limits.trap_on_grow_failure(enable);
142
}
143
144
limits.build()
145
}
146
147
pub fn ensure_allow_precompiled(&self) -> Result<()> {
148
if self.allow_precompiled {
149
Ok(())
150
} else {
151
bail!("running a precompiled module requires the `--allow-precompiled` flag")
152
}
153
}
154
155
#[cfg(feature = "component-model")]
156
fn ensure_allow_components(&self) -> Result<()> {
157
if self.common.wasm.component_model == Some(false) {
158
bail!("cannot execute a component without `--wasm component-model`");
159
}
160
161
Ok(())
162
}
163
164
pub fn load_module(&self, engine: &Engine, path: &Path) -> Result<RunTarget> {
165
let path = match path.to_str() {
166
#[cfg(unix)]
167
Some("-") => "/dev/stdin".as_ref(),
168
_ => path,
169
};
170
let file =
171
File::open(path).with_context(|| format!("failed to open wasm module {path:?}"))?;
172
173
// First attempt to load the module as an mmap. If this succeeds then
174
// detection can be done with the contents of the mmap and if a
175
// precompiled module is detected then `deserialize_file` can be used
176
// which is a slightly more optimal version than `deserialize` since we
177
// can leave most of the bytes on disk until they're referenced.
178
//
179
// If the mmap fails, for example if stdin is a pipe, then fall back to
180
// `std::fs::read` to load the contents. At that point precompiled
181
// modules must go through the `deserialize` functions.
182
//
183
// Note that this has the unfortunate side effect for precompiled
184
// modules on disk that they're opened once to detect what they are and
185
// then again internally in Wasmtime as part of the `deserialize_file`
186
// API. Currently there's no way to pass the `MmapVec` here through to
187
// Wasmtime itself (that'd require making `MmapVec` a public type, both
188
// which isn't ready to happen at this time). It's hoped though that
189
// opening a file twice isn't too bad in the grand scheme of things with
190
// respect to the CLI.
191
match wasmtime::_internal::MmapVec::from_file(file) {
192
Ok(map) => self.load_module_contents(
193
engine,
194
path,
195
&map,
196
|| unsafe { Module::deserialize_file(engine, path) },
197
#[cfg(feature = "component-model")]
198
|| unsafe { Component::deserialize_file(engine, path) },
199
),
200
Err(_) => {
201
let bytes = std::fs::read(path)
202
.with_context(|| format!("failed to read file: {}", path.display()))?;
203
self.load_module_contents(
204
engine,
205
path,
206
&bytes,
207
|| unsafe { Module::deserialize(engine, &bytes) },
208
#[cfg(feature = "component-model")]
209
|| unsafe { Component::deserialize(engine, &bytes) },
210
)
211
}
212
}
213
}
214
215
pub fn load_module_contents(
216
&self,
217
engine: &Engine,
218
path: &Path,
219
bytes: &[u8],
220
deserialize_module: impl FnOnce() -> Result<Module>,
221
#[cfg(feature = "component-model")] deserialize_component: impl FnOnce() -> Result<Component>,
222
) -> Result<RunTarget> {
223
Ok(match Engine::detect_precompiled(bytes) {
224
Some(Precompiled::Module) => {
225
self.ensure_allow_precompiled()?;
226
RunTarget::Core(deserialize_module()?)
227
}
228
#[cfg(feature = "component-model")]
229
Some(Precompiled::Component) => {
230
self.ensure_allow_precompiled()?;
231
self.ensure_allow_components()?;
232
RunTarget::Component(deserialize_component()?)
233
}
234
#[cfg(not(feature = "component-model"))]
235
Some(Precompiled::Component) => {
236
bail!("support for components was not enabled at compile time");
237
}
238
#[cfg(any(feature = "cranelift", feature = "winch"))]
239
None => {
240
let mut code = wasmtime::CodeBuilder::new(engine);
241
code.wasm_binary_or_text(bytes, Some(path))?;
242
match code.hint() {
243
Some(wasmtime::CodeHint::Component) => {
244
#[cfg(feature = "component-model")]
245
{
246
self.ensure_allow_components()?;
247
RunTarget::Component(code.compile_component()?)
248
}
249
#[cfg(not(feature = "component-model"))]
250
{
251
bail!("support for components was not enabled at compile time");
252
}
253
}
254
Some(wasmtime::CodeHint::Module) | None => {
255
RunTarget::Core(code.compile_module()?)
256
}
257
}
258
}
259
260
#[cfg(not(any(feature = "cranelift", feature = "winch")))]
261
None => {
262
let _ = (path, engine);
263
bail!("support for compiling modules was disabled at compile time");
264
}
265
})
266
}
267
268
pub fn configure_wasip2(&self, builder: &mut WasiCtxBuilder) -> Result<()> {
269
// It's ok to block the current thread since we're the only thread in
270
// the program as the CLI. This helps improve the performance of some
271
// blocking operations in WASI, for example, by skipping the
272
// back-and-forth between sync and async.
273
//
274
// However, do not set this if a timeout is configured, as that would
275
// cause the timeout to be ignored if the guest does, for example,
276
// something like `sleep(FOREVER)`.
277
builder.allow_blocking_current_thread(self.common.wasm.timeout.is_none());
278
279
if self.common.wasi.inherit_env == Some(true) {
280
for (k, v) in std::env::vars() {
281
builder.env(&k, &v);
282
}
283
}
284
for (key, value) in self.vars.iter() {
285
let value = match value {
286
Some(value) => value.clone(),
287
None => match std::env::var_os(key) {
288
Some(val) => val
289
.into_string()
290
.map_err(|_| format_err!("environment variable `{key}` not valid utf-8"))?,
291
None => {
292
// leave the env var un-set in the guest
293
continue;
294
}
295
},
296
};
297
builder.env(key, &value);
298
}
299
300
for (host, guest) in self.dirs.iter() {
301
builder.preopened_dir(
302
host,
303
guest,
304
wasmtime_wasi::DirPerms::all(),
305
wasmtime_wasi::FilePerms::all(),
306
)?;
307
}
308
309
if self.common.wasi.listenfd == Some(true) {
310
bail!("components do not support --listenfd");
311
}
312
for _ in self.compute_preopen_sockets()? {
313
bail!("components do not support --tcplisten");
314
}
315
316
if self.common.wasi.inherit_network == Some(true) {
317
builder.inherit_network();
318
}
319
if let Some(enable) = self.common.wasi.allow_ip_name_lookup {
320
builder.allow_ip_name_lookup(enable);
321
}
322
if let Some(enable) = self.common.wasi.tcp {
323
builder.allow_tcp(enable);
324
}
325
if let Some(enable) = self.common.wasi.udp {
326
builder.allow_udp(enable);
327
}
328
329
Ok(())
330
}
331
332
pub fn compute_preopen_sockets(&self) -> Result<Vec<TcpListener>> {
333
let mut listeners = vec![];
334
335
for address in &self.common.wasi.tcplisten {
336
let stdlistener = std::net::TcpListener::bind(address)
337
.with_context(|| format!("failed to bind to address '{address}'"))?;
338
339
let _ = stdlistener.set_nonblocking(true)?;
340
341
listeners.push(stdlistener)
342
}
343
Ok(listeners)
344
}
345
346
pub fn validate_p3_option(&self) -> Result<()> {
347
let p3 = self.common.wasi.p3.unwrap_or(P3_DEFAULT);
348
if p3 && !cfg!(feature = "component-model-async") {
349
bail!("support for WASIp3 disabled at compile time");
350
}
351
Ok(())
352
}
353
354
pub fn validate_cli_enabled(&self) -> Result<Option<bool>> {
355
let mut cli = self.common.wasi.cli;
356
357
// Accept -Scommon as a deprecated alias for -Scli.
358
if let Some(common) = self.common.wasi.common {
359
if cli.is_some() {
360
bail!(
361
"The -Scommon option should not be use with -Scli as it is a deprecated alias"
362
);
363
} else {
364
// In the future, we may add a warning here to tell users to use
365
// `-S cli` instead of `-S common`.
366
cli = Some(common);
367
}
368
}
369
370
Ok(cli)
371
}
372
373
/// Adds `wasmtime-wasi` interfaces (dubbed "-Scli" in the flags to the
374
/// `wasmtime` command) to the `linker` provided.
375
///
376
/// This will handle adding various WASI standard versions to the linker
377
/// internally.
378
#[cfg(feature = "component-model")]
379
pub fn add_wasmtime_wasi_to_linker<T>(
380
&self,
381
linker: &mut wasmtime::component::Linker<T>,
382
) -> Result<()>
383
where
384
T: wasmtime_wasi::WasiView,
385
{
386
let mut p2_options = wasmtime_wasi::p2::bindings::LinkOptions::default();
387
p2_options.cli_exit_with_code(self.common.wasi.cli_exit_with_code.unwrap_or(false));
388
p2_options.network_error_code(self.common.wasi.network_error_code.unwrap_or(false));
389
wasmtime_wasi::p2::add_to_linker_with_options_async(linker, &p2_options)?;
390
391
#[cfg(feature = "component-model-async")]
392
if self.common.wasi.p3.unwrap_or(P3_DEFAULT) {
393
let mut p3_options = wasmtime_wasi::p3::bindings::LinkOptions::default();
394
p3_options.cli_exit_with_code(self.common.wasi.cli_exit_with_code.unwrap_or(false));
395
wasmtime_wasi::p3::add_to_linker_with_options(linker, &p3_options)
396
.context("failed to link `wasi:[email protected]`")?;
397
}
398
399
Ok(())
400
}
401
}
402
403
#[derive(Clone, PartialEq)]
404
pub enum Profile {
405
Native(wasmtime::ProfilingStrategy),
406
Guest { path: String, interval: Duration },
407
}
408
409
impl Profile {
410
/// Parse the `profile` argument to either the `run` or `serve` commands.
411
pub fn parse(s: &str) -> Result<Profile> {
412
let parts = s.split(',').collect::<Vec<_>>();
413
match &parts[..] {
414
["perfmap"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::PerfMap)),
415
["jitdump"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::JitDump)),
416
["vtune"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::VTune)),
417
["pulley"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::Pulley)),
418
["guest"] => Ok(Profile::Guest {
419
path: "wasmtime-guest-profile.json".to_string(),
420
interval: Duration::from_millis(10),
421
}),
422
["guest", path] => Ok(Profile::Guest {
423
path: path.to_string(),
424
interval: Duration::from_millis(10),
425
}),
426
["guest", path, dur] => Ok(Profile::Guest {
427
path: path.to_string(),
428
interval: WasmtimeOptionValue::parse(Some(dur))?,
429
}),
430
_ => bail!("unknown profiling strategy: {s}"),
431
}
432
}
433
}
434
435
#[derive(Default, Clone)]
436
#[cfg(all(feature = "wasi-http", feature = "component-model-async"))]
437
pub struct DefaultP3Ctx;
438
#[cfg(all(feature = "wasi-http", feature = "component-model-async"))]
439
impl wasmtime_wasi_http::p3::WasiHttpCtx for DefaultP3Ctx {}
440
441