wasmer_package/
utils.rs

1#![allow(
2    clippy::result_large_err,
3    reason = "WasmerPackageError is large, but not often used"
4)]
5
6use bytes::{Buf, Bytes};
7use std::{
8    fs::File,
9    io::{BufRead, BufReader, Read, Seek},
10    path::Path,
11};
12use wasmer_types::Features;
13use webc::{Container, ContainerError, Version};
14
15use crate::package::{Package, WasmerPackageError};
16
17/// Check if something looks like a `*.tar.gz` file.
18fn is_tarball(mut file: impl Read + Seek) -> bool {
19    /// Magic bytes for a `*.tar.gz` file according to
20    /// [Wikipedia](https://en.wikipedia.org/wiki/List_of_file_signatures).
21    const TAR_GZ_MAGIC_BYTES: [u8; 2] = [0x1F, 0x8B];
22
23    let mut buffer = [0_u8; 2];
24    let result = match file.read_exact(&mut buffer) {
25        Ok(_) => buffer == TAR_GZ_MAGIC_BYTES,
26        Err(_) => false,
27    };
28
29    let _ = file.rewind();
30
31    result
32}
33
34pub fn from_disk(path: impl AsRef<Path>) -> Result<Container, WasmerPackageError> {
35    let path = path.as_ref();
36
37    if path.is_dir() {
38        return parse_dir(path);
39    }
40
41    let mut f = File::open(path).map_err(|error| ContainerError::Open {
42        error,
43        path: path.to_path_buf(),
44    })?;
45
46    if is_tarball(&mut f) {
47        return parse_tarball(BufReader::new(f));
48    }
49
50    match webc::detect(&mut f) {
51        Ok(Version::V1) => parse_v1_mmap(f).map_err(Into::into),
52        Ok(Version::V2) => parse_v2_mmap(f).map_err(Into::into),
53        Ok(Version::V3) => parse_v3_mmap(f).map_err(Into::into),
54        Ok(other) => {
55            // fall back to the allocating generic version
56            let mut buffer = Vec::new();
57            f.rewind()
58                .and_then(|_| f.read_to_end(&mut buffer))
59                .map_err(|error| ContainerError::Read {
60                    path: path.to_path_buf(),
61                    error,
62                })?;
63
64            Container::from_bytes_and_version(buffer.into(), other).map_err(Into::into)
65        }
66        Err(e) => Err(ContainerError::Detect(e).into()),
67    }
68}
69
70/// Check if the data looks like a webc.
71pub fn is_container(bytes: &[u8]) -> bool {
72    is_tarball(std::io::Cursor::new(bytes)) || webc::detect(bytes).is_ok()
73}
74
75pub fn from_bytes(bytes: impl Into<Bytes>) -> Result<Container, WasmerPackageError> {
76    let bytes: Bytes = bytes.into();
77
78    if is_tarball(std::io::Cursor::new(&bytes)) {
79        return parse_tarball(bytes.reader());
80    }
81
82    let version = webc::detect(bytes.as_ref())?;
83    Container::from_bytes_and_version(bytes, version).map_err(Into::into)
84}
85
86#[allow(clippy::result_large_err)]
87fn parse_tarball(reader: impl BufRead) -> Result<Container, WasmerPackageError> {
88    let pkg = Package::from_tarball(reader)?;
89    Ok(Container::new(pkg))
90}
91
92#[allow(clippy::result_large_err)]
93fn parse_dir(path: &Path) -> Result<Container, WasmerPackageError> {
94    let wasmer_toml = path.join("wasmer.toml");
95    let pkg = Package::from_manifest(wasmer_toml)?;
96    Ok(Container::new(pkg))
97}
98
99#[allow(clippy::result_large_err)]
100fn parse_v1_mmap(f: File) -> Result<Container, ContainerError> {
101    // We need to explicitly use WebcMmap to get a memory-mapped
102    // parser
103    let options = webc::v1::ParseOptions::default();
104    let webc = webc::v1::WebCMmap::from_file(f, &options)?;
105    Ok(Container::new(webc))
106}
107
108#[allow(clippy::result_large_err)]
109fn parse_v2_mmap(f: File) -> Result<Container, ContainerError> {
110    // Note: OwnedReader::from_file() will automatically try to
111    // use a memory-mapped file when possible.
112    let webc = webc::v2::read::OwnedReader::from_file(f)?;
113    Ok(Container::new(webc))
114}
115
116#[allow(clippy::result_large_err)]
117fn parse_v3_mmap(f: File) -> Result<Container, ContainerError> {
118    // Note: OwnedReader::from_file() will automatically try to
119    // use a memory-mapped file when possible.
120    let webc = webc::v3::read::OwnedReader::from_file(f)?;
121    Ok(Container::new(webc))
122}
123
124/// Convert a `Features` object to a list of WebAssembly feature strings
125/// that can be used in annotations.
126///
127/// This maps each enabled feature to its corresponding string identifier
128/// used in the WebAssembly ecosystem.
129pub fn features_to_wasm_annotations(features: &Features) -> Vec<String> {
130    let mut feature_strings = Vec::new();
131
132    if features.simd {
133        feature_strings.push("simd".to_string());
134    }
135    if features.bulk_memory {
136        feature_strings.push("bulk-memory".to_string());
137    }
138    if features.reference_types {
139        feature_strings.push("reference-types".to_string());
140    }
141    if features.multi_value {
142        feature_strings.push("multi-value".to_string());
143    }
144    if features.threads {
145        feature_strings.push("threads".to_string());
146    }
147    if features.exceptions {
148        feature_strings.push("exception-handling".to_string());
149    }
150    if features.memory64 {
151        feature_strings.push("memory64".to_string());
152    }
153    // Note: We don't currently include tail_call, module_linking, multi_memory,
154    // relaxed_simd, or extended_const in the feature strings
155
156    feature_strings
157}
158
159/// Create a `Features` object from a list of WebAssembly feature strings.
160///
161/// This is the inverse of `features_to_wasm_annotations`, mapping string identifiers
162/// back to Features settings.
163pub fn wasm_annotations_to_features(feature_strings: &[String]) -> Features {
164    let mut features = Features::default();
165
166    // Initialize with default values
167    features
168        .simd(false)
169        .bulk_memory(false)
170        .reference_types(false)
171        .multi_value(false)
172        .threads(false)
173        .exceptions(false)
174        .memory64(false);
175
176    // Set features based on the string values
177    for feature in feature_strings {
178        match feature.as_str() {
179            "simd" => {
180                features.simd(true);
181            }
182            "bulk-memory" => {
183                features.bulk_memory(true);
184            }
185            "reference-types" => {
186                features.reference_types(true);
187            }
188            "multi-value" => {
189                features.multi_value(true);
190            }
191            "threads" => {
192                features.threads(true);
193            }
194            "exception-handling" => {
195                features.exceptions(true);
196            }
197            "memory64" => {
198                features.memory64(true);
199            }
200            // Ignore unrecognized features
201            _ => {}
202        }
203    }
204
205    features
206}