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}