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}