xref: /aosp_15_r20/external/crosvm/src/crosvm/plugin/config.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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