1 // Copyright 2023 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 //! plugin configuration options
6
7 use std::path::PathBuf;
8 use std::str::FromStr;
9
10 use serde::Deserialize;
11 use serde::Serialize;
12
13 use crate::crosvm::config::invalid_value_err;
14
15 /// A bind mount for directories in the plugin process.
16 #[derive(Debug, Serialize, Deserialize)]
17 pub struct BindMount {
18 pub src: PathBuf,
19 pub dst: PathBuf,
20 pub writable: bool,
21 }
22
23 impl FromStr for BindMount {
24 type Err = String;
25
from_str(value: &str) -> Result<Self, Self::Err>26 fn from_str(value: &str) -> Result<Self, Self::Err> {
27 let components: Vec<&str> = value.split(':').collect();
28 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
29 return Err(invalid_value_err(
30 value,
31 "`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]",
32 ));
33 }
34
35 let src = PathBuf::from(components[0]);
36 if src.is_relative() {
37 return Err(invalid_value_err(
38 components[0],
39 "the source path for `plugin-mount` must be absolute",
40 ));
41 }
42 if !src.exists() {
43 return Err(invalid_value_err(
44 components[0],
45 "the source path for `plugin-mount` does not exist",
46 ));
47 }
48
49 let dst = PathBuf::from(match components.get(1) {
50 None | Some(&"") => components[0],
51 Some(path) => path,
52 });
53 if dst.is_relative() {
54 return Err(invalid_value_err(
55 components[1],
56 "the destination path for `plugin-mount` must be absolute",
57 ));
58 }
59
60 let writable: bool = match components.get(2) {
61 None => false,
62 Some(s) => s.parse().map_err(|_| {
63 invalid_value_err(
64 components[2],
65 "the <writable> component for `plugin-mount` is not valid bool",
66 )
67 })?,
68 };
69
70 Ok(BindMount { src, dst, writable })
71 }
72 }
73
74 /// A mapping of linux group IDs for the plugin process.
75 #[derive(Debug, Deserialize, Serialize)]
76 pub struct GidMap {
77 pub inner: base::Gid,
78 pub outer: base::Gid,
79 pub count: u32,
80 }
81
82 impl FromStr for GidMap {
83 type Err = String;
84
from_str(value: &str) -> Result<Self, Self::Err>85 fn from_str(value: &str) -> Result<Self, Self::Err> {
86 let components: Vec<&str> = value.split(':').collect();
87 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
88 return Err(invalid_value_err(
89 value,
90 "`plugin-gid-map` must have exactly 3 components: <inner>[:[<outer>][:<count>]]",
91 ));
92 }
93
94 let inner: base::Gid = components[0].parse().map_err(|_| {
95 invalid_value_err(
96 components[0],
97 "the <inner> component for `plugin-gid-map` is not valid gid",
98 )
99 })?;
100
101 let outer: base::Gid = match components.get(1) {
102 None | Some(&"") => inner,
103 Some(s) => s.parse().map_err(|_| {
104 invalid_value_err(
105 components[1],
106 "the <outer> component for `plugin-gid-map` is not valid gid",
107 )
108 })?,
109 };
110
111 let count: u32 = match components.get(2) {
112 None => 1,
113 Some(s) => s.parse().map_err(|_| {
114 invalid_value_err(
115 components[2],
116 "the <count> component for `plugin-gid-map` is not valid number",
117 )
118 })?,
119 };
120
121 Ok(GidMap {
122 inner,
123 outer,
124 count,
125 })
126 }
127 }
128
parse_plugin_mount_option(value: &str) -> Result<BindMount, String>129 pub fn parse_plugin_mount_option(value: &str) -> Result<BindMount, String> {
130 let components: Vec<&str> = value.split(':').collect();
131 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
132 return Err(invalid_value_err(
133 value,
134 "`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]",
135 ));
136 }
137
138 let src = PathBuf::from(components[0]);
139 if src.is_relative() {
140 return Err(invalid_value_err(
141 components[0],
142 "the source path for `plugin-mount` must be absolute",
143 ));
144 }
145 if !src.exists() {
146 return Err(invalid_value_err(
147 components[0],
148 "the source path for `plugin-mount` does not exist",
149 ));
150 }
151
152 let dst = PathBuf::from(match components.get(1) {
153 None | Some(&"") => components[0],
154 Some(path) => path,
155 });
156 if dst.is_relative() {
157 return Err(invalid_value_err(
158 components[1],
159 "the destination path for `plugin-mount` must be absolute",
160 ));
161 }
162
163 let writable: bool = match components.get(2) {
164 None => false,
165 Some(s) => s.parse().map_err(|_| {
166 invalid_value_err(
167 components[2],
168 "the <writable> component for `plugin-mount` is not valid bool",
169 )
170 })?,
171 };
172
173 Ok(BindMount { src, dst, writable })
174 }
175
176 #[cfg(test)]
177 mod tests {
178 use super::*;
179
180 #[test]
parse_plugin_mount_invalid()181 fn parse_plugin_mount_invalid() {
182 "".parse::<BindMount>().expect_err("parse should fail");
183 "/dev/null:/dev/null:true:false"
184 .parse::<BindMount>()
185 .expect_err("parse should fail because too many arguments");
186
187 "null:/dev/null:true"
188 .parse::<BindMount>()
189 .expect_err("parse should fail because source is not absolute");
190 "/dev/null:null:true"
191 .parse::<BindMount>()
192 .expect_err("parse should fail because source is not absolute");
193 "/dev/null:null:blah"
194 .parse::<BindMount>()
195 .expect_err("parse should fail because flag is not boolean");
196 }
197
198 #[test]
parse_plugin_mount_valid()199 fn parse_plugin_mount_valid() {
200 let opt: BindMount = "/dev/null:/dev/zero:true".parse().unwrap();
201
202 assert_eq!(opt.src, PathBuf::from("/dev/null"));
203 assert_eq!(opt.dst, PathBuf::from("/dev/zero"));
204 assert!(opt.writable);
205 }
206
207 #[test]
parse_plugin_mount_valid_shorthand()208 fn parse_plugin_mount_valid_shorthand() {
209 let opt: BindMount = "/dev/null".parse().unwrap();
210 assert_eq!(opt.dst, PathBuf::from("/dev/null"));
211 assert!(!opt.writable);
212
213 let opt: BindMount = "/dev/null:/dev/zero".parse().unwrap();
214 assert_eq!(opt.dst, PathBuf::from("/dev/zero"));
215 assert!(!opt.writable);
216
217 let opt: BindMount = "/dev/null::true".parse().unwrap();
218 assert_eq!(opt.dst, PathBuf::from("/dev/null"));
219 assert!(opt.writable);
220 }
221
222 #[test]
parse_plugin_gid_map_valid()223 fn parse_plugin_gid_map_valid() {
224 let opt: GidMap = "1:2:3".parse().expect("parse should succeed");
225 assert_eq!(opt.inner, 1);
226 assert_eq!(opt.outer, 2);
227 assert_eq!(opt.count, 3);
228 }
229
230 #[test]
parse_plugin_gid_map_valid_shorthand()231 fn parse_plugin_gid_map_valid_shorthand() {
232 let opt: GidMap = "1".parse().expect("parse should succeed");
233 assert_eq!(opt.inner, 1);
234 assert_eq!(opt.outer, 1);
235 assert_eq!(opt.count, 1);
236
237 let opt: GidMap = "1:2".parse().expect("parse should succeed");
238 assert_eq!(opt.inner, 1);
239 assert_eq!(opt.outer, 2);
240 assert_eq!(opt.count, 1);
241
242 let opt: GidMap = "1::3".parse().expect("parse should succeed");
243 assert_eq!(opt.inner, 1);
244 assert_eq!(opt.outer, 1);
245 assert_eq!(opt.count, 3);
246 }
247
248 #[test]
parse_plugin_gid_map_invalid()249 fn parse_plugin_gid_map_invalid() {
250 "".parse::<GidMap>().expect_err("parse should fail");
251 "1:2:3:4"
252 .parse::<GidMap>()
253 .expect_err("parse should fail because too many arguments");
254 "blah:2:3"
255 .parse::<GidMap>()
256 .expect_err("parse should fail because inner is not a number");
257 "1:blah:3"
258 .parse::<GidMap>()
259 .expect_err("parse should fail because outer is not a number");
260 "1:2:blah"
261 .parse::<GidMap>()
262 .expect_err("parse should fail because count is not a number");
263 }
264 }
265