fuse_backend_rs/api/filesystem/overlay.rs
1// Copyright (C) 2023 Ant Group. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE-BSD-3-Clause file.
4
5#![allow(missing_docs)]
6
7use std::ffi::{CStr, CString};
8use std::io::{Error, ErrorKind, Result};
9
10use super::{Context, Entry, FileSystem, GetxattrReply};
11use crate::abi::fuse_abi::stat64;
12
13pub const OPAQUE_XATTR_LEN: u32 = 16;
14pub const OPAQUE_XATTR: &str = "user.fuseoverlayfs.opaque";
15pub const UNPRIVILEGED_OPAQUE_XATTR: &str = "user.overlay.opaque";
16pub const PRIVILEGED_OPAQUE_XATTR: &str = "trusted.overlay.opaque";
17
18/// A filesystem must implement Layer trait, or it cannot be used as an OverlayFS layer.
19pub trait Layer: FileSystem {
20 /// Return the root inode number
21 fn root_inode(&self) -> Self::Inode;
22
23 /// Create whiteout file with name <name>.
24 ///
25 /// If this call is successful then the lookup count of the `Inode` associated with the returned
26 /// `Entry` must be increased by 1.
27 fn create_whiteout(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> Result<Entry> {
28 // Use temp value to avoid moved 'parent'.
29 let ino: u64 = parent.into();
30 match self.lookup(ctx, ino.into(), name) {
31 Ok(v) => {
32 // Find whiteout char dev.
33 if is_whiteout(v.attr) {
34 return Ok(v);
35 }
36 // Non-negative entry with inode larger than 0 indicates file exists.
37 if v.inode != 0 {
38 // Decrease the refcount.
39 self.forget(ctx, v.inode.into(), 1);
40 // File exists with same name, create whiteout file is not allowed.
41 return Err(Error::from_raw_os_error(libc::EEXIST));
42 }
43 }
44 Err(e) => match e.raw_os_error() {
45 Some(raw_error) => {
46 // We expect ENOENT error.
47 if raw_error != libc::ENOENT {
48 return Err(e);
49 }
50 }
51 None => return Err(e),
52 },
53 }
54
55 // Try to create whiteout char device with 0/0 device number.
56 let dev = libc::makedev(0, 0);
57 let mode = libc::S_IFCHR | 0o777;
58 self.mknod(ctx, ino.into(), name, mode, dev as u32, 0)
59 }
60
61 /// Delete whiteout file with name <name>.
62 fn delete_whiteout(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> Result<()> {
63 // Use temp value to avoid moved 'parent'.
64 let ino: u64 = parent.into();
65 match self.lookup(ctx, ino.into(), name) {
66 Ok(v) => {
67 if v.inode != 0 {
68 // Decrease the refcount since we make a lookup call.
69 self.forget(ctx, v.inode.into(), 1);
70 }
71
72 // Find whiteout so we can safely delete it.
73 if is_whiteout(v.attr) {
74 return self.unlink(ctx, ino.into(), name);
75 }
76 // Non-negative entry with inode larger than 0 indicates file exists.
77 if v.inode != 0 {
78 // File exists but not whiteout file.
79 return Err(Error::from_raw_os_error(libc::EINVAL));
80 }
81 }
82 Err(e) => match e.raw_os_error() {
83 Some(raw_error) => {
84 // ENOENT is acceptable.
85 if raw_error != libc::ENOENT {
86 return Err(e);
87 }
88 }
89 None => return Err(e),
90 },
91 }
92 Ok(())
93 }
94
95 /// Check if the Inode is a whiteout file
96 fn is_whiteout(&self, ctx: &Context, inode: Self::Inode) -> Result<bool> {
97 let (st, _) = self.getattr(ctx, inode, None)?;
98
99 // Check attributes of the inode to see if it's a whiteout char device.
100 Ok(is_whiteout(st))
101 }
102
103 /// Set the directory to opaque.
104 fn set_opaque(&self, ctx: &Context, inode: Self::Inode) -> Result<()> {
105 // Use temp value to avoid moved 'parent'.
106 let ino: u64 = inode.into();
107
108 // Get attributes and check if it's directory.
109 let (st, _d) = self.getattr(ctx, ino.into(), None)?;
110 if !is_dir(st) {
111 // Only directory can be set to opaque.
112 return Err(Error::from_raw_os_error(libc::ENOTDIR));
113 }
114 // A directory is made opaque by setting the xattr "trusted.overlay.opaque" to "y".
115 // See ref: https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
116 self.setxattr(
117 ctx,
118 ino.into(),
119 to_cstring(OPAQUE_XATTR)?.as_c_str(),
120 b"y",
121 0,
122 )
123 }
124
125 /// Check if the directory is opaque.
126 fn is_opaque(&self, ctx: &Context, inode: Self::Inode) -> Result<bool> {
127 // Use temp value to avoid moved 'parent'.
128 let ino: u64 = inode.into();
129
130 // Get attributes of the directory.
131 let (st, _d) = self.getattr(ctx, ino.into(), None)?;
132 if !is_dir(st) {
133 return Err(Error::from_raw_os_error(libc::ENOTDIR));
134 }
135
136 // Return Result<is_opaque>.
137 let check_attr = |inode: Self::Inode, attr_name: &str, attr_size: u32| -> Result<bool> {
138 let cname = CString::new(attr_name)?;
139 match self.getxattr(ctx, inode, cname.as_c_str(), attr_size) {
140 Ok(v) => {
141 // xattr name exists and we get value.
142 if let GetxattrReply::Value(buf) = v {
143 if buf.len() == 1 && buf[0].to_ascii_lowercase() == b'y' {
144 return Ok(true);
145 }
146 }
147 // No value found, go on to next check.
148 Ok(false)
149 }
150 Err(e) => {
151 if let Some(raw_error) = e.raw_os_error() {
152 if raw_error == libc::ENODATA {
153 return Ok(false);
154 }
155 }
156
157 Err(e)
158 }
159 }
160 };
161
162 // A directory is made opaque by setting some specific xattr to "y".
163 // See ref: https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
164
165 // Check our customized version of the xattr "user.fuseoverlayfs.opaque".
166 let is_opaque = check_attr(ino.into(), OPAQUE_XATTR, OPAQUE_XATTR_LEN)?;
167 if is_opaque {
168 return Ok(true);
169 }
170
171 // Also check for the unprivileged version of the xattr "trusted.overlay.opaque".
172 let is_opaque = check_attr(ino.into(), PRIVILEGED_OPAQUE_XATTR, OPAQUE_XATTR_LEN)?;
173 if is_opaque {
174 return Ok(true);
175 }
176
177 // Also check for the unprivileged version of the xattr "user.overlay.opaque".
178 let is_opaque = check_attr(ino.into(), UNPRIVILEGED_OPAQUE_XATTR, OPAQUE_XATTR_LEN)?;
179 if is_opaque {
180 return Ok(true);
181 }
182
183 Ok(false)
184 }
185}
186
187pub(crate) fn is_dir(st: stat64) -> bool {
188 st.st_mode & libc::S_IFMT == libc::S_IFDIR
189}
190
191pub(crate) fn is_chardev(st: stat64) -> bool {
192 st.st_mode & libc::S_IFMT == libc::S_IFCHR
193}
194
195pub(crate) fn is_whiteout(st: stat64) -> bool {
196 // A whiteout is created as a character device with 0/0 device number.
197 // See ref: https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
198 let major = unsafe { libc::major(st.st_rdev) };
199 let minor = unsafe { libc::minor(st.st_rdev) };
200 is_chardev(st) && major == 0 && minor == 0
201}
202
203pub(crate) fn to_cstring(name: &str) -> Result<CString> {
204 CString::new(name).map_err(|e| Error::new(ErrorKind::InvalidData, e))
205}