Skip to main content

libcontainer/container/
init_builder.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3use std::rc::Rc;
4
5use oci_spec::runtime::Spec;
6use user_ns::UserNamespaceConfig;
7
8use super::builder::ContainerBuilder;
9use super::builder_impl::ContainerBuilderImpl;
10use super::{Container, ContainerStatus};
11use crate::config::YoukiConfig;
12use crate::error::{ErrInvalidSpec, LibcontainerError, MissingSpecError};
13use crate::notify_socket::NOTIFY_FILE;
14use crate::process::args::ContainerType;
15use crate::syscall::syscall::create_syscall;
16use crate::{apparmor, tty, user_ns, utils};
17
18// Builder that can be used to configure the properties of a new container
19pub struct InitContainerBuilder {
20    base: ContainerBuilder,
21    bundle: PathBuf,
22    use_systemd: bool,
23    detached: bool,
24    no_pivot: bool,
25    as_sibling: bool,
26}
27
28impl InitContainerBuilder {
29    /// Generates the base configuration for a new container from which
30    /// configuration methods can be chained
31    pub(super) fn new(builder: ContainerBuilder, bundle: PathBuf) -> Self {
32        Self {
33            base: builder,
34            bundle,
35            use_systemd: true,
36            detached: true,
37            no_pivot: false,
38            as_sibling: false,
39        }
40    }
41
42    /// Sets if systemd should be used for managing cgroups
43    pub fn with_systemd(mut self, should_use: bool) -> Self {
44        self.use_systemd = should_use;
45        self
46    }
47
48    /// Sets if the init process should be run as a child or a sibling of
49    /// the calling process
50    pub fn as_sibling(mut self, as_sibling: bool) -> Self {
51        self.as_sibling = as_sibling;
52        self
53    }
54
55    pub fn with_detach(mut self, detached: bool) -> Self {
56        self.detached = detached;
57        self
58    }
59
60    pub fn with_no_pivot(mut self, no_pivot: bool) -> Self {
61        self.no_pivot = no_pivot;
62        self
63    }
64
65    /// Creates a new container
66    pub fn build(self) -> Result<Container, LibcontainerError> {
67        let spec = self.load_spec()?;
68        let container_dir = self.create_container_dir()?;
69
70        let mut container = self.create_container_state(&container_dir)?;
71        container
72            .set_systemd(self.use_systemd)
73            .set_annotations(spec.annotations().clone());
74
75        let notify_path = container_dir.join(NOTIFY_FILE);
76        // convert path of root file system of the container to absolute path
77        let rootfs = fs::canonicalize(spec.root().as_ref().ok_or(MissingSpecError::Root)?.path())
78            .map_err(LibcontainerError::OtherIO)?;
79
80        // if socket file path is given in commandline options,
81        // get file descriptors of console socket
82        let csocketfd = if let Some(console_socket) = &self.base.console_socket {
83            Some(tty::setup_console_socket(
84                &container_dir,
85                console_socket,
86                "console-socket",
87            )?)
88        } else {
89            None
90        };
91
92        let user_ns_config = UserNamespaceConfig::new(&spec)?;
93
94        let config = YoukiConfig::from_spec(&spec, container.id())?;
95        config.save(&container_dir).map_err(|err| {
96            tracing::error!(?container_dir, "failed to save config: {}", err);
97            err
98        })?;
99
100        let mut builder_impl = ContainerBuilderImpl {
101            container_type: ContainerType::InitContainer,
102            syscall: self.base.syscall,
103            container_id: self.base.container_id,
104            pid_file: self.base.pid_file,
105            console_socket: csocketfd,
106            use_systemd: self.use_systemd,
107            spec: Rc::new(spec),
108            rootfs,
109            user_ns_config,
110            notify_path,
111            container: Some(container.clone()),
112            preserve_fds: self.base.preserve_fds,
113            detached: self.detached,
114            executor: self.base.executor,
115            no_pivot: self.no_pivot,
116            stdin: self.base.stdin,
117            stdout: self.base.stdout,
118            stderr: self.base.stderr,
119            as_sibling: self.as_sibling,
120            sub_cgroup_path: None,
121            process_label: None,
122        };
123
124        builder_impl.create()?;
125
126        container.refresh_state()?;
127
128        Ok(container)
129    }
130
131    fn create_container_dir(&self) -> Result<PathBuf, LibcontainerError> {
132        let container_dir = self.base.root_path.join(&self.base.container_id);
133        tracing::debug!("container directory will be {:?}", container_dir);
134
135        if container_dir.exists() {
136            tracing::error!(id = self.base.container_id, dir = ?container_dir, "container already exists");
137            return Err(LibcontainerError::Exist);
138        }
139
140        std::fs::create_dir_all(&container_dir).map_err(|err| {
141            tracing::error!(
142                ?container_dir,
143                "failed to create container directory: {}",
144                err
145            );
146            LibcontainerError::OtherIO(err)
147        })?;
148
149        Ok(container_dir)
150    }
151
152    fn load_spec(&self) -> Result<Spec, LibcontainerError> {
153        let source_spec_path = self.bundle.join("config.json");
154        let mut spec = Spec::load(source_spec_path)?;
155        Self::validate_spec(&spec)?;
156
157        spec.canonicalize_rootfs(&self.bundle).map_err(|err| {
158            tracing::error!(bundle = ?self.bundle, "failed to canonicalize rootfs: {}", err);
159            err
160        })?;
161
162        Ok(spec)
163    }
164
165    fn validate_spec(spec: &Spec) -> Result<(), LibcontainerError> {
166        let version = spec.version();
167        if !version.starts_with("1.") {
168            tracing::error!(
169                "runtime spec has incompatible version '{}'. Only 1.X.Y is supported",
170                spec.version()
171            );
172            Err(ErrInvalidSpec::UnsupportedVersion)?;
173        }
174
175        if let Some(process) = spec.process() {
176            if let Some(profile) = process.apparmor_profile() {
177                let apparmor_is_enabled = apparmor::is_enabled().map_err(|err| {
178                    tracing::error!(?err, "failed to check if apparmor is enabled");
179                    LibcontainerError::OtherIO(err)
180                })?;
181                if !apparmor_is_enabled {
182                    tracing::error!(
183                        ?profile,
184                        "apparmor profile exists in the spec, but apparmor is not activated on this system"
185                    );
186                    Err(ErrInvalidSpec::AppArmorNotEnabled)?;
187                }
188            }
189
190            if let Some(io_priority) = process.io_priority() {
191                let priority = io_priority.priority();
192                let iop_class_res = serde_json::to_string(&io_priority.class());
193                match iop_class_res {
194                    Ok(iop_class) => {
195                        if !(0..=7).contains(&priority) {
196                            tracing::error!(
197                                ?priority,
198                                "io priority '{}' not between 0 and 7 (inclusive), class '{}' not in (IO_PRIO_CLASS_RT,IO_PRIO_CLASS_BE,IO_PRIO_CLASS_IDLE)",
199                                priority,
200                                iop_class
201                            );
202                            Err(ErrInvalidSpec::IoPriority)?;
203                        }
204                    }
205                    Err(e) => {
206                        tracing::error!(?priority, ?e, "failed to parse io priority class");
207                        Err(ErrInvalidSpec::IoPriority)?;
208                    }
209                }
210            }
211        }
212
213        if let Some(mounts) = spec.mounts() {
214            utils::validate_mount_options(mounts)?;
215        }
216
217        let syscall = create_syscall();
218        utils::validate_spec_for_new_user_ns(spec, &*syscall)?;
219        utils::validate_spec_for_net_devices(spec, &*syscall)
220            .map_err(LibcontainerError::NetDevicesError)?;
221
222        Ok(())
223    }
224
225    fn create_container_state(&self, container_dir: &Path) -> Result<Container, LibcontainerError> {
226        let container = Container::new(
227            &self.base.container_id,
228            ContainerStatus::Creating,
229            None,
230            &self.bundle,
231            container_dir,
232        )?;
233        container.save()?;
234        Ok(container)
235    }
236}