libcontainer/container/builder.rs
1use std::os::fd::OwnedFd;
2use std::path::PathBuf;
3
4use super::init_builder::InitContainerBuilder;
5use super::tenant_builder::TenantContainerBuilder;
6use crate::error::{ErrInvalidID, LibcontainerError};
7use crate::syscall::syscall::SyscallType;
8use crate::utils::PathBufExt;
9use crate::workload::{self, Executor};
10
11pub struct ContainerBuilder {
12 /// Id of the container
13 pub(super) container_id: String,
14 /// Root directory for container state
15 pub(super) root_path: PathBuf,
16 /// Interface to operating system primitives
17 pub(super) syscall: SyscallType,
18 /// File which will be used to communicate the pid of the
19 /// container process to the higher level runtime
20 pub(super) pid_file: Option<PathBuf>,
21 /// Socket to communicate the file descriptor of the ptty
22 pub(super) console_socket: Option<PathBuf>,
23 /// File descriptors to be passed into the container process
24 pub(super) preserve_fds: i32,
25 /// The function that actually runs on the container init process. Default
26 /// is to execute the specified command in the oci spec.
27 pub(super) executor: Box<dyn Executor>,
28 // RawFd set to stdin of the container init process.
29 pub stdin: Option<OwnedFd>,
30 // RawFd set to stdout of the container init process.
31 pub stdout: Option<OwnedFd>,
32 // RawFd set to stderr of the container init process.
33 pub stderr: Option<OwnedFd>,
34}
35
36/// Builder that can be used to configure the common properties of
37/// either a init or a tenant container
38///
39/// # Example
40///
41/// ```no_run
42/// use libcontainer::container::builder::ContainerBuilder;
43/// use libcontainer::syscall::syscall::SyscallType;
44///
45/// ContainerBuilder::new(
46/// "74f1a4cb3801".to_owned(),
47/// SyscallType::default(),
48/// )
49/// .with_root_path("/run/containers/youki").expect("invalid root path")
50/// .with_pid_file(Some("/var/run/docker.pid")).expect("invalid pid file")
51/// .with_console_socket(Some("/var/run/docker/sock.tty"))
52/// .as_init("/var/run/docker/bundle")
53/// .build();
54/// ```
55impl ContainerBuilder {
56 /// Generates the base configuration for a container which can be
57 /// transformed into either a init container or a tenant container
58 ///
59 /// # Example
60 ///
61 /// ```no_run
62 /// use libcontainer::container::builder::ContainerBuilder;
63 /// use libcontainer::syscall::syscall::SyscallType;
64 ///
65 /// let builder = ContainerBuilder::new(
66 /// "74f1a4cb3801".to_owned(),
67 /// SyscallType::default(),
68 /// );
69 /// ```
70 pub fn new(container_id: String, syscall: SyscallType) -> Self {
71 let root_path = PathBuf::from("/run/youki");
72 Self {
73 container_id,
74 root_path,
75 syscall,
76 pid_file: None,
77 console_socket: None,
78 preserve_fds: 0,
79 executor: workload::default::get_executor(),
80 stdin: None,
81 stdout: None,
82 stderr: None,
83 }
84 }
85
86 /// validate_id checks if the supplied container ID is valid, returning
87 /// the ErrInvalidID in case it is not.
88 ///
89 /// The format of valid ID was never formally defined, instead the code
90 /// was modified to allow or disallow specific characters.
91 ///
92 /// Currently, a valid ID is a non-empty string consisting only of
93 /// the following characters:
94 /// - uppercase (A-Z) and lowercase (a-z) Latin letters;
95 /// - digits (0-9);
96 /// - underscore (_);
97 /// - plus sign (+);
98 /// - minus sign (-);
99 /// - period (.).
100 ///
101 /// In addition, IDs that can't be used to represent a file name
102 /// (such as . or ..) are rejected.
103 pub fn validate_id(self) -> Result<Self, LibcontainerError> {
104 let container_id = self.container_id.clone();
105 if container_id.is_empty() {
106 Err(ErrInvalidID::Empty)?;
107 }
108
109 if container_id == "." || container_id == ".." {
110 Err(ErrInvalidID::FileName)?;
111 }
112
113 for c in container_id.chars() {
114 match c {
115 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '+' | '-' | '.' => (),
116 _ => Err(ErrInvalidID::InvalidChars(c))?,
117 }
118 }
119 Ok(self)
120 }
121
122 /// Transforms this builder into a tenant builder
123 /// # Example
124 ///
125 /// ```no_run
126 /// # use libcontainer::container::builder::ContainerBuilder;
127 /// # use libcontainer::syscall::syscall::SyscallType;
128 ///
129 /// ContainerBuilder::new(
130 /// "74f1a4cb3801".to_owned(),
131 /// SyscallType::default(),
132 /// )
133 /// .as_tenant()
134 /// .with_container_args(vec!["sleep".to_owned(), "9001".to_owned()])
135 /// .build();
136 /// ```
137 #[allow(clippy::wrong_self_convention)]
138 pub fn as_tenant(self) -> TenantContainerBuilder {
139 TenantContainerBuilder::new(self)
140 }
141
142 /// Transforms this builder into an init builder
143 /// # Example
144 ///
145 /// ```no_run
146 /// # use libcontainer::container::builder::ContainerBuilder;
147 /// # use libcontainer::syscall::syscall::SyscallType;
148 ///
149 /// ContainerBuilder::new(
150 /// "74f1a4cb3801".to_owned(),
151 /// SyscallType::default(),
152 /// )
153 /// .as_init("/var/run/docker/bundle")
154 /// .with_systemd(false)
155 /// .build();
156 /// ```
157 #[allow(clippy::wrong_self_convention)]
158 pub fn as_init<P: Into<PathBuf>>(self, bundle: P) -> InitContainerBuilder {
159 InitContainerBuilder::new(self, bundle.into())
160 }
161
162 /// Sets the root path which will be used to store the container state
163 /// # Example
164 ///
165 /// ```no_run
166 /// # use libcontainer::container::builder::ContainerBuilder;
167 /// # use libcontainer::syscall::syscall::SyscallType;
168 ///
169 /// ContainerBuilder::new(
170 /// "74f1a4cb3801".to_owned(),
171 /// SyscallType::default(),
172 /// )
173 /// .with_root_path("/run/containers/youki").expect("invalid root path");
174 /// ```
175 pub fn with_root_path<P: Into<PathBuf>>(mut self, path: P) -> Result<Self, LibcontainerError> {
176 let path = path.into();
177 self.root_path = path.canonicalize_safely().map_err(|err| {
178 tracing::error!(?path, ?err, "failed to canonicalize root path");
179 LibcontainerError::InvalidInput(format!("invalid root path {path:?}: {err:?}"))
180 })?;
181
182 Ok(self)
183 }
184
185 /// Sets the pid file which will be used to write the pid of the container
186 /// process
187 /// # Example
188 ///
189 /// ```no_run
190 /// # use libcontainer::container::builder::ContainerBuilder;
191 /// # use libcontainer::syscall::syscall::SyscallType;
192 ///
193 /// ContainerBuilder::new(
194 /// "74f1a4cb3801".to_owned(),
195 /// SyscallType::default(),
196 /// )
197 /// .with_pid_file(Some("/var/run/docker.pid")).expect("invalid pid file");
198 /// ```
199 pub fn with_pid_file<P: Into<PathBuf>>(
200 mut self,
201 path: Option<P>,
202 ) -> Result<Self, LibcontainerError> {
203 self.pid_file = match path.map(|p| p.into()) {
204 Some(path) => Some(path.canonicalize_safely().map_err(|err| {
205 tracing::error!(?path, ?err, "failed to canonicalize pid file");
206 LibcontainerError::InvalidInput(format!("invalid pid file path {path:?}: {err:?}"))
207 })?),
208 None => None,
209 };
210
211 Ok(self)
212 }
213
214 /// Sets the console socket, which will be used to send the file descriptor
215 /// of the pseudoterminal
216 /// # Example
217 ///
218 /// ```no_run
219 /// # use libcontainer::container::builder::ContainerBuilder;
220 /// # use libcontainer::syscall::syscall::SyscallType;
221 ///
222 /// ContainerBuilder::new(
223 /// "74f1a4cb3801".to_owned(),
224 /// SyscallType::default(),
225 /// )
226 /// .with_console_socket(Some("/var/run/docker/sock.tty"));
227 /// ```
228 pub fn with_console_socket<P: Into<PathBuf>>(mut self, path: Option<P>) -> Self {
229 self.console_socket = path.map(|p| p.into());
230 self
231 }
232
233 /// Sets the number of additional file descriptors which will be passed into
234 /// the container process.
235 /// # Example
236 ///
237 /// ```no_run
238 /// # use libcontainer::container::builder::ContainerBuilder;
239 /// # use libcontainer::syscall::syscall::SyscallType;
240 ///
241 /// ContainerBuilder::new(
242 /// "74f1a4cb3801".to_owned(),
243 /// SyscallType::default(),
244 /// )
245 /// .with_preserved_fds(5);
246 /// ```
247 pub fn with_preserved_fds(mut self, preserved_fds: i32) -> Self {
248 self.preserve_fds = preserved_fds;
249 self
250 }
251
252 /// Sets the function that actually runs on the container init process.
253 /// # Example
254 ///
255 /// ```no_run
256 /// # use libcontainer::container::builder::ContainerBuilder;
257 /// # use libcontainer::syscall::syscall::SyscallType;
258 /// # use libcontainer::workload::default::DefaultExecutor;
259 ///
260 /// ContainerBuilder::new(
261 /// "74f1a4cb3801".to_owned(),
262 /// SyscallType::default(),
263 /// )
264 /// .with_executor(DefaultExecutor{});
265 /// ```
266 pub fn with_executor(mut self, executor: impl Executor + 'static) -> Self {
267 self.executor = Box::new(executor);
268 self
269 }
270
271 /// Sets the stdin of the container, for those who use libcontainer as a library,
272 /// the container stdin may have to be set to an opened file descriptor
273 /// rather than the stdin of the current process.
274 /// # Example
275 ///
276 /// ```no_run
277 /// # use libcontainer::container::builder::ContainerBuilder;
278 /// # use libcontainer::syscall::syscall::SyscallType;
279 /// # use libcontainer::workload::default::DefaultExecutor;
280 /// # use nix::unistd::pipe;
281 ///
282 /// let (r, _w) = pipe().unwrap();
283 /// ContainerBuilder::new(
284 /// "74f1a4cb3801".to_owned(),
285 /// SyscallType::default(),
286 /// )
287 /// .with_stdin(r);
288 /// ```
289 pub fn with_stdin(mut self, stdin: impl Into<OwnedFd>) -> Self {
290 self.stdin = Some(stdin.into());
291 self
292 }
293
294 /// Sets the stdout of the container, for those who use libcontainer as a library,
295 /// the container stdout may have to be set to an opened file descriptor
296 /// rather than the stdout of the current process.
297 /// # Example
298 ///
299 /// ```no_run
300 /// # use libcontainer::container::builder::ContainerBuilder;
301 /// # use libcontainer::syscall::syscall::SyscallType;
302 /// # use libcontainer::workload::default::DefaultExecutor;
303 /// # use nix::unistd::pipe;
304 ///
305 /// let (_r, w) = pipe().unwrap();
306 /// ContainerBuilder::new(
307 /// "74f1a4cb3801".to_owned(),
308 /// SyscallType::default(),
309 /// )
310 /// .with_stdout(w);
311 /// ```
312 pub fn with_stdout(mut self, stdout: impl Into<OwnedFd>) -> Self {
313 self.stdout = Some(stdout.into());
314 self
315 }
316
317 /// Sets the stderr of the container, for those who use libcontainer as a library,
318 /// the container stderr may have to be set to an opened file descriptor
319 /// rather than the stderr of the current process.
320 /// # Example
321 ///
322 /// ```no_run
323 /// # use libcontainer::container::builder::ContainerBuilder;
324 /// # use libcontainer::syscall::syscall::SyscallType;
325 /// # use libcontainer::workload::default::DefaultExecutor;
326 /// # use nix::unistd::pipe;
327 ///
328 /// let (_r, w) = pipe().unwrap();
329 /// ContainerBuilder::new(
330 /// "74f1a4cb3801".to_owned(),
331 /// SyscallType::default(),
332 /// )
333 /// .with_stderr(w);
334 /// ```
335 pub fn with_stderr(mut self, stderr: impl Into<OwnedFd>) -> Self {
336 self.stderr = Some(stderr.into());
337 self
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use std::os::fd::AsRawFd;
344 use std::path::PathBuf;
345
346 use anyhow::{Context, Result};
347 use nix::unistd::pipe;
348
349 use crate::container::builder::ContainerBuilder;
350 use crate::syscall::syscall::SyscallType;
351
352 #[test]
353 fn test_failable_functions() -> Result<()> {
354 let root_path_temp_dir = tempfile::tempdir().context("failed to create temp dir")?;
355 let pid_file_temp_dir = tempfile::tempdir().context("failed to create temp dir")?;
356 let syscall = SyscallType::default();
357
358 ContainerBuilder::new("74f1a4cb3801".to_owned(), syscall)
359 .with_root_path(root_path_temp_dir.path())?
360 .with_pid_file(Some(pid_file_temp_dir.path().join("fake.pid")))?
361 .with_console_socket(Some("/var/run/docker/sock.tty"))
362 .as_init("/var/run/docker/bundle");
363
364 // accept None pid file.
365 ContainerBuilder::new("74f1a4cb3801".to_owned(), syscall).with_pid_file::<PathBuf>(None)?;
366
367 // accept absolute root path which does not exist
368 let abs_root_path = PathBuf::from("/not/existing/path");
369 let path_builder = ContainerBuilder::new("74f1a4cb3801".to_owned(), syscall)
370 .with_root_path(&abs_root_path)
371 .context("build container")?;
372 assert_eq!(path_builder.root_path, abs_root_path);
373
374 // accept relative root path which does not exist
375 let cwd = std::env::current_dir().context("get current dir")?;
376 let path_builder = ContainerBuilder::new("74f1a4cb3801".to_owned(), syscall)
377 .with_root_path("./not/existing/path")
378 .context("build container")?;
379 assert_eq!(path_builder.root_path, cwd.join("not/existing/path"));
380
381 // accept absolute pid path which does not exist
382 let abs_pid_path = PathBuf::from("/not/existing/path");
383 let path_builder = ContainerBuilder::new("74f1a4cb3801".to_owned(), syscall)
384 .with_pid_file(Some(&abs_pid_path))
385 .context("build container")?;
386 assert_eq!(path_builder.pid_file, Some(abs_pid_path));
387
388 // accept relative pid path which does not exist
389 let cwd = std::env::current_dir().context("get current dir")?;
390 let path_builder = ContainerBuilder::new("74f1a4cb3801".to_owned(), syscall)
391 .with_pid_file(Some("./not/existing/path"))
392 .context("build container")?;
393 assert_eq!(path_builder.pid_file, Some(cwd.join("not/existing/path")));
394
395 Ok(())
396 }
397
398 #[test]
399 fn test_validate_id() -> Result<()> {
400 let syscall = SyscallType::default();
401 // validate container_id
402 let result = ContainerBuilder::new("$#".to_owned(), syscall).validate_id();
403 assert!(result.is_err());
404
405 let result = ContainerBuilder::new(".".to_owned(), syscall).validate_id();
406 assert!(result.is_err());
407
408 let result = ContainerBuilder::new("..".to_owned(), syscall).validate_id();
409 assert!(result.is_err());
410
411 let result = ContainerBuilder::new("...".to_owned(), syscall).validate_id();
412 assert!(result.is_ok());
413
414 let result = ContainerBuilder::new("74f1a4cb3801".to_owned(), syscall).validate_id();
415 assert!(result.is_ok());
416 Ok(())
417 }
418
419 #[test]
420 fn test_stdios() -> Result<()> {
421 let (r, _w) = pipe()?;
422 let stdin_raw = r.as_raw_fd();
423 let builder =
424 ContainerBuilder::new("74f1a4cb3801".to_owned(), SyscallType::default()).with_stdin(r);
425 assert_eq!(
426 builder.stdin.as_ref().map(|o| o.as_raw_fd()),
427 Some(stdin_raw)
428 );
429
430 let (_r, w) = pipe()?;
431 let stdout_raw = w.as_raw_fd();
432 let builder =
433 ContainerBuilder::new("74f1a4cb3801".to_owned(), SyscallType::default()).with_stdout(w);
434 assert_eq!(
435 builder.stdout.as_ref().map(|o| o.as_raw_fd()),
436 Some(stdout_raw)
437 );
438
439 let (_r, w) = pipe()?;
440 let stderr_raw = w.as_raw_fd();
441 let builder =
442 ContainerBuilder::new("74f1a4cb3801".to_owned(), SyscallType::default()).with_stderr(w);
443 assert_eq!(
444 builder.stderr.as_ref().map(|o| o.as_raw_fd()),
445 Some(stderr_raw)
446 );
447 Ok(())
448 }
449}