container_device_interface/
container_edits_unix.rs1use std::{
2 fmt,
3 io::{Error, ErrorKind},
4 os::unix::fs::{FileTypeExt, MetadataExt},
5 path::Path,
6};
7
8use anyhow::Result;
9
10pub enum DeviceType {
11 Block,
12 Char,
13 Fifo,
14}
15
16impl fmt::Display for DeviceType {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 let s = match self {
19 DeviceType::Block => "b",
20 DeviceType::Char => "c",
21 DeviceType::Fifo => "p",
22 };
23 write!(f, "{}", s)
24 }
25}
26
27pub fn device_info_from_path<P: AsRef<Path>>(path: P) -> Result<(String, i64, i64)> {
30 let major = |dev: u64| -> i64 { (dev >> 8) as i64 & 0xff };
31 let minor = |dev: u64| -> i64 { dev as i64 & 0xff };
32
33 let metadata = std::fs::metadata(path)?;
34 let file_type = metadata.file_type();
35
36 let (dev_type, major, minor) = if file_type.is_block_device() {
37 (
38 DeviceType::Block.to_string(),
39 major(metadata.rdev()),
40 minor(metadata.rdev()),
41 )
42 } else if file_type.is_char_device() {
43 (
44 DeviceType::Char.to_string(),
45 major(metadata.rdev()),
46 minor(metadata.rdev()),
47 )
48 } else if file_type.is_fifo() {
49 (DeviceType::Fifo.to_string(), 0, 0)
50 } else {
51 return Err(Error::new(ErrorKind::InvalidInput, "It's not a device node").into());
52 };
53
54 Ok((dev_type, major, minor))
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 use std::ffi::CString;
62
63 use anyhow::Result;
64 use nix::libc::{self, dev_t, mknodat, mode_t, S_IFBLK, S_IFCHR, S_IFIFO};
65 use tempfile::TempDir;
66
67 use crate::{container_edits::DeviceNode, specs::config::DeviceNode as CDIDeviceNode};
68
69 fn is_root() -> bool {
70 unsafe { libc::geteuid() == 0 }
71 }
72
73 fn create_device(path: &str, mode: mode_t, dev: u64, dev_type: &str) -> Result<()> {
74 let path_c = CString::new(path)?;
75
76 let mode = match dev_type {
78 "b" => mode | S_IFBLK,
79 "c" => mode | S_IFCHR,
80 "p" => mode | S_IFIFO,
81 _ => 0,
82 };
83
84 let res = unsafe { mknodat(libc::AT_FDCWD, path_c.as_ptr(), mode, dev as dev_t) };
86 if res < 0 {
87 println!("create device with path: {:?} failed", path);
88 return Err(nix::Error::last().into());
89 }
90
91 println!("create device with path: {:?} successfully", path);
92 Ok(())
93 }
94
95 #[test]
96 fn test_fill_missing_info_block_device() {
97 if !is_root() {
98 println!("INFO: skipping, needs root");
99 return;
100 }
101
102 let temp_dir = TempDir::new().unwrap();
103 let block_device_path = temp_dir.path().join("block_device").display().to_string();
104
105 let res = create_device(&block_device_path, 0o666, 0x0101, "b");
107 assert!(res.is_ok(), "Failed to create block device: {:?}", res);
108
109 let mut dev_node = DeviceNode {
110 node: CDIDeviceNode {
111 path: block_device_path,
112 ..Default::default()
113 },
114 };
115
116 assert!(dev_node.fill_missing_info().is_ok());
117
118 assert_eq!(dev_node.node.r#type, Some(DeviceType::Block.to_string()));
119 assert_eq!(dev_node.node.major, Some(1));
120 assert_eq!(dev_node.node.minor, Some(1));
121 }
122
123 #[test]
124 fn test_fill_missing_info_char_device() {
125 if !is_root() {
126 println!("INFO: skipping, needs root");
127 return;
128 }
129
130 let temp_dir = TempDir::new().unwrap();
131 let char_device_path = temp_dir.path().join("char_device").display().to_string();
132
133 let res = create_device(&char_device_path, 0o666, 0x0202, "c");
135 assert!(res.is_ok(), "Failed to create char device: {:?}", res);
136
137 let mut dev_node = DeviceNode {
138 node: CDIDeviceNode {
139 path: char_device_path,
140 ..Default::default()
141 },
142 };
143
144 assert!(dev_node.fill_missing_info().is_ok());
145
146 assert_eq!(dev_node.node.r#type, Some(DeviceType::Char.to_string()));
147 assert_eq!(dev_node.node.major, Some(1));
148 assert_eq!(dev_node.node.minor, Some(2));
149 }
150
151 #[test]
152 fn test_fill_missing_info_fifo() {
153 if !is_root() {
154 println!("INFO: skipping which needs root");
155 return;
156 }
157
158 let temp_dir = TempDir::new().unwrap();
159 let fifo_device_path = temp_dir.path().join("fifo_device").display().to_string();
160
161 let res = create_device(&fifo_device_path, 0o666, 0x0, "p");
163 assert!(res.is_ok(), "Failed to create fifo device: {:?}", res);
164
165 let mut dev_node = DeviceNode {
166 node: CDIDeviceNode {
167 path: fifo_device_path,
168 ..Default::default()
169 },
170 };
171
172 dev_node.fill_missing_info().unwrap();
173
174 assert_eq!(dev_node.node.r#type, Some(DeviceType::Fifo.to_string()));
175 assert_eq!(dev_node.node.major, None);
176 assert_eq!(dev_node.node.minor, None);
177 }
178
179 #[test]
180 fn test_fill_missing_info_regular_file() {
181 let temp_dir = TempDir::new().unwrap();
182 let file_path = temp_dir.path().join("regular_file");
183 std::fs::File::create(&file_path).unwrap();
184
185 let mut dev_node = DeviceNode {
186 node: CDIDeviceNode {
187 path: file_path.to_string_lossy().to_string(),
188 ..Default::default()
189 },
190 };
191
192 let result = dev_node.fill_missing_info();
193 assert!(result.is_err());
194 }
195}