oci_unpack/unpacker/
mod.rs1mod event_handler;
2mod images;
3mod layers;
4
5use std::collections::BTreeMap;
6use std::io;
7use std::os::unix::ffi::OsStrExt;
8use std::path::{Path, PathBuf};
9
10use crate::{digest::DigestError, reference::Reference, MediaType};
11
12pub use event_handler::{EventHandler, NoEventHandler};
13
14#[derive(thiserror::Error, Debug)]
16pub enum UnpackError {
17 #[error("I/O error: {1}: {0}")]
18 Io(io::Error, PathBuf),
19
20 #[cfg(feature = "sandbox")]
21 #[error("Failed to create a sandbox: {0}")]
22 Sandbox(#[from] landlock::RulesetError),
23
24 #[error("Operation interrupted.")]
25 Interrupted,
26
27 #[error("HTTP request failed: {0}")]
28 HttpRequest(#[from] crate::http::HttpError),
29
30 #[error("Invalid JSON: {0}")]
31 Json(#[from] serde_json::Error),
32
33 #[error("Invalid digest: {0}")]
34 InvalidDigest(#[from] DigestError),
35
36 #[error("Missing or invalid Content-Type.")]
37 MissingContentType,
38
39 #[error("Invalid Content-Type: {0}")]
40 InvalidContentType(MediaType),
41
42 #[error("No image for the architecture.")]
43 MissingArchitecture,
44}
45
46macro_rules! try_io {
50 ($path:expr, $b:block) => {
51 match (|| -> Result<_, io::Error> { Ok($b) })() {
52 Ok(ok) => ok,
53 Err(err) => return Err(UnpackError::Io(io::Error::from(err), $path.into())),
54 }
55 };
56
57 ($path:expr, $e:expr $(,)?) => {
58 $e.map_err(|e| UnpackError::Io(io::Error::from(e), $path.into()))?
59 };
60}
61
62use try_io;
64
65type DirectoryMetadata = BTreeMap<(usize, PathBuf), DirectoryMetadataEntry>;
77
78struct DirectoryMetadataEntry {
79 mode: rustix::fs::Mode,
80 mtime: u64,
81 uid: Option<u32>,
82 gid: Option<u32>,
83}
84
85impl DirectoryMetadataEntry {
86 fn key(path: PathBuf) -> (usize, PathBuf) {
88 let path_len = path.as_os_str().as_bytes().len();
89 (usize::MAX - path_len, path)
90 }
91}
92
93pub struct Unpacker<'a, E> {
95 reference: Reference<'a>,
96 architecture: Option<&'a str>,
97 os: Option<&'a str>,
98 event_handler: E,
99 require_sandbox: bool,
100}
101
102impl<'a> Unpacker<'a, NoEventHandler> {
103 pub fn new(reference: Reference<'a>) -> Self {
107 Self {
108 reference,
109 architecture: None,
110 os: None,
111 event_handler: NoEventHandler,
112 require_sandbox: true,
113 }
114 }
115
116 pub fn event_handler<E: EventHandler>(self, event_handler: E) -> Unpacker<'a, E> {
118 Unpacker {
119 event_handler,
120 reference: self.reference,
121 architecture: self.architecture,
122 os: self.os,
123 require_sandbox: self.require_sandbox,
124 }
125 }
126}
127
128impl<'a, E: EventHandler> Unpacker<'a, E> {
129 pub fn require_sandbox(mut self, require_sandbox: bool) -> Self {
134 self.require_sandbox = require_sandbox;
135 self
136 }
137
138 pub fn architecture(mut self, architecture: &'a str) -> Self {
142 self.architecture = Some(architecture);
143 self
144 }
145
146 pub fn os(mut self, os: &'a str) -> Self {
150 self.os = Some(os);
151 self
152 }
153
154 pub fn unpack(self, target: impl AsRef<Path>) -> Result<(), UnpackError> {
163 let target = target.as_ref();
164
165 Self::check_empty_dir(target).map_err(|e| UnpackError::Io(e, target.to_owned()))?;
166
167 let mut client = crate::http::Client::new(&self.reference, &self.event_handler);
168
169 let manifest =
170 crate::manifests::get(&self.reference, self.architecture, self.os, &mut client)?;
171
172 #[cfg(feature = "sandbox")]
176 if let Err(err) = Self::sandbox(target, &self.event_handler) {
177 if self.require_sandbox {
178 return Err(UnpackError::Sandbox(err));
179 }
180 }
181
182 images::get(client, manifest, target, &self.event_handler)
183 }
184
185 fn check_empty_dir(path: &Path) -> io::Result<()> {
189 if !path.exists() {
190 return std::fs::create_dir_all(path);
191 }
192
193 if std::fs::read_dir(path)?.next().is_some() {
194 return Err(io::Error::from_raw_os_error(libc::ENOTEMPTY));
197 }
198
199 Ok(())
200 }
201
202 #[cfg(feature = "sandbox")]
208 fn sandbox(
209 target: &Path,
210 event_handler: &impl EventHandler,
211 ) -> Result<(), landlock::RulesetError> {
212 use landlock::*;
213
214 let abi = ABI::V2;
215
216 let status = Ruleset::default()
217 .set_compatibility(CompatLevel::HardRequirement)
218 .handle_access(AccessFs::from_all(abi))?
219 .create()?
220 .add_rules(path_beneath_rules(&[target], AccessFs::from_all(abi)))?
221 .restrict_self()?;
222
223 event_handler.sandbox_status(status);
224
225 Ok(())
226 }
227}