Skip to content

Commit

Permalink
CI: add xfstests for passthrough fs
Browse files Browse the repository at this point in the history
Run xfstests on pathrough fs.

Signed-off-by: Wei Zhang <[email protected]>
  • Loading branch information
WeiZhang555 authored and jiangliu committed Oct 30, 2023
1 parent 4064d1b commit 6cc253d
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 1 deletion.
34 changes: 34 additions & 0 deletions .github/workflows/xfstests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Rust

on:
push:
branches: '*'
pull_request:
branches: [ master ]

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1

jobs:
xfstests_on_passthrough:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build passthrough binary
run: |
cd tests/passthrough
cargo build --release
sudo install -t /usr/sbin/ -m 700 ./target/release/passthrough
- name: Setup and run xfstest
run: |
cd $GITHUB_WORKSPACE
sudo ./tests/scripts/xfstests_pathr.sh
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/target
**/target
**/*.rs.bk
**/Cargo.lock
**/.vscode
Expand Down
13 changes: 13 additions & 0 deletions tests/passthrough/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "passthrough"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
fuse-backend-rs = { path = "../../", features = ["fusedev"] }
log = ">=0.4.6"
libc = ">=0.2.68"
simple_logger = ">=1.13.0"
signal-hook = ">=0.3.10"
201 changes: 201 additions & 0 deletions tests/passthrough/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
use log::{error, info, warn, LevelFilter};
use std::env;
use std::fs;
use std::io::{Error, Result};
use std::path::Path;
use std::sync::Arc;
use std::thread;

use signal_hook::{consts::TERM_SIGNALS, iterator::Signals};

use fuse_backend_rs::api::{server::Server, Vfs, VfsOptions};
use fuse_backend_rs::passthrough::{Config, PassthroughFs};
use fuse_backend_rs::transport::{FuseChannel, FuseSession};

use simple_logger::SimpleLogger;

/// A fusedev daemon example
#[allow(dead_code)]
pub struct Daemon {
mountpoint: String,
server: Arc<Server<Arc<Vfs>>>,
thread_cnt: u32,
session: Option<FuseSession>,
}

#[allow(dead_code)]
impl Daemon {
/// Creates a fusedev daemon instance
pub fn new(src: &str, mountpoint: &str, thread_cnt: u32) -> Result<Self> {
// create vfs
let vfs = Vfs::new(VfsOptions {
no_open: false,
no_opendir: false,
..Default::default()
});

// create passthrough fs
let mut cfg = Config::default();
cfg.root_dir = src.to_string();
cfg.do_import = false;
let fs = PassthroughFs::<()>::new(cfg).unwrap();
fs.import().unwrap();

// attach passthrough fs to vfs root
vfs.mount(Box::new(fs), "/").unwrap();

Ok(Daemon {
mountpoint: mountpoint.to_string(),
server: Arc::new(Server::new(Arc::new(vfs))),
thread_cnt,
session: None,
})
}

/// Mounts a fusedev daemon to the mountpoint, then start service threads to handle
/// FUSE requests.
pub fn mount(&mut self) -> Result<()> {
let mut se =
FuseSession::new(Path::new(&self.mountpoint), "testpassthrough", "", false).unwrap();
se.mount().unwrap();
for _ in 0..self.thread_cnt {
let mut server = FuseServer {
server: self.server.clone(),
ch: se.new_channel().unwrap(),
};
let _thread = thread::Builder::new()
.name("fuse_server".to_string())
.spawn(move || {
info!("new fuse thread");
let _ = server.svc_loop();
warn!("fuse service thread exits");
})
.unwrap();
}
self.session = Some(se);
Ok(())
}

/// Umounts and destroies a fusedev daemon
pub fn umount(&mut self) -> Result<()> {
if let Some(mut se) = self.session.take() {
se.umount().unwrap();
se.wake().unwrap();
}
Ok(())
}
}

impl Drop for Daemon {
fn drop(&mut self) {
let _ = self.umount();
}
}

struct FuseServer {
server: Arc<Server<Arc<Vfs>>>,
ch: FuseChannel,
}

impl FuseServer {
fn svc_loop(&mut self) -> Result<()> {
// Given error EBADF, it means kernel has shut down this session.
let _ebadf = std::io::Error::from_raw_os_error(libc::EBADF);
loop {
if let Some((reader, writer)) = self
.ch
.get_request()
.map_err(|_| std::io::Error::from_raw_os_error(libc::EINVAL))?
{
if let Err(e) = self
.server
.handle_message(reader, writer.into(), None, None)
{
match e {
fuse_backend_rs::Error::EncodeMessage(_ebadf) => {
break;
}
_ => {
error!("Handling fuse message failed");
continue;
}
}
}
} else {
info!("fuse server exits");
break;
}
}
Ok(())
}
}

struct Args {
src: String,
dest: String,
}

fn help() {
println!("Usage:\n passthrough <src> <dest>\n");
}

fn parse_args() -> Result<Args> {
let args = env::args().collect::<Vec<String>>();
let cmd_args = Args {
src: args[1].clone(),
dest: args[2].clone(),
};
if cmd_args.src.len() == 0 || cmd_args.dest.len() == 0 {
help();
return Err(Error::from_raw_os_error(libc::EINVAL));
}
Ok(cmd_args)
}

fn main() -> Result<()> {
SimpleLogger::new()
.with_level(LevelFilter::Info)
.init()
.unwrap();
let args = parse_args().unwrap();

// Check if src exists, create dir if not.
let src = Path::new(args.src.as_str());
let src_dir = src.to_str().unwrap();
if src.exists() {
if !src.is_dir() {
error!("src {} is not a directory", src_dir);
return Err(Error::from_raw_os_error(libc::EINVAL));
}
} else {
fs::create_dir_all(src_dir).unwrap();
}

let dest = Path::new(args.dest.as_str());
let dest_dir = dest.to_str().unwrap();
if dest.exists() {
if !dest.is_dir() {
error!("dest {} is not a directory", dest_dir);
return Err(Error::from_raw_os_error(libc::EINVAL));
}
} else {
fs::create_dir_all(dest_dir).unwrap();
}
info!(
"test passthroughfs src {:?} mountpoint {}",
src_dir, dest_dir
);

let mut daemon = Daemon::new(src_dir, dest_dir, 2).unwrap();
daemon.mount().unwrap();

// main thread
let mut signals = Signals::new(TERM_SIGNALS).unwrap();
for _sig in signals.forever() {
break;
}

daemon.umount().unwrap();

Ok(())
}
11 changes: 11 additions & 0 deletions tests/scripts/xfstests_pathr.exclude
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Exclude list for tests that we know are broken in passthrough fs
#
generic/002
generic/184
generic/426
generic/434
generic/467
generic/471
generic/477
generic/591
generic/633
48 changes: 48 additions & 0 deletions tests/scripts/xfstests_pathr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/bin/bash

current_dir=$(dirname $(realpath $0))

sudo apt-get update
sudo apt-get install acl attr automake bc dbench dump e2fsprogs fio gawk \
gcc git indent libacl1-dev libaio-dev libcap-dev libgdbm-dev libtool \
libtool-bin liburing-dev libuuid1 lvm2 make psmisc python3 quota sed \
uuid-dev uuid-runtime xfsprogs linux-headers-$(uname -r) sqlite3 \
exfatprogs f2fs-tools ocfs2-tools udftools xfsdump \
xfslibs-dev

# clone xfstests and install.
cd /tmp/
git clone git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git
cd xfstests-dev
make
sudo make install
# overwrite local config.
cat >local.config <<EOF
export TEST_DEV=testpassthrough
export TEST_DIR=/tmp/pathr_dst
export FSTYP=fuse
export FUSE_SUBTYP=.testpassthrough
EOF

# create fuse overlay mount script.
# /tmp/testoverlay must exists.
sudo cat >/usr/sbin/mount.fuse.testpassthrough <<EOF
#!/bin/bash
ulimit -n 1048576
exec /usr/sbin/passthrough /tmp/pathr_src /tmp/pathr_dst \
1>>/tmp/testpassthrough.log 2>&1 &
sleep 1
EOF
sudo chmod +x /usr/sbin/mount.fuse.testpassthrough

# create related dirs.
mkdir -p /tmp/pathr_src /tmp/pathr_dst

echo "====> Start to run xfstests."
# run tests.
cd /tmp/xfstests-dev
# Some tests are not supported by fuse or cannot pass currently.
sudo ./check -fuse -E $current_dir/xfstests_pathr.exclude


0 comments on commit 6cc253d

Please sign in to comment.