wasmer_package/
utils.rs

1use bytes::{Buf, Bytes};
2use std::{
3    fs::File,
4    io::{BufRead, BufReader, Read, Seek},
5    path::Path,
6};
7use webc::{Container, ContainerError, Version};
8
9use crate::package::{Package, WasmerPackageError};
10
11/// Check if something looks like a `*.tar.gz` file.
12fn is_tarball(mut file: impl Read + Seek) -> bool {
13    /// Magic bytes for a `*.tar.gz` file according to
14    /// [Wikipedia](https://en.wikipedia.org/wiki/List_of_file_signatures).
15    const TAR_GZ_MAGIC_BYTES: [u8; 2] = [0x1F, 0x8B];
16
17    let mut buffer = [0_u8; 2];
18    let result = match file.read_exact(&mut buffer) {
19        Ok(_) => buffer == TAR_GZ_MAGIC_BYTES,
20        Err(_) => false,
21    };
22
23    let _ = file.rewind();
24
25    result
26}
27
28pub fn from_disk(path: impl AsRef<Path>) -> Result<Container, WasmerPackageError> {
29    let path = path.as_ref();
30
31    if path.is_dir() {
32        return parse_dir(path);
33    }
34
35    let mut f = File::open(path).map_err(|error| ContainerError::Open {
36        error,
37        path: path.to_path_buf(),
38    })?;
39
40    if is_tarball(&mut f) {
41        return parse_tarball(BufReader::new(f));
42    }
43
44    match webc::detect(&mut f) {
45        Ok(Version::V1) => parse_v1_mmap(f).map_err(Into::into),
46        Ok(Version::V2) => parse_v2_mmap(f).map_err(Into::into),
47        Ok(Version::V3) => parse_v3_mmap(f).map_err(Into::into),
48        Ok(other) => {
49            // fall back to the allocating generic version
50            let mut buffer = Vec::new();
51            f.rewind()
52                .and_then(|_| f.read_to_end(&mut buffer))
53                .map_err(|error| ContainerError::Read {
54                    path: path.to_path_buf(),
55                    error,
56                })?;
57
58            Container::from_bytes_and_version(buffer.into(), other).map_err(Into::into)
59        }
60        Err(e) => Err(ContainerError::Detect(e).into()),
61    }
62}
63
64pub fn from_bytes(bytes: impl Into<Bytes>) -> Result<Container, WasmerPackageError> {
65    let bytes: Bytes = bytes.into();
66
67    if is_tarball(std::io::Cursor::new(&bytes)) {
68        return parse_tarball(bytes.reader());
69    }
70
71    let version = webc::detect(bytes.as_ref())?;
72    Container::from_bytes_and_version(bytes, version).map_err(Into::into)
73}
74
75#[allow(clippy::result_large_err)]
76fn parse_tarball(reader: impl BufRead) -> Result<Container, WasmerPackageError> {
77    let pkg = Package::from_tarball(reader)?;
78    Ok(Container::new(pkg))
79}
80
81#[allow(clippy::result_large_err)]
82fn parse_dir(path: &Path) -> Result<Container, WasmerPackageError> {
83    let wasmer_toml = path.join("wasmer.toml");
84    let pkg = Package::from_manifest(wasmer_toml)?;
85    Ok(Container::new(pkg))
86}
87
88#[allow(clippy::result_large_err)]
89fn parse_v1_mmap(f: File) -> Result<Container, ContainerError> {
90    // We need to explicitly use WebcMmap to get a memory-mapped
91    // parser
92    let options = webc::v1::ParseOptions::default();
93    let webc = webc::v1::WebCMmap::from_file(f, &options)?;
94    Ok(Container::new(webc))
95}
96
97#[allow(clippy::result_large_err)]
98fn parse_v2_mmap(f: File) -> Result<Container, ContainerError> {
99    // Note: OwnedReader::from_file() will automatically try to
100    // use a memory-mapped file when possible.
101    let webc = webc::v2::read::OwnedReader::from_file(f)?;
102    Ok(Container::new(webc))
103}
104
105#[allow(clippy::result_large_err)]
106fn parse_v3_mmap(f: File) -> Result<Container, ContainerError> {
107    // Note: OwnedReader::from_file() will automatically try to
108    // use a memory-mapped file when possible.
109    let webc = webc::v3::read::OwnedReader::from_file(f)?;
110    Ok(Container::new(webc))
111}