Skip to main content

libfuse_fs/util/
mapping.rs

1use itertools::Itertools;
2use std::{fs, str::FromStr};
3
4/// Represents a single UID or GID map entry.
5///
6/// Format: `host_id:to_id:len`
7#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
8pub struct IdMapEntry {
9    pub host: u32,
10    pub to: u32,
11    pub len: u32,
12}
13
14#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
15pub struct IdMappings {
16    pub uid_map: Vec<IdMapEntry>,
17    pub gid_map: Vec<IdMapEntry>,
18
19    /// Fallback UID used when no mapping is found.
20    /// Typically read from `/proc/sys/kernel/overflowuid`.
21    overflow_uid: u32,
22    /// Fallback GID used when no mapping is found.
23    /// Typically read from `/proc/sys/kernel/overflowgid`.
24    overflow_gid: u32,
25}
26
27impl IdMappings {
28    pub fn new(uid_map: Vec<IdMapEntry>, gid_map: Vec<IdMapEntry>) -> Self {
29        let overflow_uid = fs::read_to_string("/proc/sys/kernel/overflowuid")
30            .ok()
31            .and_then(|s| s.trim().parse().ok())
32            .unwrap_or_default();
33        let overflow_gid = fs::read_to_string("/proc/sys/kernel/overflowgid")
34            .ok()
35            .and_then(|s| s.trim().parse().ok())
36            .unwrap_or_default();
37        IdMappings {
38            uid_map,
39            gid_map,
40            overflow_uid,
41            overflow_gid,
42        }
43    }
44
45    /// Parses a colon-separated string in the format `host:to:len[:host2:to2:len2...]`
46    /// into a vector of `IdMapEntry` structs.
47    ///
48    /// # Arguments
49    ///
50    /// * `mapping` - A string slice containing the mapping(s) in the format `host:to:len[:host2:to2:len2...]`.
51    ///
52    /// # Returns
53    ///
54    /// * `Ok(Vec<IdMapEntry>)` if the input string is valid and successfully parsed.
55    /// * `Err(String)` if the format is invalid (e.g., the number of fields is not a multiple of 3, or parsing fails).
56    fn read_mappings(mapping: &str) -> Result<Vec<IdMapEntry>, String> {
57        let parts: Vec<&str> = mapping.split(':').collect();
58        if !parts.len().is_multiple_of(3) {
59            return Err(format!(
60                "Invalid mapping specified: '{mapping}'. The number of fields must be a multiple of 3.",
61            ));
62        }
63        parts
64            .into_iter()
65            .tuples()
66            .map(|(host, to, len)| {
67                let host: u32 = host
68                    .parse()
69                    .map_err(|e| format!("Invalid host id in mapping: {e}"))?;
70                let to: u32 = to
71                    .parse()
72                    .map_err(|e| format!("Invalid to id in mapping: {e}"))?;
73                let len: u32 = len
74                    .parse()
75                    .map_err(|e| format!("Invalid length in mapping: {e}"))?;
76                if len == 0 {
77                    return Err("Length in mapping cannot be zero".to_string());
78                }
79                Ok(IdMapEntry { host, to, len })
80            })
81            .collect::<Result<Vec<_>, _>>()
82    }
83
84    /// Finds the mapped ID based on the provided mappings.
85    ///
86    /// If no mapping is found, returns the original ID.
87    ///
88    /// - `direct` is `true`: Reverse mapping (Host -> Container).
89    /// - `direct` is `false`: Forward mapping (Container -> Host).
90    pub fn find_mapping(&self, id: u32, direct: bool, uid: bool) -> u32 {
91        let map = if uid { &self.uid_map } else { &self.gid_map };
92        if map.is_empty() {
93            return id;
94        }
95        for entry in map {
96            if direct {
97                // Reverse mapping: check if id is in host range
98                if id >= entry.host && id < entry.host + entry.len {
99                    return entry.to + (id - entry.host);
100                }
101            } else {
102                // Forward mapping: check if id is in container range
103                if id >= entry.to && id < entry.to + entry.len {
104                    return entry.host + (id - entry.to);
105                }
106            }
107        }
108
109        if uid {
110            self.overflow_uid
111        } else {
112            self.overflow_gid
113        }
114    }
115
116    /// Gets the host UID from a container UID (Forward mapping).
117    pub fn get_uid(&self, container_id: u32) -> u32 {
118        self.find_mapping(container_id, false, true)
119    }
120
121    /// Gets the host GID from a container GID (Forward mapping).
122    pub fn get_gid(&self, container_id: u32) -> u32 {
123        self.find_mapping(container_id, false, false)
124    }
125}
126
127impl FromStr for IdMappings {
128    type Err = String;
129
130    fn from_str(s: &str) -> Result<Self, Self::Err> {
131        let parts: Vec<&str> = s.split(',').collect();
132        if parts.len() != 2 {
133            return Err("Invalid mapping format. Expected 'uidmapping=,gidmapping='".to_string());
134        }
135        let uid_mappings_str = parts[0]
136            .strip_prefix("uidmapping=")
137            .ok_or_else(|| "Invalid uidmapping format: missing 'uidmapping=' prefix".to_string())?;
138
139        let gid_mappings_str = parts[1]
140            .strip_prefix("gidmapping=")
141            .ok_or_else(|| "Invalid gidmapping format: missing 'gidmapping=' prefix".to_string())?;
142
143        let uid_map = IdMappings::read_mappings(uid_mappings_str)?;
144        let gid_map = IdMappings::read_mappings(gid_mappings_str)?;
145        Ok(IdMappings::new(uid_map, gid_map))
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use nix::unistd::{getgid, getuid};
152
153    use crate::util::mapping::IdMappings;
154
155    #[test]
156    fn test_parse_mappings() {
157        let cur_uid = getuid().as_raw();
158        let cur_gid = getgid().as_raw();
159        let mapping = format!("uidmapping={cur_uid}:1000:1,gidmapping={cur_gid}:1000:1");
160        let id_mappings: IdMappings = mapping.parse().unwrap();
161        assert_eq!(id_mappings.uid_map.len(), 1);
162        assert_eq!(id_mappings.gid_map.len(), 1);
163        assert_eq!(id_mappings.uid_map[0].host, cur_uid);
164        assert_eq!(id_mappings.uid_map[0].to, 1000);
165        assert_eq!(id_mappings.uid_map[0].len, 1);
166        assert_eq!(id_mappings.gid_map[0].host, cur_gid);
167        assert_eq!(id_mappings.gid_map[0].to, 1000);
168        assert_eq!(id_mappings.gid_map[0].len, 1);
169
170        let mapping =
171            format!("uidmapping={cur_uid}:1000:1:0:65534:1,gidmapping={cur_gid}:1000:1:0:65534:1");
172        let id_mappings: IdMappings = mapping.parse().unwrap();
173        assert_eq!(id_mappings.uid_map.len(), 2);
174        assert_eq!(id_mappings.gid_map.len(), 2);
175        assert_eq!(id_mappings.uid_map[0].host, cur_uid);
176        assert_eq!(id_mappings.uid_map[0].to, 1000);
177        assert_eq!(id_mappings.uid_map[0].len, 1);
178        assert_eq!(id_mappings.uid_map[1].host, 0);
179        assert_eq!(id_mappings.uid_map[1].to, 65534);
180        assert_eq!(id_mappings.uid_map[1].len, 1);
181        assert_eq!(id_mappings.gid_map[0].host, cur_gid);
182        assert_eq!(id_mappings.gid_map[0].to, 1000);
183        assert_eq!(id_mappings.gid_map[0].len, 1);
184        assert_eq!(id_mappings.gid_map[1].host, 0);
185        assert_eq!(id_mappings.gid_map[1].to, 65534);
186        assert_eq!(id_mappings.gid_map[1].len, 1);
187    }
188}