nydus_builder/core/
overlay.rs1use std::ffi::{OsStr, OsString};
10use std::fmt::{self, Display, Formatter};
11use std::os::unix::ffi::OsStrExt;
12use std::str::FromStr;
13
14use anyhow::{anyhow, Error, Result};
15
16use super::node::Node;
17
18pub const OCISPEC_WHITEOUT_PREFIX: &str = ".wh.";
20pub const OCISPEC_WHITEOUT_OPAQUE: &str = ".wh..wh..opq";
22pub const OVERLAYFS_WHITEOUT_OPAQUE: &str = "trusted.overlay.opaque";
24
25#[derive(Clone, Copy, PartialEq)]
61pub enum WhiteoutSpec {
62 Oci,
66 Overlayfs,
70 None,
72}
73
74impl fmt::Display for WhiteoutSpec {
75 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
76 match self {
77 WhiteoutSpec::Oci => write!(f, "oci"),
78 WhiteoutSpec::Overlayfs => write!(f, "overlayfs"),
79 WhiteoutSpec::None => write!(f, "none"),
80 }
81 }
82}
83
84impl Default for WhiteoutSpec {
85 fn default() -> Self {
86 Self::Oci
87 }
88}
89
90impl FromStr for WhiteoutSpec {
91 type Err = Error;
92
93 fn from_str(s: &str) -> Result<Self> {
94 match s.to_lowercase().as_str() {
95 "oci" => Ok(Self::Oci),
96 "overlayfs" => Ok(Self::Overlayfs),
97 "none" => Ok(Self::None),
98 _ => Err(anyhow!("invalid whiteout spec")),
99 }
100 }
101}
102
103#[derive(Clone, Copy, Debug, PartialEq)]
105pub enum WhiteoutType {
106 OciOpaque,
107 OciRemoval,
108 OverlayFsOpaque,
109 OverlayFsRemoval,
110}
111
112impl WhiteoutType {
113 pub fn is_removal(&self) -> bool {
114 *self == WhiteoutType::OciRemoval || *self == WhiteoutType::OverlayFsRemoval
115 }
116}
117
118#[allow(dead_code)]
120#[derive(Clone, Debug, PartialEq)]
121pub enum Overlay {
122 Lower,
123 UpperAddition,
124 UpperModification,
125}
126
127impl Overlay {
128 pub fn is_lower_layer(&self) -> bool {
129 self == &Overlay::Lower
130 }
131}
132
133impl Display for Overlay {
134 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
135 match self {
136 Overlay::Lower => write!(f, "LOWER"),
137 Overlay::UpperAddition => write!(f, "ADDED"),
138 Overlay::UpperModification => write!(f, "MODIFIED"),
139 }
140 }
141}
142
143impl Node {
144 pub fn is_overlayfs_whiteout(&self, spec: WhiteoutSpec) -> bool {
146 if spec != WhiteoutSpec::Overlayfs {
147 return false;
148 }
149 self.inode.is_chrdev()
150 && nydus_utils::compact::major_dev(self.info.rdev) == 0
151 && nydus_utils::compact::minor_dev(self.info.rdev) == 0
152 }
153
154 pub fn is_overlayfs_opaque(&self, spec: WhiteoutSpec) -> bool {
156 if spec != WhiteoutSpec::Overlayfs || !self.is_dir() {
157 return false;
158 }
159
160 if let Some(v) = self
162 .info
163 .xattrs
164 .get(&OsString::from(OVERLAYFS_WHITEOUT_OPAQUE))
165 {
166 if let Ok(v) = std::str::from_utf8(v.as_slice()) {
167 return v == "y";
168 }
169 }
170
171 false
172 }
173
174 pub fn whiteout_type(&self, spec: WhiteoutSpec) -> Option<WhiteoutType> {
176 if self.overlay == Overlay::Lower {
177 return None;
178 }
179
180 match spec {
181 WhiteoutSpec::Oci => {
182 if let Some(name) = self.name().to_str() {
183 if name == OCISPEC_WHITEOUT_OPAQUE {
184 return Some(WhiteoutType::OciOpaque);
185 } else if name.starts_with(OCISPEC_WHITEOUT_PREFIX) {
186 return Some(WhiteoutType::OciRemoval);
187 }
188 }
189 }
190 WhiteoutSpec::Overlayfs => {
191 if self.is_overlayfs_whiteout(spec) {
192 return Some(WhiteoutType::OverlayFsRemoval);
193 } else if self.is_overlayfs_opaque(spec) {
194 return Some(WhiteoutType::OverlayFsOpaque);
195 }
196 }
197 WhiteoutSpec::None => {
198 return None;
199 }
200 }
201
202 None
203 }
204
205 pub fn origin_name(&self, t: WhiteoutType) -> Option<&OsStr> {
207 if let Some(name) = self.name().to_str() {
208 if t == WhiteoutType::OciRemoval {
209 return Some(OsStr::from_bytes(
211 name[OCISPEC_WHITEOUT_PREFIX.len()..].as_bytes(),
212 ));
213 } else if t == WhiteoutType::OverlayFsRemoval {
214 return Some(name.as_ref());
216 }
217 }
218
219 None
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use nydus_rafs::metadata::{inode::InodeWrapper, layout::v5::RafsV5Inode};
226
227 use crate::core::node::NodeInfo;
228
229 use super::*;
230
231 #[test]
232 fn test_white_spec_from_str() {
233 let spec = WhiteoutSpec::default();
234 assert!(matches!(spec, WhiteoutSpec::Oci));
235
236 assert!(WhiteoutSpec::from_str("oci").is_ok());
237 assert!(WhiteoutSpec::from_str("overlayfs").is_ok());
238 assert!(WhiteoutSpec::from_str("none").is_ok());
239 assert!(WhiteoutSpec::from_str("foo").is_err());
240 }
241
242 #[test]
243 fn test_white_type_removal_check() {
244 let t1 = WhiteoutType::OciOpaque;
245 let t2 = WhiteoutType::OciRemoval;
246 let t3 = WhiteoutType::OverlayFsOpaque;
247 let t4 = WhiteoutType::OverlayFsRemoval;
248 assert!(!t1.is_removal());
249 assert!(t2.is_removal());
250 assert!(!t3.is_removal());
251 assert!(t4.is_removal());
252 }
253
254 #[test]
255 fn test_overlay_low_layer_check() {
256 let t1 = Overlay::Lower;
257 let t2 = Overlay::UpperAddition;
258 let t3 = Overlay::UpperModification;
259
260 assert!(t1.is_lower_layer());
261 assert!(!t2.is_lower_layer());
262 assert!(!t3.is_lower_layer());
263 }
264
265 #[test]
266 fn test_node() {
267 let mut inode = InodeWrapper::V5(RafsV5Inode::default());
268 inode.set_mode(libc::S_IFCHR as u32);
269 let node = Node::new(inode, NodeInfo::default(), 0);
270 assert!(!node.is_overlayfs_whiteout(WhiteoutSpec::None));
271 assert!(node.is_overlayfs_whiteout(WhiteoutSpec::Overlayfs));
272 assert_eq!(
273 node.whiteout_type(WhiteoutSpec::Overlayfs).unwrap(),
274 WhiteoutType::OverlayFsRemoval
275 );
276
277 let mut inode = InodeWrapper::V5(RafsV5Inode::default());
278 let mut info: NodeInfo = NodeInfo::default();
279 assert!(info
280 .xattrs
281 .add(OVERLAYFS_WHITEOUT_OPAQUE.into(), "y".into())
282 .is_ok());
283 inode.set_mode(libc::S_IFDIR as u32);
284 let node = Node::new(inode, info, 0);
285 assert!(!node.is_overlayfs_opaque(WhiteoutSpec::None));
286 assert!(node.is_overlayfs_opaque(WhiteoutSpec::Overlayfs));
287 assert_eq!(
288 node.whiteout_type(WhiteoutSpec::Overlayfs).unwrap(),
289 WhiteoutType::OverlayFsOpaque
290 );
291
292 let mut inode = InodeWrapper::V5(RafsV5Inode::default());
293 let mut info = NodeInfo::default();
294 assert!(info
295 .xattrs
296 .add(OVERLAYFS_WHITEOUT_OPAQUE.into(), "n".into())
297 .is_ok());
298 inode.set_mode(libc::S_IFDIR as u32);
299 let node = Node::new(inode, info, 0);
300 assert!(!node.is_overlayfs_opaque(WhiteoutSpec::None));
301 assert!(!node.is_overlayfs_opaque(WhiteoutSpec::Overlayfs));
302
303 let mut inode = InodeWrapper::V5(RafsV5Inode::default());
304 let mut info = NodeInfo::default();
305 assert!(info
306 .xattrs
307 .add(OVERLAYFS_WHITEOUT_OPAQUE.into(), "y".into())
308 .is_ok());
309 inode.set_mode(libc::S_IFCHR as u32);
310 let node = Node::new(inode, info, 0);
311 assert!(!node.is_overlayfs_opaque(WhiteoutSpec::None));
312 assert!(!node.is_overlayfs_opaque(WhiteoutSpec::Overlayfs));
313
314 let mut inode = InodeWrapper::V5(RafsV5Inode::default());
315 let mut info = NodeInfo::default();
316 assert!(info
317 .xattrs
318 .add(OVERLAYFS_WHITEOUT_OPAQUE.into(), "n".into())
319 .is_ok());
320 inode.set_mode(libc::S_IFDIR as u32);
321 let node = Node::new(inode, info, 0);
322 assert!(!node.is_overlayfs_opaque(WhiteoutSpec::None));
323 assert!(!node.is_overlayfs_opaque(WhiteoutSpec::Overlayfs));
324
325 let inode = InodeWrapper::V5(RafsV5Inode::default());
326 let info = NodeInfo::default();
327 let mut node = Node::new(inode, info, 0);
328
329 assert_eq!(node.whiteout_type(WhiteoutSpec::None), None);
330 assert_eq!(node.whiteout_type(WhiteoutSpec::Oci), None);
331 assert_eq!(node.whiteout_type(WhiteoutSpec::Overlayfs), None);
332
333 node.overlay = Overlay::Lower;
334 assert_eq!(node.whiteout_type(WhiteoutSpec::Overlayfs), None);
335
336 let inode = InodeWrapper::V5(RafsV5Inode::default());
337 let mut info = NodeInfo::default();
338 let name = OCISPEC_WHITEOUT_PREFIX.to_string() + "foo";
339 info.target_vec.push(name.clone().into());
340 let node = Node::new(inode, info, 0);
341 assert_eq!(
342 node.whiteout_type(WhiteoutSpec::Oci).unwrap(),
343 WhiteoutType::OciRemoval
344 );
345 assert_eq!(node.origin_name(WhiteoutType::OciRemoval).unwrap(), "foo");
346 assert_eq!(node.origin_name(WhiteoutType::OciOpaque), None);
347 assert_eq!(
348 node.origin_name(WhiteoutType::OverlayFsRemoval).unwrap(),
349 OsStr::new(&name)
350 );
351
352 let inode = InodeWrapper::V5(RafsV5Inode::default());
353 let mut info = NodeInfo::default();
354 info.target_vec.push(OCISPEC_WHITEOUT_OPAQUE.into());
355 let node = Node::new(inode, info, 0);
356 assert_eq!(
357 node.whiteout_type(WhiteoutSpec::Oci).unwrap(),
358 WhiteoutType::OciOpaque
359 );
360 }
361}