wasmer_wasix/runtime/resolver/
inputs.rs

1use std::{
2    fmt::{self, Display, Formatter},
3    fs::File,
4    io::{BufRead, BufReader, Read},
5    path::Path,
6};
7
8use anyhow::Error;
9use semver::VersionReq;
10use sha2::{Digest, Sha256};
11use url::Url;
12use wasmer_config::package::{NamedPackageId, PackageHash, PackageId, PackageSource};
13use wasmer_package::utils::from_disk;
14use webc::metadata::{Manifest, UrlOrManifest, annotations::Wapm as WapmAnnotations};
15
16/// A dependency constraint.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Dependency {
19    pub alias: String,
20    pub pkg: PackageSource,
21}
22
23impl Dependency {
24    pub fn package_name(&self) -> Option<String> {
25        self.pkg.as_named().map(|x| x.full_name())
26    }
27
28    pub fn alias(&self) -> &str {
29        &self.alias
30    }
31
32    pub fn version(&self) -> Option<&VersionReq> {
33        self.pkg.as_named().and_then(|n| n.version_opt())
34    }
35}
36
37/// Some metadata a [`Source`][source] can provide about a package without
38/// needing to download the entire `*.webc` file.
39///
40/// [source]: crate::runtime::resolver::Source
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct PackageSummary {
43    pub pkg: PackageInfo,
44    pub dist: DistributionInfo,
45}
46
47impl PackageSummary {
48    pub fn package_id(&self) -> PackageId {
49        self.pkg.id.clone()
50    }
51
52    pub fn from_webc_file(path: impl AsRef<Path>) -> Result<PackageSummary, Error> {
53        let path = path.as_ref().canonicalize()?;
54        let container = from_disk(&path)?;
55        let webc_sha256 = WebcHash::for_file(&path)?;
56        let url = crate::runtime::resolver::utils::url_from_file_path(&path).ok_or_else(|| {
57            anyhow::anyhow!("Unable to turn \"{}\" into a file:// URL", path.display())
58        })?;
59
60        let manifest = container.manifest();
61        let id = PackageInfo::package_id_from_manifest(manifest)?
62            .unwrap_or_else(|| PackageId::Hash(PackageHash::from_sha256_bytes(webc_sha256.0)));
63
64        let pkg = PackageInfo::from_manifest(id, manifest, container.version())?;
65        let dist = DistributionInfo {
66            webc: url,
67            webc_sha256,
68        };
69
70        Ok(PackageSummary { pkg, dist })
71    }
72}
73
74/// Information about a package's contents.
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct PackageInfo {
77    pub id: PackageId,
78    /// Commands this package exposes to the outside world.
79    pub commands: Vec<Command>,
80    /// The name of a [`Command`] that should be used as this package's
81    /// entrypoint.
82    pub entrypoint: Option<String>,
83    /// Any dependencies this package may have.
84    pub dependencies: Vec<Dependency>,
85    pub filesystem: Vec<FileSystemMapping>,
86}
87
88impl PackageInfo {
89    pub fn package_ident_from_manifest(
90        manifest: &Manifest,
91    ) -> Result<Option<NamedPackageId>, Error> {
92        let wapm_annotations = manifest.wapm()?;
93
94        let name = wapm_annotations
95            .as_ref()
96            .map_or_else(|| None, |annotations| annotations.name.clone());
97
98        let version = wapm_annotations.as_ref().map_or_else(
99            || String::from("0.0.0"),
100            |annotations| {
101                annotations
102                    .version
103                    .clone()
104                    .unwrap_or_else(|| String::from("0.0.0"))
105            },
106        );
107
108        if let Some(name) = name {
109            Ok(Some(NamedPackageId {
110                full_name: name,
111                version: version.parse()?,
112            }))
113        } else {
114            Ok(None)
115        }
116    }
117
118    pub fn package_id_from_manifest(
119        manifest: &Manifest,
120    ) -> Result<Option<PackageId>, anyhow::Error> {
121        let ident = Self::package_ident_from_manifest(manifest)?;
122
123        Ok(ident.map(PackageId::Named))
124    }
125
126    pub fn from_manifest(
127        id: PackageId,
128        manifest: &Manifest,
129        webc_version: webc::Version,
130    ) -> Result<Self, Error> {
131        // FIXME: is this still needed?
132        // let wapm_annotations = manifest.wapm()?;
133        // let name = wapm_annotations
134        //     .as_ref()
135        //     .map_or_else(|| None, |annotations| annotations.name.clone());
136        //
137        // let version = wapm_annotations.as_ref().map_or_else(
138        //     || String::from("0.0.0"),
139        //     |annotations| {
140        //         annotations
141        //             .version
142        //             .clone()
143        //             .unwrap_or_else(|| String::from("0.0.0"))
144        //     },
145        // );
146
147        let dependencies = manifest
148            .use_map
149            .iter()
150            .map(|(alias, value)| {
151                Ok(Dependency {
152                    alias: alias.clone(),
153                    pkg: url_or_manifest_to_specifier(value)?,
154                })
155            })
156            .collect::<Result<Vec<_>, Error>>()?;
157
158        let commands = manifest
159            .commands
160            .iter()
161            .map(|(name, _value)| crate::runtime::resolver::Command {
162                name: name.to_string(),
163            })
164            .collect();
165
166        let filesystem = filesystem_mapping_from_manifest(manifest, webc_version)?;
167
168        Ok(PackageInfo {
169            id,
170            dependencies,
171            commands,
172            entrypoint: manifest.entrypoint.clone(),
173            filesystem,
174        })
175    }
176
177    pub fn id(&self) -> PackageId {
178        self.id.clone()
179    }
180}
181
182fn filesystem_mapping_from_manifest(
183    manifest: &Manifest,
184    webc_version: webc::Version,
185) -> Result<Vec<FileSystemMapping>, anyhow::Error> {
186    match manifest.filesystem()? {
187        Some(webc::metadata::annotations::FileSystemMappings(mappings)) => {
188            let mappings = mappings
189                .into_iter()
190                .map(|mapping| FileSystemMapping {
191                    volume_name: mapping.volume_name,
192                    mount_path: mapping.mount_path,
193                    dependency_name: mapping.from,
194                    original_path: mapping.host_path,
195                })
196                .collect();
197
198            Ok(mappings)
199        }
200        None => {
201            if webc_version == webc::Version::V2 || webc_version == webc::Version::V1 {
202                tracing::debug!(
203                    "No \"fs\" package annotations found. Mounting the \"atom\" volume to \"/\" for compatibility."
204                );
205                Ok(vec![FileSystemMapping {
206                    volume_name: "atom".to_string(),
207                    mount_path: "/".to_string(),
208                    original_path: Some("/".to_string()),
209                    dependency_name: None,
210                }])
211            } else {
212                // There is no atom volume in v3 by default, so we return an empty Vec.
213                Ok(vec![])
214            }
215        }
216    }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
220pub struct FileSystemMapping {
221    /// The volume to be mounted.
222    pub volume_name: String,
223    /// Where the volume should be mounted within the resulting filesystem.
224    pub mount_path: String,
225    /// The path of the mapped item within its original volume.
226    pub original_path: Option<String>,
227    /// The name of the package this volume comes from (current package if
228    /// `None`).
229    pub dependency_name: Option<String>,
230}
231
232fn url_or_manifest_to_specifier(value: &UrlOrManifest) -> Result<PackageSource, Error> {
233    match value {
234        UrlOrManifest::Url(url) => Ok(PackageSource::Url(url.clone())),
235        UrlOrManifest::Manifest(manifest) => {
236            if let Ok(Some(WapmAnnotations { name, version, .. })) =
237                manifest.package_annotation("wapm")
238            {
239                let version = version.unwrap().parse()?;
240                let id = NamedPackageId {
241                    full_name: name.unwrap(),
242                    version,
243                };
244
245                return Ok(PackageSource::from(id));
246            }
247
248            if let Some(origin) = manifest
249                .origin
250                .as_deref()
251                .and_then(|origin| Url::parse(origin).ok())
252            {
253                return Ok(PackageSource::Url(origin));
254            }
255
256            Err(Error::msg(
257                "Unable to determine a package specifier for a vendored dependency",
258            ))
259        }
260        UrlOrManifest::RegistryDependentUrl(specifier) => {
261            specifier.parse().map_err(anyhow::Error::from)
262        }
263    }
264}
265
266/// Information used when retrieving a package.
267#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct DistributionInfo {
269    /// A URL that can be used to download the `*.webc` file.
270    pub webc: Url,
271    /// A SHA-256 checksum for the `*.webc` file.
272    pub webc_sha256: WebcHash,
273}
274
275/// The SHA-256 hash of a `*.webc` file.
276#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
277pub struct WebcHash(pub(crate) [u8; 32]);
278
279impl WebcHash {
280    pub fn from_bytes(bytes: [u8; 32]) -> Self {
281        WebcHash(bytes)
282    }
283
284    /// Parse a sha256 hash from a hex-encoded string.
285    pub fn parse_hex(hex_str: &str) -> Result<Self, hex::FromHexError> {
286        let mut hash = [0_u8; 32];
287        hex::decode_to_slice(hex_str, &mut hash)?;
288        Ok(Self(hash))
289    }
290
291    pub fn for_file(path: &Path) -> Result<Self, std::io::Error> {
292        // check for a hash at the file location
293        let mut path_hash = path.to_owned();
294        path_hash.set_extension("webc.sha256");
295        if let Ok(mut file) = File::open(&path_hash) {
296            let mut hash = Vec::new();
297            if let Ok(amt) = file.read_to_end(&mut hash) {
298                if amt == 32 {
299                    return Ok(WebcHash::from_bytes(hash[0..32].try_into().unwrap()));
300                }
301            }
302        }
303
304        // compute the hash
305        let mut hasher = Sha256::default();
306        let mut reader = BufReader::new(File::open(path)?);
307
308        loop {
309            let buffer = reader.fill_buf()?;
310            if buffer.is_empty() {
311                break;
312            }
313            hasher.update(buffer);
314            let bytes_read = buffer.len();
315            reader.consume(bytes_read);
316        }
317
318        let hash = hasher.finalize().into();
319
320        // write the cache of the hash to the file system
321        std::fs::write(path_hash, hash).ok();
322        let hash = WebcHash::from_bytes(hash);
323
324        Ok(hash)
325    }
326
327    /// Generate a new [`WebcHash`] based on the SHA-256 hash of some bytes.
328    pub fn sha256(webc: impl AsRef<[u8]>) -> Self {
329        let webc = webc.as_ref();
330
331        let mut hasher = Sha256::default();
332        hasher.update(webc);
333        WebcHash::from_bytes(hasher.finalize().into())
334    }
335
336    pub fn as_bytes(self) -> [u8; 32] {
337        self.0
338    }
339
340    pub fn as_hex(&self) -> String {
341        hex::encode(self.0)
342    }
343}
344
345impl From<[u8; 32]> for WebcHash {
346    fn from(bytes: [u8; 32]) -> Self {
347        WebcHash::from_bytes(bytes)
348    }
349}
350
351impl Display for WebcHash {
352    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
353        for byte in self.0 {
354            write!(f, "{byte:02X}")?;
355        }
356
357        Ok(())
358    }
359}
360
361#[derive(Debug, Clone, PartialEq, Eq)]
362pub struct Command {
363    pub name: String,
364}