|
| 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 | + |
| 7 | +use std::ffi::{CStr, CString}; |
| 8 | +use std::io::{Error, ErrorKind, Result}; |
| 9 | + |
| 10 | +use super::{Context, Entry, FileSystem, GetxattrReply}; |
| 11 | +use crate::abi::fuse_abi::stat64; |
| 12 | + |
| 13 | +pub const OPAQUE_XATTR_LEN: u32 = 16; |
| 14 | +pub const OPAQUE_XATTR: &str = "user.fuseoverlayfs.opaque"; |
| 15 | +pub const UNPRIVILEGED_OPAQUE_XATTR: &str = "user.overlay.opaque"; |
| 16 | +pub 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. |
| 19 | +pub 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 | + |
| 187 | +pub(crate) fn is_dir(st: stat64) -> bool { |
| 188 | + st.st_mode & libc::S_IFMT == libc::S_IFDIR |
| 189 | +} |
| 190 | + |
| 191 | +pub(crate) fn is_chardev(st: stat64) -> bool { |
| 192 | + st.st_mode & libc::S_IFMT == libc::S_IFCHR |
| 193 | +} |
| 194 | + |
| 195 | +pub(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 | + |
| 203 | +pub(crate) fn to_cstring(name: &str) -> Result<CString> { |
| 204 | + CString::new(name).map_err(|e| Error::new(ErrorKind::InvalidData, e)) |
| 205 | +} |
0 commit comments