wasmer_wasix/runtime/resolver/
inputs.rs1use 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#[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#[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#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct PackageInfo {
77 pub id: PackageId,
78 pub commands: Vec<Command>,
80 pub entrypoint: Option<String>,
83 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 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 Ok(vec![])
214 }
215 }
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
220pub struct FileSystemMapping {
221 pub volume_name: String,
223 pub mount_path: String,
225 pub original_path: Option<String>,
227 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#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct DistributionInfo {
269 pub webc: Url,
271 pub webc_sha256: WebcHash,
273}
274
275#[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 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 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 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 std::fs::write(path_hash, hash).ok();
322 let hash = WebcHash::from_bytes(hash);
323
324 Ok(hash)
325 }
326
327 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}