Skip to main content

zub/namespace/
proc.rs

1use std::path::Path;
2
3use crate::error::{Error, Result};
4use crate::namespace::MapEntry;
5
6/// parse /proc/self/uid_map or gid_map format
7/// format: "inside_start outside_start count" per line, whitespace separated
8pub fn parse_id_map(content: &str) -> Result<Vec<MapEntry>> {
9    let mut entries = Vec::new();
10
11    for line in content.lines() {
12        let line = line.trim();
13        if line.is_empty() {
14            continue;
15        }
16
17        let parts: Vec<&str> = line.split_whitespace().collect();
18        if parts.len() != 3 {
19            continue; // skip malformed lines
20        }
21
22        let inside_start: u32 = parts[0].parse().map_err(|_| {
23            Error::NamespaceParseError(Path::new("/proc/self/uid_map").to_path_buf())
24        })?;
25        let outside_start: u32 = parts[1].parse().map_err(|_| {
26            Error::NamespaceParseError(Path::new("/proc/self/uid_map").to_path_buf())
27        })?;
28        let count: u32 = parts[2].parse().map_err(|_| {
29            Error::NamespaceParseError(Path::new("/proc/self/uid_map").to_path_buf())
30        })?;
31
32        entries.push(MapEntry::new(inside_start, outside_start, count));
33    }
34
35    Ok(entries)
36}
37
38/// read current process uid map from /proc/self/uid_map
39pub fn current_uid_map() -> Result<Vec<MapEntry>> {
40    let path = Path::new("/proc/self/uid_map");
41    let content = std::fs::read_to_string(path).map_err(|e| Error::Io {
42        path: path.to_path_buf(),
43        source: e,
44    })?;
45    parse_id_map(&content)
46}
47
48/// read current process gid map from /proc/self/gid_map
49pub fn current_gid_map() -> Result<Vec<MapEntry>> {
50    let path = Path::new("/proc/self/gid_map");
51    let content = std::fs::read_to_string(path).map_err(|e| Error::Io {
52        path: path.to_path_buf(),
53        source: e,
54    })?;
55    parse_id_map(&content)
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_parse_identity_mapping() {
64        // real root outside any namespace
65        let content = "         0          0 4294967295\n";
66        let entries = parse_id_map(content).unwrap();
67        assert_eq!(entries.len(), 1);
68        assert_eq!(entries[0].inside_start, 0);
69        assert_eq!(entries[0].outside_start, 0);
70        assert_eq!(entries[0].count, 4294967295);
71    }
72
73    #[test]
74    fn test_parse_podman_mapping() {
75        // typical podman unshare mapping
76        let content = "         0       1000          1\n         1     100000      65536\n";
77        let entries = parse_id_map(content).unwrap();
78        assert_eq!(entries.len(), 2);
79
80        assert_eq!(entries[0].inside_start, 0);
81        assert_eq!(entries[0].outside_start, 1000);
82        assert_eq!(entries[0].count, 1);
83
84        assert_eq!(entries[1].inside_start, 1);
85        assert_eq!(entries[1].outside_start, 100000);
86        assert_eq!(entries[1].count, 65536);
87    }
88
89    #[test]
90    fn test_parse_empty() {
91        let entries = parse_id_map("").unwrap();
92        assert!(entries.is_empty());
93    }
94
95    #[test]
96    fn test_parse_whitespace_only() {
97        let entries = parse_id_map("   \n\n  \n").unwrap();
98        assert!(entries.is_empty());
99    }
100
101    #[test]
102    fn test_parse_multiple_ranges() {
103        let content = "0 1000 1\n1 100000 10000\n10001 200000 50000\n";
104        let entries = parse_id_map(content).unwrap();
105        assert_eq!(entries.len(), 3);
106    }
107}