1 // Copyright (c) 2024 Huawei Technologies Co.,Ltd. All rights reserved.
2 //
3 // StratoVirt is licensed under Mulan PSL v2.
4 // You can use this software according to the terms and conditions of the Mulan
5 // PSL v2.
6 // You may obtain a copy of Mulan PSL v2 at:
7 //         http://license.coscl.org.cn/MulanPSL2
8 // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
9 // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
10 // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
11 // See the Mulan PSL v2 for more details.
12 
13 use std::{collections::HashMap, fs::File, io::BufReader, path::Path};
14 
15 use anyhow::{anyhow, Context, Result};
16 use serde::{Deserialize, Serialize};
17 
18 #[cfg(target_os = "linux")]
19 use crate::linux::IdMapping;
20 #[cfg(target_family = "unix")]
21 use crate::posix::Root;
22 use crate::{linux::LinuxPlatform, posix::Hooks, process::Process, vm::VmPlatform};
23 
24 /// Additional mounts beyond root.
25 #[allow(non_snake_case)]
26 #[derive(Serialize, Deserialize, Debug, Clone)]
27 pub struct Mount {
28     /// Destination of mount point: path inside container.
29     pub destination: String,
30     /// A device name, but can also be a file or directory name for bind mounts
31     /// or a dummy.
32     #[serde(skip_serializing_if = "Option::is_none")]
33     pub source: Option<String>,
34     /// Mount options of the filesystem to be used.
35     #[serde(skip_serializing_if = "Option::is_none")]
36     pub options: Option<Vec<String>>,
37     /// The type of the filesystem to be mounted.
38     #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
39     pub fs_type: Option<String>,
40     /// The mapping to convert UIDs from the source file system to the
41     /// destination mount point.
42     #[serde(skip_serializing_if = "Option::is_none")]
43     pub uidMappings: Option<IdMapping>,
44     /// The mapping to convert GIDs from the source file system to the
45     /// destination mount point.
46     #[serde(skip_serializing_if = "Option::is_none")]
47     pub gidMappings: Option<IdMapping>,
48 }
49 
50 /// Metadata necessary to implement standard operations against the container.
51 #[allow(non_snake_case)]
52 #[derive(Serialize, Deserialize, Debug, Clone)]
53 pub struct RuntimeConfig {
54     /// Version of the Open Container Initiative Runtime Specification
55     /// with which the bundle complies.
56     pub ociVersion: String,
57     /// Container's root filesystem.
58     pub root: Root,
59     /// Additional mounts beyond root.
60     pub mounts: Vec<Mount>,
61     /// Container process.
62     pub process: Process,
63     /// Container's hostname as seen by processes running inside the container.
64     #[serde(skip_serializing_if = "Option::is_none")]
65     pub hostname: Option<String>,
66     /// Container's domainname as seen by processes running inside the
67     /// container.
68     #[serde(skip_serializing_if = "Option::is_none")]
69     pub domainname: Option<String>,
70     /// Linux-specific section of the container configuration.
71     #[cfg(target_os = "linux")]
72     #[serde(skip_serializing_if = "Option::is_none")]
73     pub linux: Option<LinuxPlatform>,
74     /// Vm-specific section of the container configuration.
75     #[serde(skip_serializing_if = "Option::is_none")]
76     pub vm: Option<VmPlatform>,
77     /// Custom actions related to the lifecycle of the container.
78     #[cfg(target_family = "unix")]
79     #[serde(skip_serializing_if = "Option::is_none")]
80     pub hooks: Option<Hooks>,
81     /// Arbitrary metadata for the container.
82     #[serde(skip_serializing_if = "Option::is_none")]
83     pub annotations: Option<HashMap<String, String>>,
84 }
85 
86 impl RuntimeConfig {
from_file(path: &String) -> Result<RuntimeConfig>87     pub fn from_file(path: &String) -> Result<RuntimeConfig> {
88         let file = File::open(Path::new(path)).with_context(|| "Failed to open config.json")?;
89         let reader = BufReader::new(file);
90         serde_json::from_reader(reader).map_err(|e| anyhow!("Failed to load config.json: {:?}", e))
91     }
92 }
93 
94 #[cfg(test)]
95 mod tests {
96     use super::*;
97     use serde_json;
98 
99     #[test]
test_mounts()100     fn test_mounts() {
101         let json = r#"{
102             "mounts": [
103                 {
104                     "destination": "/proc",
105                     "type": "proc",
106                     "source": "proc"
107                 },
108                 {
109                     "destination": "/dev",
110                     "type": "tmpfs",
111                     "source": "tmpfs",
112                     "options": [
113                         "nosuid",
114                         "strictatime",
115                         "mode=755",
116                         "size=65536k"
117                     ]
118                 }
119             ]
120         }"#;
121 
122         #[allow(non_snake_case)]
123         #[derive(Serialize, Deserialize)]
124         struct Section {
125             mounts: Vec<Mount>,
126         }
127 
128         let section: Section = serde_json::from_str(json).unwrap();
129         assert_eq!(section.mounts.len(), 2);
130         assert_eq!(section.mounts[0].destination, "/proc");
131         assert_eq!(section.mounts[0].fs_type, Some("proc".to_string()));
132         assert_eq!(section.mounts[0].source, Some("proc".to_string()));
133         let options = section.mounts[1].options.as_ref().unwrap();
134         assert_eq!(options.len(), 4);
135         assert_eq!(options[0], "nosuid");
136         assert_eq!(options[1], "strictatime");
137         assert_eq!(options[2], "mode=755");
138         assert_eq!(options[3], "size=65536k");
139     }
140 }
141