1use async_trait::async_trait;
2use rfuse3::raw::reply::{FileAttr, ReplyCreated, ReplyXAttr};
3use rfuse3::raw::{ObjectSafeFilesystem, Request, reply::ReplyEntry};
4use rfuse3::{Inode, Result};
5use std::ffi::OsStr;
6use std::io::Error;
7use std::time::Duration;
8
9use crate::context::OperationContext;
10use crate::passthrough::PassthroughFs;
11pub const OPAQUE_XATTR_LEN: u32 = 16;
12pub const OPAQUE_XATTR: &str = "user.fuseoverlayfs.opaque";
13pub const UNPRIVILEGED_OPAQUE_XATTR: &str = "user.overlay.opaque";
14pub const PRIVILEGED_OPAQUE_XATTR: &str = "trusted.overlay.opaque";
15
16#[async_trait]
18pub trait Layer: ObjectSafeFilesystem {
19 fn root_inode(&self) -> Inode;
21 async fn create_whiteout(
26 &self,
27 ctx: Request,
28 parent: Inode,
29 name: &OsStr,
30 ) -> Result<ReplyEntry> {
31 let ino: u64 = parent;
33 match self.lookup(ctx, ino, name).await {
34 Ok(v) => {
35 if is_whiteout(&v.attr) {
37 return Ok(v);
38 }
39 if v.attr.ino != 0 {
41 self.forget(ctx, v.attr.ino, 1).await;
43 return Err(Error::from_raw_os_error(libc::EEXIST).into());
45 }
46 }
47 Err(e) => {
48 let e: std::io::Error = e.into();
49 match e.raw_os_error() {
50 Some(raw_error) => {
51 if raw_error != libc::ENOENT {
53 return Err(e.into());
54 }
55 }
56 None => return Err(e.into()),
57 }
58 }
59 }
60
61 let dev = libc::makedev(0, 0);
63 let mode = libc::S_IFCHR | 0o777;
64 self.mknod(ctx, ino, name, mode, dev as u32).await
65 }
66
67 async fn delete_whiteout(&self, ctx: Request, parent: Inode, name: &OsStr) -> Result<()> {
69 let ino: u64 = parent;
71 match self.lookup(ctx, ino, name).await {
72 Ok(v) => {
73 if v.attr.ino != 0 {
74 self.forget(ctx, v.attr.ino, 1).await;
76 }
77
78 if is_whiteout(&v.attr) {
80 return self.unlink(ctx, ino, name).await;
81 }
82 if v.attr.ino != 0 {
84 return Err(Error::from_raw_os_error(libc::EINVAL).into());
86 }
87 }
88 Err(e) => return Err(e),
89 }
90 Ok(())
91 }
92
93 async fn is_whiteout(&self, ctx: Request, inode: Inode) -> Result<bool> {
95 let rep = self.getattr(ctx, inode, None, 0).await?;
96
97 Ok(is_whiteout(&rep.attr))
99 }
100
101 async fn set_opaque(&self, ctx: Request, inode: Inode) -> Result<()> {
103 let ino: u64 = inode;
105
106 let rep = self.getattr(ctx, ino, None, 0).await?;
108 if !is_dir(&rep.attr) {
109 return Err(Error::from_raw_os_error(libc::ENOTDIR).into());
111 }
112 self.setxattr(ctx, ino, OsStr::new(OPAQUE_XATTR), b"y", 0, 0)
115 .await
116 }
117
118 async fn is_opaque(&self, ctx: Request, inode: Inode) -> Result<bool> {
120 let ino: u64 = inode;
122
123 let attr: rfuse3::raw::prelude::ReplyAttr = self.getattr(ctx, ino, None, 0).await?;
125 if !is_dir(&attr.attr) {
126 return Err(Error::from_raw_os_error(libc::ENOTDIR).into());
127 }
128
129 let check_attr = |inode: Inode, attr_name: &'static str, attr_size: u32| async move {
131 let cname = OsStr::new(attr_name);
132 match self.getxattr(ctx, inode, cname, attr_size).await {
133 Ok(v) => {
134 if let ReplyXAttr::Data(bufs) = v
136 && bufs.len() == 1
137 && bufs[0].eq_ignore_ascii_case(&b'y')
138 {
139 return Ok(true);
140 }
141 Ok(false)
143 }
144 Err(e) => {
145 let ioerror: std::io::Error = e.into();
146 if let Some(raw_error) = ioerror.raw_os_error()
147 && raw_error == libc::ENODATA
148 {
149 return Ok(false);
150 }
151
152 Err(e)
153 }
154 }
155 };
156
157 let is_opaque = check_attr(ino, OPAQUE_XATTR, OPAQUE_XATTR_LEN).await?;
162 if is_opaque {
163 return Ok(true);
164 }
165
166 let is_opaque = check_attr(ino, PRIVILEGED_OPAQUE_XATTR, OPAQUE_XATTR_LEN).await?;
168 if is_opaque {
169 return Ok(true);
170 }
171
172 let is_opaque = check_attr(ino, UNPRIVILEGED_OPAQUE_XATTR, OPAQUE_XATTR_LEN).await?;
174 if is_opaque {
175 return Ok(true);
176 }
177
178 Ok(false)
179 }
180
181 async fn create_with_context(
184 &self,
185 _ctx: OperationContext,
186 _parent: Inode,
187 _name: &OsStr,
188 _mode: u32,
189 _flags: u32,
190 ) -> Result<ReplyCreated> {
191 Err(Error::from_raw_os_error(libc::ENOSYS).into())
192 }
193
194 async fn mkdir_with_context(
197 &self,
198 _ctx: OperationContext,
199 _parent: Inode,
200 _name: &OsStr,
201 _mode: u32,
202 _umask: u32,
203 ) -> Result<ReplyEntry> {
204 Err(Error::from_raw_os_error(libc::ENOSYS).into())
205 }
206
207 async fn symlink_with_context(
210 &self,
211 _ctx: OperationContext,
212 _parent: Inode,
213 _name: &OsStr,
214 _link: &OsStr,
215 ) -> Result<ReplyEntry> {
216 Err(Error::from_raw_os_error(libc::ENOSYS).into())
217 }
218
219 async fn do_getattr_helper(
222 &self,
223 _inode: Inode,
224 _handle: Option<u64>,
225 ) -> std::io::Result<(libc::stat64, Duration)> {
226 Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
227 }
228}
229#[async_trait]
230impl Layer for PassthroughFs {
231 fn root_inode(&self) -> Inode {
232 1
233 }
234
235 async fn create_with_context(
236 &self,
237 ctx: OperationContext,
238 parent: Inode,
239 name: &OsStr,
240 mode: u32,
241 flags: u32,
242 ) -> Result<ReplyCreated> {
243 PassthroughFs::do_create_helper(
244 self,
245 ctx.req,
246 parent,
247 name,
248 mode,
249 flags,
250 ctx.uid.unwrap_or(ctx.req.uid),
251 ctx.gid.unwrap_or(ctx.req.gid),
252 )
253 .await
254 }
255
256 async fn mkdir_with_context(
257 &self,
258 ctx: OperationContext,
259 parent: Inode,
260 name: &OsStr,
261 mode: u32,
262 umask: u32,
263 ) -> Result<ReplyEntry> {
264 PassthroughFs::do_mkdir_helper(
265 self,
266 ctx.req,
267 parent,
268 name,
269 mode,
270 umask,
271 ctx.uid.unwrap_or(ctx.req.uid),
272 ctx.gid.unwrap_or(ctx.req.gid),
273 )
274 .await
275 }
276
277 async fn symlink_with_context(
278 &self,
279 ctx: OperationContext,
280 parent: Inode,
281 name: &OsStr,
282 link: &OsStr,
283 ) -> Result<ReplyEntry> {
284 PassthroughFs::do_symlink_helper(
285 self,
286 ctx.req,
287 parent,
288 name,
289 link,
290 ctx.uid.unwrap_or(ctx.req.uid),
291 ctx.gid.unwrap_or(ctx.req.gid),
292 )
293 .await
294 }
295
296 async fn do_getattr_helper(
297 &self,
298 inode: Inode,
299 handle: Option<u64>,
300 ) -> std::io::Result<(libc::stat64, Duration)> {
301 PassthroughFs::do_getattr_helper(self, inode, handle).await
302 }
303}
304pub(crate) fn is_dir(st: &FileAttr) -> bool {
305 st.kind.const_into_mode_t() & libc::S_IFMT == libc::S_IFDIR
306}
307
308pub(crate) fn is_chardev(st: &FileAttr) -> bool {
309 st.kind.const_into_mode_t() & libc::S_IFMT == libc::S_IFCHR
310}
311
312pub(crate) fn is_whiteout(st: &FileAttr) -> bool {
313 let major = libc::major(st.rdev.into());
316 let minor = libc::minor(st.rdev.into());
317 is_chardev(st) && major == 0 && minor == 0
318}
319
320#[cfg(test)]
321mod test {
322 use std::{ffi::OsStr, path::PathBuf};
323
324 use rfuse3::raw::{Filesystem as _, Request};
325
326 use crate::{
327 passthrough::{PassthroughArgs, new_passthroughfs_layer},
328 unionfs::layer::Layer,
329 unwrap_or_skip_eperm,
330 };
331
332 #[ignore]
334 #[tokio::test]
335 async fn test_whiteout_create_delete() {
336 let temp_dir = "/tmp/test_whiteout/t2";
337 let rootdir = PathBuf::from(temp_dir);
338 std::fs::create_dir_all(&rootdir).unwrap();
339 if std::env::var("RUN_PRIVILEGED_TESTS").ok().as_deref() != Some("1") {
340 eprintln!("skip test_whiteout_create_delete: RUN_PRIVILEGED_TESTS!=1");
341 return;
342 }
343 let fs = unwrap_or_skip_eperm!(
344 new_passthroughfs_layer(PassthroughArgs {
345 root_dir: rootdir,
346 mapping: None::<&str>
347 })
348 .await,
349 "init passthrough layer"
350 );
351 let _ = unwrap_or_skip_eperm!(fs.init(Request::default()).await, "fs init");
352 let white_name = OsStr::new(&"test");
353 let res = unwrap_or_skip_eperm!(
354 fs.create_whiteout(Request::default(), 1, white_name).await,
355 "create whiteout"
356 );
357
358 print!("{res:?}");
359 let res = fs.delete_whiteout(Request::default(), 1, white_name).await;
360 if res.is_err() {
361 panic!("{res:?}");
362 }
363 let _ = fs.destroy(Request::default()).await;
364 }
365
366 #[tokio::test]
367 async fn test_is_opaque_on_non_directory() {
368 let temp_dir = "/tmp/test_opaque_non_dir/t2";
369 let rootdir = PathBuf::from(temp_dir);
370 std::fs::create_dir_all(&rootdir).unwrap();
371 if std::env::var("RUN_PRIVILEGED_TESTS").ok().as_deref() != Some("1") {
372 eprintln!("skip test_is_opaque_on_non_directory: RUN_PRIVILEGED_TESTS!=1");
373 return;
374 }
375 let fs = unwrap_or_skip_eperm!(
376 new_passthroughfs_layer(PassthroughArgs {
377 root_dir: rootdir,
378 mapping: None::<&str>
379 })
380 .await,
381 "init passthrough layer"
382 );
383 let _ = unwrap_or_skip_eperm!(fs.init(Request::default()).await, "fs init");
384
385 let file_name = OsStr::new("not_a_dir");
387 let _ = unwrap_or_skip_eperm!(
388 fs.create(Request::default(), 1, file_name, 0o644, 0).await,
389 "create file"
390 );
391
392 let entry = unwrap_or_skip_eperm!(
394 fs.lookup(Request::default(), 1, file_name).await,
395 "lookup file"
396 );
397 let file_inode = entry.attr.ino;
398
399 let res = fs.is_opaque(Request::default(), file_inode).await;
401 assert!(res.is_err());
402 let err = res.err().unwrap();
403 let ioerr: std::io::Error = err.into();
404 assert_eq!(ioerr.raw_os_error(), Some(libc::ENOTDIR));
405
406 let _ = fs.unlink(Request::default(), 1, file_name).await;
408 let _ = fs.destroy(Request::default()).await;
409 }
410
411 #[tokio::test]
412 async fn test_set_opaque_on_non_directory() {
413 let temp_dir = "/tmp/test_set_opaque_non_dir/t2";
414 let rootdir = PathBuf::from(temp_dir);
415 std::fs::create_dir_all(&rootdir).unwrap();
416 if std::env::var("RUN_PRIVILEGED_TESTS").ok().as_deref() != Some("1") {
417 eprintln!("skip test_set_opaque_on_non_directory: RUN_PRIVILEGED_TESTS!=1");
418 return;
419 }
420 let fs = unwrap_or_skip_eperm!(
421 new_passthroughfs_layer(PassthroughArgs {
422 root_dir: rootdir,
423 mapping: None::<&str>
424 })
425 .await,
426 "init passthrough layer"
427 );
428 let _ = unwrap_or_skip_eperm!(fs.init(Request::default()).await, "fs init");
429
430 let file_name = OsStr::new("not_a_dir2");
432 let _ = unwrap_or_skip_eperm!(
433 fs.create(Request::default(), 1, file_name, 0o644, 0).await,
434 "create file"
435 );
436
437 let entry = unwrap_or_skip_eperm!(
439 fs.lookup(Request::default(), 1, file_name).await,
440 "lookup file"
441 );
442 let file_inode = entry.attr.ino;
443
444 let res = fs.set_opaque(Request::default(), file_inode).await;
446 assert!(res.is_err());
447 let err = res.err().unwrap();
448 let ioerr: std::io::Error = err.into();
449 assert_eq!(ioerr.raw_os_error(), Some(libc::ENOTDIR));
450
451 let _ = fs.unlink(Request::default(), 1, file_name).await;
453 let _ = fs.destroy(Request::default()).await;
454 }
455}