ya_runtime_wasi/
entrypoint.rs

1use crate::{deploy::DeployFile, manifest::WasmImage, wasmtime_unit::Wasmtime};
2
3use std::path::{Component, Path, PathBuf};
4
5use anyhow::{bail, Result};
6use log::info;
7
8/// Validates the deployed image.
9///
10/// Takes path to the workdir as an argument.
11pub fn start(workdir: impl AsRef<Path>) -> Result<()> {
12    let workdir = workdir.as_ref();
13    let deploy_file = DeployFile::load(workdir)?;
14
15    info!(
16        "Validating deployed image {:?}.",
17        get_log_path(workdir, deploy_file.image_path())
18    );
19
20    let mut image = WasmImage::new(&deploy_file.image_path())?;
21    let mut wasmtime = create_wasmtime(workdir, &mut image, &deploy_file)?;
22
23    wasmtime.load_binaries(&mut image)?;
24
25    info!("Validation completed.");
26
27    Ok(())
28}
29
30/// Instantiates and executes the deployed image using Wasmtime runtime.
31///
32/// Takes path to the workdir, an entrypoint (name of WASI binary), and input arguments as arguments.
33///
34/// ## Example
35///
36/// ```rust,no_run
37/// use std::path::Path;
38/// use ya_runtime_wasi::run;
39///
40/// run(
41///     Path::new("workspace"),
42///     "hello",
43///     vec![
44///         "/workdir/input".into(),
45///         "/workdir/output".into(),
46///     ],
47/// ).unwrap();
48/// ```
49pub fn run(
50    workdir: impl AsRef<Path>,
51    entrypoint: impl AsRef<str>,
52    args: impl IntoIterator<Item = String>,
53) -> Result<()> {
54    let workdir = workdir.as_ref();
55    let deploy_file = DeployFile::load(workdir)?;
56
57    let mut image = WasmImage::new(&deploy_file.image_path())?;
58    let mut wasmtime = create_wasmtime(workdir, &mut image, &deploy_file)?;
59
60    info!(
61        "Running image: {:?}",
62        get_log_path(workdir, deploy_file.image_path())
63    );
64    info!("Running image: {}", deploy_file.image_path().display());
65
66    // Since wasmtime object doesn't live across binary executions,
67    // we must deploy image for the second time, what will load binary to wasmtime.
68    let entrypoint = image.find_entrypoint(entrypoint.as_ref())?;
69    wasmtime.load_binary(&mut image, &entrypoint)?;
70    wasmtime.run(entrypoint, args.into_iter().collect())?;
71
72    info!("Computations completed.");
73
74    Ok(())
75}
76
77pub(crate) struct DirectoryMount {
78    pub host: PathBuf,
79    pub guest: PathBuf,
80}
81
82fn create_wasmtime(
83    workdir: &Path,
84    _image: &mut WasmImage,
85    deploy: &DeployFile,
86) -> Result<Wasmtime> {
87    let mounts = deploy
88        .vols()
89        .map(|v| {
90            let host = workdir.join(&v.name);
91            let guest = PathBuf::from(&v.path);
92            validate_mount_path(&guest)?;
93            Ok(DirectoryMount { host, guest })
94        })
95        .collect::<anyhow::Result<Vec<_>>>()?;
96    Ok(Wasmtime::new(mounts))
97}
98
99fn validate_mount_path(path: &Path) -> Result<()> {
100    // Protect ExeUnit from directory traversal attack.
101    // Wasm can access only paths inside working directory.
102    let path = PathBuf::from(path);
103    for component in path.components() {
104        match component {
105            Component::Prefix { .. } => {
106                bail!("Expected unix path instead of [{}].", path.display())
107            }
108            Component::ParentDir { .. } => {
109                bail!("Path [{}] contains illegal '..' component.", path.display())
110            }
111            Component::CurDir => bail!("Path [{}] contains illegal '.' component.", path.display()),
112            _ => (),
113        }
114    }
115    Ok(())
116}
117
118fn get_log_path<'a>(workdir: &'a Path, path: &'a Path) -> &'a Path {
119    // try to return a relative path
120    path.strip_prefix(workdir)
121        .ok()
122        // use the file name if paths do not share a common prefix
123        .or_else(|| path.file_name().map(|file_name| Path::new(file_name)))
124        // in an unlikely situation return an empty path
125        .unwrap_or_else(|| Path::new(""))
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_mount_path_validation() {
134        assert_eq!(
135            validate_mount_path(&PathBuf::from("path/path/path")).is_err(),
136            false
137        );
138        assert_eq!(
139            validate_mount_path(&PathBuf::from("path/../path")).is_err(),
140            true
141        );
142        assert_eq!(
143            validate_mount_path(&PathBuf::from("./path/../path")).is_err(),
144            true
145        );
146        assert_eq!(
147            validate_mount_path(&PathBuf::from("./path/path")).is_err(),
148            true
149        );
150    }
151}