diff --git a/Cargo.lock b/Cargo.lock index 114a2e5e8d4..4b622d5faf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1135,22 +1135,6 @@ dependencies = [ "vmm-sys-util", ] -[[package]] -name = "nydus-blobfs" -version = "0.2.0" -dependencies = [ - "fuse-backend-rs", - "libc", - "log", - "nydus-api", - "nydus-error", - "nydus-rafs", - "nydus-storage", - "serde", - "serde_json", - "vm-memory", -] - [[package]] name = "nydus-clib" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 6c2e04cca63..0d50076fa41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,4 +106,4 @@ backend-registry = ["nydus-storage/backend-registry"] backend-s3 = ["nydus-storage/backend-s3"] [workspace] -members = ["api", "blobfs", "clib", "error", "rafs", "storage", "service", "utils"] +members = ["api", "clib", "error", "rafs", "storage", "service", "utils"] diff --git a/blobfs/Cargo.toml b/blobfs/Cargo.toml deleted file mode 100644 index e8c35c34748..00000000000 --- a/blobfs/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "nydus-blobfs" -version = "0.2.0" -description = "Blob object file system for Nydus Image Service" -authors = ["The Nydus Developers"] -license = "Apache-2.0 OR BSD-3-Clause" -homepage = "https://nydus.dev/" -repository = "https://github.com/dragonflyoss/image-service" -edition = "2018" - -[dependencies] -fuse-backend-rs = "^0.10.3" -libc = "0.2" -log = "0.4.8" -serde = { version = "1.0.110", features = ["serde_derive", "rc"] } -serde_json = "1.0.53" -vm-memory = { version = "0.9" } - -nydus-error = { version = "0.2", path = "../error" } -nydus-api = { version = "0.2", path = "../api" } -nydus-rafs = { version = "0.2", path = "../rafs" } -nydus-storage = { version = "0.6", path = "../storage", features = [ - "backend-localfs", -] } - -[features] -virtiofs = ["fuse-backend-rs/virtiofs", "nydus-rafs/virtio-fs"] - -[package.metadata.docs.rs] -all-features = true -targets = ["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"] diff --git a/blobfs/src/lib.rs b/blobfs/src/lib.rs deleted file mode 100644 index 11a1d8ecb50..00000000000 --- a/blobfs/src/lib.rs +++ /dev/null @@ -1,507 +0,0 @@ -// Copyright (C) 2020 Alibaba Cloud. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -//! Fuse blob passthrough file system, mirroring an existing FS hierarchy. -//! -//! This file system mirrors the existing file system hierarchy of the system, starting at the -//! root file system. This is implemented by just "passing through" all requests to the -//! corresponding underlying file system. -//! -//! The code is derived from the -//! [CrosVM](https://chromium.googlesource.com/chromiumos/platform/crosvm/) project, -//! with heavy modification/enhancements from Alibaba Cloud OS team. - -#[macro_use] -extern crate log; - -use fuse_backend_rs::{ - api::{filesystem::*, BackendFileSystem, VFS_MAX_INO}, - passthrough::Config as PassthroughConfig, - passthrough::PassthroughFs, -}; -use nydus_api::ConfigV2; -use nydus_error::einval; -use nydus_rafs::fs::Rafs; -use serde::Deserialize; -use std::any::Any; -#[cfg(feature = "virtiofs")] -use std::ffi::CStr; -use std::ffi::CString; -use std::fs::create_dir_all; -#[cfg(feature = "virtiofs")] -use std::fs::File; -use std::io; -#[cfg(feature = "virtiofs")] -use std::mem::MaybeUninit; -#[cfg(feature = "virtiofs")] -use std::os::unix::ffi::OsStrExt; -#[cfg(feature = "virtiofs")] -use std::os::unix::io::{AsRawFd, FromRawFd}; -use std::path::Path; -use std::str::FromStr; -use std::sync::{Arc, Mutex}; -use std::thread; - -#[cfg(feature = "virtiofs")] -use nydus_storage::device::BlobPrefetchRequest; -use vm_memory::ByteValued; - -mod sync_io; - -#[cfg(feature = "virtiofs")] -const EMPTY_CSTR: &[u8] = b"\0"; - -type Inode = u64; -type Handle = u64; - -#[repr(C, packed)] -#[derive(Clone, Copy, Debug, Default)] -struct LinuxDirent64 { - d_ino: libc::ino64_t, - d_off: libc::off64_t, - d_reclen: libc::c_ushort, - d_ty: libc::c_uchar, -} -unsafe impl ByteValued for LinuxDirent64 {} - -/// Options that configure xxx -#[derive(Clone, Default, Deserialize)] -pub struct BlobOndemandConfig { - /// The rafs config used to set up rafs device for the purpose of - /// `on demand read`. - pub rafs_conf: ConfigV2, - - /// THe path of bootstrap of an container image (for rafs in - /// kernel). - /// - /// The default is ``. - #[serde(default)] - pub bootstrap_path: String, - - /// The path of blob cache directory. - #[serde(default)] - pub blob_cache_dir: String, -} - -impl FromStr for BlobOndemandConfig { - type Err = io::Error; - - fn from_str(s: &str) -> io::Result { - serde_json::from_str(s).map_err(|e| einval!(e)) - } -} - -/// Options that configure the behavior of the blobfs fuse file system. -#[derive(Default, Debug, Clone, PartialEq)] -pub struct Config { - /// Blobfs config is embedded with passthrough config - pub ps_config: PassthroughConfig, - /// This provides on demand config of blob management. - pub blob_ondemand_cfg: String, -} - -#[allow(dead_code)] -struct RafsHandle { - rafs: Arc>>, - handle: Arc>>>>, -} - -#[allow(dead_code)] -struct BootstrapArgs { - rafs_handle: RafsHandle, - blob_cache_dir: String, -} - -// Safe to Send/Sync because the underlying data structures are readonly -unsafe impl Sync for BootstrapArgs {} -unsafe impl Send for BootstrapArgs {} - -#[cfg(feature = "virtiofs")] -impl BootstrapArgs { - fn get_rafs_handle(&self) -> io::Result<()> { - let mut c = self.rafs_handle.rafs.lock().unwrap(); - match (*self.rafs_handle.handle.lock().unwrap()).take() { - Some(handle) => { - let rafs = handle.join().unwrap().ok_or_else(|| { - error!("blobfs: get rafs failed."); - einval!("create rafs failed in thread.") - })?; - debug!("blobfs: async create Rafs finish!"); - - *c = Some(rafs); - Ok(()) - } - None => Err(einval!("create rafs failed in thread.")), - } - } - - fn fetch_range_sync(&self, prefetches: &[BlobPrefetchRequest]) -> io::Result<()> { - let c = self.rafs_handle.rafs.lock().unwrap(); - match &*c { - Some(rafs) => rafs.fetch_range_synchronous(prefetches), - None => Err(einval!("create rafs failed in thread.")), - } - } -} - -/// A file system that simply "passes through" all requests it receives to the underlying file -/// system. -/// -/// To keep the implementation simple it servers the contents of its root directory. Users -/// that wish to serve only a specific directory should set up the environment so that that -/// directory ends up as the root of the file system process. One way to accomplish this is via a -/// combination of mount namespaces and the pivot_root system call. -pub struct BlobFs { - pfs: PassthroughFs, - #[allow(dead_code)] - bootstrap_args: BootstrapArgs, -} - -impl BlobFs { - fn ensure_path_exist(path: &Path) -> io::Result<()> { - if path.as_os_str().is_empty() { - return Err(einval!("path is empty")); - } - if !path.exists() { - create_dir_all(path).map_err(|e| { - error!( - "create dir error. directory is {:?}. {}:{}", - path, - file!(), - line!() - ); - e - })?; - } - - Ok(()) - } - - /// Create a Blob file system instance. - pub fn new(cfg: Config) -> io::Result { - trace!("BlobFs config is: {:?}", cfg); - - let bootstrap_args = Self::load_bootstrap(&cfg)?; - let pfs = PassthroughFs::new(cfg.ps_config)?; - Ok(BlobFs { - pfs, - bootstrap_args, - }) - } - - fn load_bootstrap(cfg: &Config) -> io::Result { - let blob_ondemand_conf = BlobOndemandConfig::from_str(&cfg.blob_ondemand_cfg)?; - if !blob_ondemand_conf.rafs_conf.validate() { - return Err(einval!("invlidate configuration for blobfs")); - } - let rafs_cfg = blob_ondemand_conf.rafs_conf.get_rafs_config()?; - if rafs_cfg.mode != "direct" { - return Err(einval!("blobfs only supports RAFS 'direct' mode")); - } - - // check if blob cache dir exists. - let path = Path::new(blob_ondemand_conf.blob_cache_dir.as_str()); - Self::ensure_path_exist(path).map_err(|e| { - error!("blob_cache_dir not exist"); - e - })?; - - let path = Path::new(blob_ondemand_conf.bootstrap_path.as_str()); - if !path.exists() || blob_ondemand_conf.bootstrap_path.is_empty() { - return Err(einval!("no valid bootstrap")); - } - - let bootstrap_path = blob_ondemand_conf.bootstrap_path.clone(); - let config = Arc::new(blob_ondemand_conf.rafs_conf.clone()); - - trace!("blobfs: async create Rafs start!"); - let rafs_join_handle = std::thread::spawn(move || { - let (mut rafs, reader) = match Rafs::new(&config, "blobfs", Path::new(&bootstrap_path)) - { - Ok(rafs) => rafs, - Err(e) => { - error!("blobfs: new rafs failed {:?}.", e); - return None; - } - }; - match rafs.import(reader, None) { - Ok(_) => {} - Err(e) => { - error!("blobfs: new rafs failed {:?}.", e); - return None; - } - } - Some(rafs) - }); - let rafs_handle = RafsHandle { - rafs: Arc::new(Mutex::new(None)), - handle: Arc::new(Mutex::new(Some(rafs_join_handle))), - }; - - Ok(BootstrapArgs { - rafs_handle, - blob_cache_dir: blob_ondemand_conf.blob_cache_dir, - }) - } - - #[cfg(feature = "virtiofs")] - fn stat(f: &File) -> io::Result { - // Safe because this is a constant value and a valid C string. - let pathname = unsafe { CStr::from_bytes_with_nul_unchecked(EMPTY_CSTR) }; - let mut st = MaybeUninit::::zeroed(); - - // Safe because the kernel will only write data in `st` and we check the return value. - let res = unsafe { - libc::fstatat64( - f.as_raw_fd(), - pathname.as_ptr(), - st.as_mut_ptr(), - libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, - ) - }; - if res >= 0 { - // Safe because the kernel guarantees that the struct is now fully initialized. - Ok(unsafe { st.assume_init() }) - } else { - Err(io::Error::last_os_error()) - } - } - - /// Initialize the PassthroughFs - pub fn import(&self) -> io::Result<()> { - self.pfs.import() - } - - #[cfg(feature = "virtiofs")] - fn open_file(dfd: i32, pathname: &Path, flags: i32, mode: u32) -> io::Result { - let pathname = CString::new(pathname.as_os_str().as_bytes()) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let fd = if flags & libc::O_CREAT == libc::O_CREAT { - unsafe { libc::openat(dfd, pathname.as_ptr(), flags, mode) } - } else { - unsafe { libc::openat(dfd, pathname.as_ptr(), flags) } - }; - - if fd < 0 { - return Err(io::Error::last_os_error()); - } - - // Safe because we just opened this fd. - Ok(unsafe { File::from_raw_fd(fd) }) - } -} - -impl BackendFileSystem for BlobFs { - fn mount(&self) -> io::Result<(Entry, u64)> { - let ctx = &Context::default(); - let entry = self.lookup(ctx, ROOT_ID, &CString::new(".").unwrap())?; - Ok((entry, VFS_MAX_INO)) - } - - fn as_any(&self) -> &dyn Any { - self - } -} - -#[cfg(test2)] -mod tests { - use super::*; - use fuse_backend_rs::abi::virtio_fs; - use fuse_backend_rs::transport::FsCacheReqHandler; - use std::os::unix::prelude::RawFd; - - struct DummyCacheReq {} - - impl FsCacheReqHandler for DummyCacheReq { - fn map( - &mut self, - _foffset: u64, - _moffset: u64, - _len: u64, - _flags: u64, - _fd: RawFd, - ) -> io::Result<()> { - Ok(()) - } - - fn unmap(&mut self, _requests: Vec) -> io::Result<()> { - Ok(()) - } - } - - // #[test] - // #[cfg(feature = "virtiofs")] - // fn test_blobfs_new() { - // let config = r#" - // { - // "device": { - // "backend": { - // "type": "localfs", - // "config": { - // "dir": "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1/test4k" - // } - // }, - // "cache": { - // "type": "blobcache", - // "compressed": false, - // "config": { - // "work_dir": "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1/blobcache" - // } - // } - // }, - // "mode": "direct", - // "digest_validate": true, - // "enable_xattr": false, - // "fs_prefetch": { - // "enable": false, - // "threads_count": 10, - // "merging_size": 131072, - // "bandwidth_rate": 10485760 - // } - // }"#; - // // let rafs_conf = RafsConfig::from_str(config).unwrap(); - - // let fs_cfg = Config { - // root_dir: "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1" - // .to_string(), - // bootstrap_path: "test4k/bootstrap-link".to_string(), - // // blob_cache_dir: "blobcache".to_string(), - // do_import: false, - // no_open: true, - // rafs_conf: config.to_string(), - // ..Default::default() - // }; - - // assert!(BlobFs::new(fs_cfg).is_err()); - - // let fs_cfg = Config { - // root_dir: "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1" - // .to_string(), - // bootstrap_path: "test4k/bootstrap-link".to_string(), - // blob_cache_dir: "blobcache1".to_string(), - // do_import: false, - // no_open: true, - // rafs_conf: config.to_string(), - // ..Default::default() - // }; - - // assert!(BlobFs::new(fs_cfg).is_err()); - - // let fs_cfg = Config { - // root_dir: "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1" - // .to_string(), - // // bootstrap_path: "test4k/bootstrap-link".to_string(), - // blob_cache_dir: "blobcache".to_string(), - // do_import: false, - // no_open: true, - // rafs_conf: config.to_string(), - // ..Default::default() - // }; - - // assert!(BlobFs::new(fs_cfg).is_err()); - - // let fs_cfg = Config { - // root_dir: "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1" - // .to_string(), - // bootstrap_path: "test4k/bootstrap-foo".to_string(), - // blob_cache_dir: "blobcache".to_string(), - // do_import: false, - // no_open: true, - // rafs_conf: config.to_string(), - // ..Default::default() - // }; - - // assert!(BlobFs::new(fs_cfg).is_err()); - - // let fs_cfg = Config { - // root_dir: "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1" - // .to_string(), - // bootstrap_path: "test4k/bootstrap-link".to_string(), - // blob_cache_dir: "blobcache".to_string(), - // do_import: false, - // no_open: true, - // rafs_conf: config.to_string(), - // ..Default::default() - // }; - - // assert!(BlobFs::new(fs_cfg).is_ok()); - // } - - #[test] - fn test_blobfs_setupmapping() { - let config = r#" - { - "rafs_conf": { - "device": { - "backend": { - "type": "localfs", - "config": { - "blob_file": "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1/nydus-rs/myblob1/v6/blob-btrfs" - } - }, - "cache": { - "type": "blobcache", - "compressed": false, - "config": { - "work_dir": "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1/blobcache" - } - } - }, - "mode": "direct", - "digest_validate": false, - "enable_xattr": false, - "fs_prefetch": { - "enable": false, - "threads_count": 10, - "merging_size": 131072, - "bandwidth_rate": 10485760 - } - }, - "bootstrap_path": "nydus-rs/myblob1/v6/bootstrap-btrfs", - "blob_cache_dir": "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1/blobcache" - }"#; - // let rafs_conf = RafsConfig::from_str(config).unwrap(); - - let ps_config = PassthroughConfig { - root_dir: "/home/b.liu/1_source/3_ali/virtiofs/qemu-my/build-kangaroo/share_dir1" - .to_string(), - do_import: false, - no_open: true, - ..Default::default() - }; - let fs_cfg = Config { - ps_config, - blob_ondemand_cfg: config.to_string(), - }; - - let fs = BlobFs::new(fs_cfg).unwrap(); - fs.import().unwrap(); - - fs.mount().unwrap(); - - let ctx = &Context::default(); - - // read bootstrap first, should return err as it's not in blobcache dir. - // let bootstrap = CString::new("foo").unwrap(); - // let entry = fs.lookup(ctx, ROOT_ID, &bootstrap).unwrap(); - // let mut req = DummyCacheReq {}; - // fs.setupmapping(ctx, entry.inode, 0, 0, 4096, 0, 0, &mut req) - // .unwrap(); - - // FIXME: use a real blob id under test4k. - let blob_cache_dir = CString::new("blobcache").unwrap(); - let parent_entry = fs.lookup(ctx, ROOT_ID, &blob_cache_dir).unwrap(); - - let blob_id = CString::new("80da976ee69d68af6bb9170395f71b4ef1e235e815e2").unwrap(); - let entry = fs.lookup(ctx, parent_entry.inode, &blob_id).unwrap(); - - let foffset = 0; - let len = 1 << 21; - let mut req = DummyCacheReq {}; - fs.setupmapping(ctx, entry.inode, 0, foffset, len, 0, 0, &mut req) - .unwrap(); - - // FIXME: release fs - fs.destroy(); - } -} diff --git a/rafs/src/blobfs/mod.rs b/rafs/src/blobfs/mod.rs new file mode 100644 index 00000000000..7ae77960010 --- /dev/null +++ b/rafs/src/blobfs/mod.rs @@ -0,0 +1,379 @@ +// Copyright (C) 2020 Alibaba Cloud. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Fuse blob passthrough file system, mirroring an existing FS hierarchy. +//! +//! This file system mirrors the existing file system hierarchy of the system, starting at the +//! root file system. This is implemented by just "passing through" all requests to the +//! corresponding underlying file system. +//! +//! The code is derived from the +//! [CrosVM](https://chromium.googlesource.com/chromiumos/platform/crosvm/) project, +//! with heavy modification/enhancements from Alibaba Cloud OS team. + +use std::any::Any; +use std::ffi::{CStr, CString}; +use std::fs::{create_dir_all, File}; +use std::io; +use std::mem::MaybeUninit; +use std::os::fd::{AsRawFd, FromRawFd}; +use std::os::unix::ffi::OsStrExt; +use std::path::Path; +use std::str::FromStr; +use std::sync::{Arc, Mutex}; +use std::thread; + +use fuse_backend_rs::api::{filesystem::*, BackendFileSystem, VFS_MAX_INO}; +use fuse_backend_rs::{passthrough::Config as PassthroughConfig, passthrough::PassthroughFs}; +use nix::NixPath; +use nydus_api::ConfigV2; +use nydus_error::einval; +use nydus_storage::device::BlobPrefetchRequest; +use serde::Deserialize; + +use crate::fs::Rafs; +use crate::metadata::Inode; +use crate::RafsError; + +mod sync_io; + +const EMPTY_CSTR: &[u8] = b"\0"; + +/// Configuration information for blobfs instance. +#[derive(Clone, Default, Deserialize)] +pub struct BlobOndemandConfig { + /// RAFS filesystem configuration to configure backend, cache and fuse. + /// The rafs config used to set up rafs device for the purpose of `on demand read`. + pub rafs_conf: ConfigV2, + + /// Meta blob file path for a RAFS filesystem. + #[serde(default)] + pub bootstrap_path: String, + + /// Blob cache directory path. + #[serde(default)] + pub blob_cache_dir: String, +} + +impl FromStr for BlobOndemandConfig { + type Err = io::Error; + + fn from_str(s: &str) -> io::Result { + serde_json::from_str(s).map_err(|e| { + einval!(format!( + "blobfs: failed to load blobfs configuration, {}", + e + )) + }) + } +} + +/// Options that configure the behavior of the blobfs fuse file system. +#[derive(Default, Debug, Clone, PartialEq)] +pub struct Config { + /// Blobfs config is embedded with passthrough config + pub ps_config: PassthroughConfig, + /// This provides on demand config of blob management. + pub blob_ondemand_cfg: String, +} + +struct RafsHandle { + rafs: Option, + thread: Option>>, +} + +struct BootstrapArgs { + #[allow(unused)] + blob_cache_dir: String, + rafs_handle: Mutex, +} + +impl BootstrapArgs { + fn get_rafs_handle(&self) -> io::Result<()> { + let mut rafs_handle = self.rafs_handle.lock().unwrap(); + + if let Some(handle) = rafs_handle.thread.take() { + match handle.join() { + Ok(v) => match v { + Ok(rafs) => rafs_handle.rafs = Some(rafs), + Err(e) => { + return Err(eio!(format!( + "blobfs: failed to get RAFS filesystem handle, {}", + e + ))) + } + }, + Err(e) => { + return Err(eio!(format!( + "blobfs: failed to get RAFS filesystem handle, {:?}", + e + ))) + } + } + } + + if rafs_handle.rafs.is_none() { + Err(eio!("blobfs: failed to get RAFS filesystem handle")) + } else { + Ok(()) + } + } +} + +/// A file system that simply "passes through" all requests it receives to the underlying file +/// system. +/// +/// To keep the implementation simple it servers the contents of its root directory. Users +/// that wish to serve only a specific directory should set up the environment so that that +/// directory ends up as the root of the file system process. One way to accomplish this is via a +/// combination of mount namespaces and the pivot_root system call. +pub struct BlobFs { + bootstrap_args: BootstrapArgs, + pfs: PassthroughFs, +} + +impl BlobFs { + /// Create a Blob file system instance. + pub fn new(cfg: Config) -> io::Result { + let bootstrap_args = Self::load_bootstrap(&cfg)?; + let pfs = PassthroughFs::new(cfg.ps_config)?; + + Ok(BlobFs { + pfs, + bootstrap_args, + }) + } + + /// Initialize the blobfs instance. + pub fn import(&self) -> io::Result<()> { + self.pfs.import() + } + + fn ensure_path_exist(path: &Path) -> io::Result<()> { + if path.is_empty() { + return Err(einval!("blobfs: path is empty")); + } + if !path.exists() { + create_dir_all(path).map_err(|e| { + error!("blobfs: failed to create dir {}, {}", path.display(), e); + e + })?; + } + + Ok(()) + } + + fn load_bootstrap(cfg: &Config) -> io::Result { + let blob_ondemand_conf = BlobOndemandConfig::from_str(&cfg.blob_ondemand_cfg)?; + if !blob_ondemand_conf.rafs_conf.validate() { + return Err(einval!("blobfs: invlidate configuration for blobfs")); + } + let rafs_cfg = blob_ondemand_conf.rafs_conf.get_rafs_config()?; + if rafs_cfg.mode != "direct" { + return Err(einval!("blobfs: only 'direct' mode is supported")); + } + + // check if blob cache dir exists. + let path = Path::new(blob_ondemand_conf.blob_cache_dir.as_str()); + Self::ensure_path_exist(path)?; + + let path = Path::new(blob_ondemand_conf.bootstrap_path.as_str()); + if blob_ondemand_conf.bootstrap_path.is_empty() || !path.is_file() { + return Err(einval!(format!( + "blobfs: bootstrap file {} is invalid", + path.display() + ))); + } + + let bootstrap_path = blob_ondemand_conf.bootstrap_path.clone(); + let config = Arc::new(blob_ondemand_conf.rafs_conf.clone()); + + trace!("blobfs: async create Rafs start!"); + let rafs_join_handle = std::thread::spawn(move || { + let (mut rafs, reader) = Rafs::new(&config, "blobfs", Path::new(&bootstrap_path))?; + rafs.import(reader, None)?; + Ok(rafs) + }); + + let rafs_handle = RafsHandle { + rafs: None, + thread: Some(rafs_join_handle), + }; + + Ok(BootstrapArgs { + rafs_handle: Mutex::new(rafs_handle), + blob_cache_dir: blob_ondemand_conf.blob_cache_dir.clone(), + }) + } + + fn get_blob_id_and_size(&self, inode: Inode) -> io::Result<(String, u64)> { + // locate blob file that the inode refers to + let blob_id_full_path = self.pfs.readlinkat_proc_file(inode)?; + let parent = blob_id_full_path + .parent() + .ok_or_else(|| einval!("blobfs: failed to find parent"))?; + + trace!( + "parent: {:?}, blob id path: {:?}", + parent, + blob_id_full_path + ); + + let blob_file = Self::open_file( + libc::AT_FDCWD, + blob_id_full_path.as_path(), + libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC, + 0, + ) + .map_err(|e| einval!(e))?; + let st = Self::stat(&blob_file).map_err(|e| { + error!("get_blob_id_and_size: stat failed {:?}", e); + e + })?; + if st.st_size < 0 { + return Err(einval!(format!( + "load_chunks_on_demand: blob_id {:?}, size: {:?} is less than 0", + blob_id_full_path.display(), + st.st_size + ))); + } + + let blob_id = blob_id_full_path + .file_name() + .ok_or_else(|| einval!("blobfs: failed to find blob file"))?; + let blob_id = blob_id + .to_os_string() + .into_string() + .map_err(|_e| einval!("blobfs: failed to get blob id from file name"))?; + trace!("load_chunks_on_demand: blob_id {}", blob_id); + + Ok((blob_id, st.st_size as u64)) + } + + fn stat(f: &File) -> io::Result { + // Safe because this is a constant value and a valid C string. + let pathname = unsafe { CStr::from_bytes_with_nul_unchecked(EMPTY_CSTR) }; + let mut st = MaybeUninit::::zeroed(); + + // Safe because the kernel will only write data in `st` and we check the return value. + let res = unsafe { + libc::fstatat64( + f.as_raw_fd(), + pathname.as_ptr(), + st.as_mut_ptr(), + libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, + ) + }; + if res >= 0 { + // Safe because the kernel guarantees that the struct is now fully initialized. + Ok(unsafe { st.assume_init() }) + } else { + Err(io::Error::last_os_error()) + } + } + + fn open_file(dfd: i32, pathname: &Path, flags: i32, mode: u32) -> io::Result { + let pathname = CString::new(pathname.as_os_str().as_bytes()) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let fd = if flags & libc::O_CREAT == libc::O_CREAT { + unsafe { libc::openat(dfd, pathname.as_ptr(), flags, mode) } + } else { + unsafe { libc::openat(dfd, pathname.as_ptr(), flags) } + }; + + if fd < 0 { + return Err(io::Error::last_os_error()); + } + + // Safe because we just opened this fd. + Ok(unsafe { File::from_raw_fd(fd) }) + } +} + +impl BackendFileSystem for BlobFs { + fn mount(&self) -> io::Result<(Entry, u64)> { + let ctx = &Context::default(); + let name = CString::new(".").unwrap(); + let entry = self.lookup(ctx, ROOT_ID, name.as_c_str())?; + + Ok((entry, VFS_MAX_INO)) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::metadata::{RafsMode, RafsSuper}; + use crate::{RafsIoRead, RafsIoReader, RafsIoWrite, RafsIterator}; + use std::fs::OpenOptions; + use std::io::Write; + use std::path::PathBuf; + use vmm_sys_util::tempfile::TempFile; + + #[test] + fn test_rafs_io_writer() { + let mut file = TempFile::new().unwrap().into_file(); + + assert!(file.validate_alignment(2, 8).is_err()); + assert!(file.validate_alignment(7, 8).is_err()); + assert!(file.validate_alignment(9, 8).is_err()); + assert!(file.validate_alignment(8, 8).is_ok()); + + file.write_all(&[0x0u8; 7]).unwrap(); + assert!(file.validate_alignment(8, 8).is_err()); + { + let obj: &mut dyn RafsIoWrite = &mut file; + obj.write_padding(1).unwrap(); + } + assert!(file.validate_alignment(8, 8).is_ok()); + file.write_all(&[0x0u8; 1]).unwrap(); + assert!(file.validate_alignment(8, 8).is_err()); + + let obj: &mut dyn RafsIoRead = &mut file; + assert_eq!(obj.seek_to_offset(0).unwrap(), 0); + assert_eq!(obj.seek_plus_offset(7).unwrap(), 7); + assert_eq!(obj.seek_to_next_aligned(7, 8).unwrap(), 8); + assert_eq!(obj.seek_plus_offset(7).unwrap(), 15); + } + + #[test] + fn test_rafs_iterator() { + let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR"); + let path = PathBuf::from(root_dir).join("../tests/texture/bootstrap/rafs-v5.boot"); + let bootstrap = OpenOptions::new() + .read(true) + .write(false) + .open(&path) + .unwrap(); + let mut rs = RafsSuper { + mode: RafsMode::Direct, + validate_digest: false, + ..Default::default() + }; + rs.load(&mut (Box::new(bootstrap) as RafsIoReader)).unwrap(); + let iter = RafsIterator::new(&rs); + + let mut last = false; + for (idx, (_node, path)) in iter.enumerate() { + assert!(!last); + if idx == 1 { + assert_eq!(path, PathBuf::from("/bin")); + } else if idx == 2 { + assert_eq!(path, PathBuf::from("/boot")); + } else if idx == 3 { + assert_eq!(path, PathBuf::from("/dev")); + } else if idx == 10 { + assert_eq!(path, PathBuf::from("/etc/DIR_COLORS.256color")); + } else if idx == 11 { + assert_eq!(path, PathBuf::from("/etc/DIR_COLORS.lightbgcolor")); + } else if path == PathBuf::from("/var/yp") { + last = true; + } + } + assert!(last); + } +} diff --git a/blobfs/src/sync_io.rs b/rafs/src/blobfs/sync_io.rs similarity index 71% rename from blobfs/src/sync_io.rs rename to rafs/src/blobfs/sync_io.rs index e91f7e833db..b543649aaf0 100644 --- a/blobfs/src/sync_io.rs +++ b/rafs/src/blobfs/sync_io.rs @@ -3,85 +3,44 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE-BSD-3-Clause file. -//! Fuse passthrough file system, mirroring an existing FS hierarchy. - -use super::*; -use fuse_backend_rs::abi::fuse_abi::CreateIn; -#[cfg(feature = "virtiofs")] -use fuse_backend_rs::abi::virtio_fs; -#[cfg(feature = "virtiofs")] -use fuse_backend_rs::transport::FsCacheReqHandler; -use nydus_error::eacces; -#[cfg(feature = "virtiofs")] -use nydus_storage::device::BlobPrefetchRequest; -#[cfg(feature = "virtiofs")] -use std::cmp::min; use std::ffi::CStr; use std::io; -#[cfg(feature = "virtiofs")] -use std::path::Path; use std::time::Duration; -impl BlobFs { - #[cfg(feature = "virtiofs")] - fn check_st_size(blob_id: &Path, size: i64) -> io::Result<()> { - if size < 0 { - return Err(einval!(format!( - "load_chunks_on_demand: blob_id {:?}, size: {:?} is less than 0", - blob_id, size - ))); - } - Ok(()) - } - - #[cfg(feature = "virtiofs")] - fn get_blob_id_and_size(&self, inode: Inode) -> io::Result<(String, u64)> { - // locate blob file that the inode refers to - let blob_id_full_path = self.pfs.readlinkat_proc_file(inode)?; - let parent = blob_id_full_path - .parent() - .ok_or_else(|| einval!("blobfs: failed to find parent"))?; - - trace!( - "parent: {:?}, blob id path: {:?}", - parent, - blob_id_full_path - ); - - let blob_file = Self::open_file( - libc::AT_FDCWD, - blob_id_full_path.as_path(), - libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC, - 0, - ) - .map_err(|e| einval!(e))?; - let st = Self::stat(&blob_file).map_err(|e| { - error!("get_blob_id_and_size: stat failed {:?}", e); - e - })?; - let blob_id = blob_id_full_path - .file_name() - .ok_or_else(|| einval!("blobfs: failed to find blob file"))?; +use fuse_backend_rs::abi::fuse_abi::{CreateIn, FsOptions, OpenOptions, SetattrValid}; +use fuse_backend_rs::abi::virtio_fs; +use fuse_backend_rs::api::filesystem::{ + Context, DirEntry, Entry, FileSystem, GetxattrReply, ListxattrReply, ZeroCopyReader, + ZeroCopyWriter, +}; +use fuse_backend_rs::transport::FsCacheReqHandler; +use nydus_error::eacces; - trace!("load_chunks_on_demand: blob_id {:?}", blob_id); +use super::*; +use crate::fs::Handle; +use crate::metadata::Inode; - Self::check_st_size(blob_id_full_path.as_path(), st.st_size)?; +const MAPPING_UNIT_SIZE: u64 = 0x200000; - Ok(( - blob_id.to_os_string().into_string().unwrap(), - st.st_size as u64, - )) +impl BootstrapArgs { + fn fetch_range_sync(&self, prefetches: &[BlobPrefetchRequest]) -> io::Result<()> { + let rafs_handle = self.rafs_handle.lock().unwrap(); + match rafs_handle.rafs.as_ref() { + Some(rafs) => rafs.fetch_range_synchronous(prefetches), + None => Err(einval!("blobfs: failed to initialize RAFS filesystem.")), + } } +} - #[cfg(feature = "virtiofs")] +impl BlobFs { + // prepare BlobPrefetchRequest and call device.prefetch(). + // Make sure prefetch doesn't use delay_persist as we need the data immediately. fn load_chunks_on_demand(&self, inode: Inode, offset: u64) -> io::Result<()> { - // prepare BlobPrefetchRequest and call device.prefetch(). - // Make sure prefetch doesn't use delay_persist as we need the - // data immediately. let (blob_id, size) = self.get_blob_id_and_size(inode)?; + let offset = offset & !(MAPPING_UNIT_SIZE - 1); if size <= offset { return Err(einval!(format!( - "load_chunks_on_demand: blob_id {:?}, offset {:?} is larger than size {:?}", + "blobfs: blob_id {:?}, offset {:?} is larger than size {:?}", blob_id, offset, size ))); } @@ -90,11 +49,11 @@ impl BlobFs { let req = BlobPrefetchRequest { blob_id, offset, - len: min(len, 0x0020_0000_u64), // 2M range + len: std::cmp::min(len, MAPPING_UNIT_SIZE), // 2M range }; self.bootstrap_args.fetch_range_sync(&[req]).map_err(|e| { - warn!("load chunks: error, {:?}", e); + warn!("blobfs: failed to load data, {:?}", e); e }) } @@ -105,7 +64,6 @@ impl FileSystem for BlobFs { type Handle = Handle; fn init(&self, capable: FsOptions) -> io::Result { - #[cfg(feature = "virtiofs")] self.bootstrap_args.get_rafs_handle()?; self.pfs.init(capable) } @@ -114,10 +72,6 @@ impl FileSystem for BlobFs { self.pfs.destroy() } - fn statfs(&self, _ctx: &Context, inode: Inode) -> io::Result { - self.pfs.statfs(_ctx, inode) - } - fn lookup(&self, _ctx: &Context, parent: Inode, name: &CStr) -> io::Result { self.pfs.lookup(_ctx, parent, name) } @@ -130,26 +84,52 @@ impl FileSystem for BlobFs { self.pfs.batch_forget(_ctx, requests) } - fn opendir( + fn getattr( &self, _ctx: &Context, inode: Inode, - flags: u32, - ) -> io::Result<(Option, OpenOptions)> { - self.pfs.opendir(_ctx, inode, flags) + _handle: Option, + ) -> io::Result<(libc::stat64, Duration)> { + self.pfs.getattr(_ctx, inode, _handle) } - fn releasedir( + fn setattr( &self, _ctx: &Context, - inode: Inode, - _flags: u32, - handle: Handle, - ) -> io::Result<()> { - self.pfs.releasedir(_ctx, inode, _flags, handle) + _inode: Inode, + _attr: libc::stat64, + _handle: Option, + _valid: SetattrValid, + ) -> io::Result<(libc::stat64, Duration)> { + Err(eacces!("Setattr request is not allowed in blobfs")) + } + + fn readlink(&self, _ctx: &Context, inode: Inode) -> io::Result> { + self.pfs.readlink(_ctx, inode) + } + + fn symlink( + &self, + _ctx: &Context, + _linkname: &CStr, + _parent: Inode, + _name: &CStr, + ) -> io::Result { + Err(eacces!("Symlink request is not allowed in blobfs")) + } + + fn mknod( + &self, + _ctx: &Context, + _parent: Inode, + _name: &CStr, + _mode: u32, + _rdev: u32, + _umask: u32, + ) -> io::Result { + Err(eacces!("Mknod request is not allowed in blobfs")) } - #[allow(unused)] fn mkdir( &self, _ctx: &Context, @@ -158,40 +138,37 @@ impl FileSystem for BlobFs { _mode: u32, _umask: u32, ) -> io::Result { - error!("do mkdir req error: blob file can not be written."); Err(eacces!("Mkdir request is not allowed in blobfs")) } - #[allow(unused)] + fn unlink(&self, _ctx: &Context, _parent: Inode, _name: &CStr) -> io::Result<()> { + Err(eacces!("Unlink request is not allowed in blobfs")) + } + fn rmdir(&self, _ctx: &Context, _parent: Inode, _name: &CStr) -> io::Result<()> { - error!("do rmdir req error: blob file can not be written."); Err(eacces!("Rmdir request is not allowed in blobfs")) } - fn readdir( + fn rename( &self, _ctx: &Context, - inode: Inode, - handle: Handle, - size: u32, - offset: u64, - add_entry: &mut dyn FnMut(DirEntry) -> io::Result, + _olddir: Inode, + _oldname: &CStr, + _newdir: Inode, + _newname: &CStr, + _flags: u32, ) -> io::Result<()> { - self.pfs - .readdir(_ctx, inode, handle, size, offset, add_entry) + Err(eacces!("Rename request is not allowed in blobfs")) } - fn readdirplus( + fn link( &self, _ctx: &Context, - inode: Inode, - handle: Handle, - size: u32, - offset: u64, - add_entry: &mut dyn FnMut(DirEntry, Entry) -> io::Result, - ) -> io::Result<()> { - self.pfs - .readdirplus(_ctx, inode, handle, size, offset, add_entry) + _inode: Inode, + _newparent: Inode, + _newname: &CStr, + ) -> io::Result { + Err(eacces!("Link request is not allowed in blobfs")) } fn open( @@ -204,28 +181,6 @@ impl FileSystem for BlobFs { self.pfs.open(_ctx, inode, flags, _fuse_flags) } - fn release( - &self, - _ctx: &Context, - inode: Inode, - _flags: u32, - handle: Handle, - _flush: bool, - _flock_release: bool, - _lock_owner: Option, - ) -> io::Result<()> { - self.pfs.release( - _ctx, - inode, - _flags, - handle, - _flush, - _flock_release, - _lock_owner, - ) - } - - #[allow(unused)] fn create( &self, _ctx: &Context, @@ -233,52 +188,9 @@ impl FileSystem for BlobFs { _name: &CStr, _args: CreateIn, ) -> io::Result<(Entry, Option, OpenOptions)> { - error!("do create req error: blob file cannot write."); Err(eacces!("Create request is not allowed in blobfs")) } - #[allow(unused)] - fn unlink(&self, _ctx: &Context, _parent: Inode, _name: &CStr) -> io::Result<()> { - error!("do unlink req error: blob file cannot write."); - Err(eacces!("Unlink request is not allowed in blobfs")) - } - - #[cfg(feature = "virtiofs")] - fn setupmapping( - &self, - _ctx: &Context, - inode: Inode, - _handle: Handle, - foffset: u64, - len: u64, - flags: u64, - moffset: u64, - vu_req: &mut dyn FsCacheReqHandler, - ) -> io::Result<()> { - debug!( - "blobfs: setupmapping ino {:?} foffset {} len {} flags {} moffset {}", - inode, foffset, len, flags, moffset - ); - - if (flags & virtio_fs::SetupmappingFlags::WRITE.bits()) != 0 { - return Err(eacces!("blob file cannot write in dax")); - } - self.load_chunks_on_demand(inode, foffset)?; - self.pfs - .setupmapping(_ctx, inode, _handle, foffset, len, flags, moffset, vu_req) - } - - #[cfg(feature = "virtiofs")] - fn removemapping( - &self, - _ctx: &Context, - _inode: Inode, - requests: Vec, - vu_req: &mut dyn FsCacheReqHandler, - ) -> io::Result<()> { - self.pfs.removemapping(_ctx, _inode, requests, vu_req) - } - fn read( &self, _ctx: &Context, @@ -290,13 +202,9 @@ impl FileSystem for BlobFs { _lock_owner: Option, _flags: u32, ) -> io::Result { - error!( - "do Read req error: blob file cannot do nondax read, please check if dax is enabled" - ); Err(eacces!("Read request is not allowed in blobfs")) } - #[allow(unused)] fn write( &self, _ctx: &Context, @@ -310,106 +218,128 @@ impl FileSystem for BlobFs { _flags: u32, _fuse_flags: u32, ) -> io::Result { - error!("do Write req error: blob file cannot write."); Err(eacces!("Write request is not allowed in blobfs")) } - fn getattr( + fn flush( &self, _ctx: &Context, inode: Inode, - _handle: Option, - ) -> io::Result<(libc::stat64, Duration)> { - self.pfs.getattr(_ctx, inode, _handle) + handle: Handle, + _lock_owner: u64, + ) -> io::Result<()> { + self.pfs.flush(_ctx, inode, handle, _lock_owner) } - #[allow(unused)] - fn setattr( + fn fsync( &self, _ctx: &Context, - _inode: Inode, - _attr: libc::stat64, - _handle: Option, - _valid: SetattrValid, - ) -> io::Result<(libc::stat64, Duration)> { - error!("do setattr req error: blob file cannot write."); - Err(eacces!("Setattr request is not allowed in blobfs")) + inode: Inode, + datasync: bool, + handle: Handle, + ) -> io::Result<()> { + self.pfs.fsync(_ctx, inode, datasync, handle) } - #[allow(unused)] - fn rename( + fn fallocate( &self, _ctx: &Context, - _olddir: Inode, - _oldname: &CStr, - _newdir: Inode, - _newname: &CStr, - _flags: u32, + _inode: Inode, + _handle: Handle, + _mode: u32, + _offset: u64, + _length: u64, ) -> io::Result<()> { - error!("do rename req error: blob file cannot write."); - Err(eacces!("Rename request is not allowed in blobfs")) + Err(eacces!("Fallocate request is not allowed in blobfs")) } - #[allow(unused)] - fn mknod( + fn release( &self, _ctx: &Context, - _parent: Inode, - _name: &CStr, - _mode: u32, - _rdev: u32, - _umask: u32, - ) -> io::Result { - error!("do mknode req error: blob file cannot write."); - Err(eacces!("Mknod request is not allowed in blobfs")) + inode: Inode, + _flags: u32, + handle: Handle, + _flush: bool, + _flock_release: bool, + _lock_owner: Option, + ) -> io::Result<()> { + self.pfs.release( + _ctx, + inode, + _flags, + handle, + _flush, + _flock_release, + _lock_owner, + ) } - #[allow(unused)] - fn link( + fn statfs(&self, _ctx: &Context, inode: Inode) -> io::Result { + self.pfs.statfs(_ctx, inode) + } + + fn setxattr( &self, _ctx: &Context, _inode: Inode, - _newparent: Inode, - _newname: &CStr, - ) -> io::Result { - error!("do link req error: blob file cannot write."); - Err(eacces!("Link request is not allowed in blobfs")) + _name: &CStr, + _value: &[u8], + _flags: u32, + ) -> io::Result<()> { + Err(eacces!("Setxattr request is not allowed in blobfs")) } - #[allow(unused)] - fn symlink( + fn getxattr( &self, _ctx: &Context, - _linkname: &CStr, - _parent: Inode, - _name: &CStr, - ) -> io::Result { - error!("do symlink req error: blob file cannot write."); - Err(eacces!("Symlink request is not allowed in blobfs")) + inode: Inode, + name: &CStr, + size: u32, + ) -> io::Result { + self.pfs.getxattr(_ctx, inode, name, size) } - fn readlink(&self, _ctx: &Context, inode: Inode) -> io::Result> { - self.pfs.readlink(_ctx, inode) + fn listxattr(&self, _ctx: &Context, inode: Inode, size: u32) -> io::Result { + self.pfs.listxattr(_ctx, inode, size) } - fn flush( + fn removexattr(&self, _ctx: &Context, _inode: Inode, _name: &CStr) -> io::Result<()> { + Err(eacces!("Removexattr request is not allowed in blobfs")) + } + + fn opendir( + &self, + _ctx: &Context, + inode: Inode, + flags: u32, + ) -> io::Result<(Option, OpenOptions)> { + self.pfs.opendir(_ctx, inode, flags) + } + + fn readdir( &self, _ctx: &Context, inode: Inode, handle: Handle, - _lock_owner: u64, + size: u32, + offset: u64, + add_entry: &mut dyn FnMut(DirEntry) -> io::Result, ) -> io::Result<()> { - self.pfs.flush(_ctx, inode, handle, _lock_owner) + self.pfs + .readdir(_ctx, inode, handle, size, offset, add_entry) } - fn fsync( + fn readdirplus( &self, _ctx: &Context, inode: Inode, - datasync: bool, handle: Handle, + size: u32, + offset: u64, + add_entry: &mut dyn FnMut(DirEntry, Entry) -> io::Result, ) -> io::Result<()> { - self.pfs.fsync(_ctx, inode, datasync, handle) + self.pfs + .readdirplus(_ctx, inode, handle, size, offset, add_entry) } fn fsyncdir( @@ -422,58 +352,49 @@ impl FileSystem for BlobFs { self.pfs.fsyncdir(ctx, inode, datasync, handle) } - fn access(&self, ctx: &Context, inode: Inode, mask: u32) -> io::Result<()> { - self.pfs.access(ctx, inode, mask) - } - - #[allow(unused)] - fn setxattr( + fn releasedir( &self, _ctx: &Context, - _inode: Inode, - _name: &CStr, - _value: &[u8], + inode: Inode, _flags: u32, + handle: Handle, ) -> io::Result<()> { - error!("do setxattr req error: blob file cannot write."); - Err(eacces!("Setxattr request is not allowed in blobfs")) + self.pfs.releasedir(_ctx, inode, _flags, handle) } - fn getxattr( + fn setupmapping( &self, _ctx: &Context, inode: Inode, - name: &CStr, - size: u32, - ) -> io::Result { - self.pfs.getxattr(_ctx, inode, name, size) - } - - fn listxattr(&self, _ctx: &Context, inode: Inode, size: u32) -> io::Result { - self.pfs.listxattr(_ctx, inode, size) - } - - #[allow(unused)] - fn removexattr(&self, _ctx: &Context, _inode: Inode, _name: &CStr) -> io::Result<()> { - error!("do removexattr req error: blob file cannot write."); - Err(eacces!("Removexattr request is not allowed in blobfs")) + _handle: Handle, + foffset: u64, + len: u64, + flags: u64, + moffset: u64, + vu_req: &mut dyn FsCacheReqHandler, + ) -> io::Result<()> { + if (flags & virtio_fs::SetupmappingFlags::WRITE.bits()) != 0 { + return Err(eacces!("blob file cannot write in dax")); + } + self.load_chunks_on_demand(inode, foffset)?; + self.pfs + .setupmapping(_ctx, inode, _handle, foffset, len, flags, moffset, vu_req) } - #[allow(unused)] - fn fallocate( + fn removemapping( &self, _ctx: &Context, _inode: Inode, - _handle: Handle, - _mode: u32, - _offset: u64, - _length: u64, + requests: Vec, + vu_req: &mut dyn FsCacheReqHandler, ) -> io::Result<()> { - error!("do fallocate req error: blob file cannot write."); - Err(eacces!("Fallocate request is not allowed in blobfs")) + self.pfs.removemapping(_ctx, _inode, requests, vu_req) + } + + fn access(&self, ctx: &Context, inode: Inode, mask: u32) -> io::Result<()> { + self.pfs.access(ctx, inode, mask) } - #[allow(unused)] fn lseek( &self, _ctx: &Context, diff --git a/rafs/src/lib.rs b/rafs/src/lib.rs index 062bc5f9877..cedea54ae97 100644 --- a/rafs/src/lib.rs +++ b/rafs/src/lib.rs @@ -48,6 +48,8 @@ use std::sync::Arc; use crate::metadata::{RafsInodeExt, RafsSuper}; +#[cfg(feature = "virtio-fs")] +pub mod blobfs; #[cfg(feature = "builder")] pub mod builder; pub mod fs;