Skip to content

Commit 6cc253d

Browse files
WeiZhang555jiangliu
authored andcommitted
CI: add xfstests for passthrough fs
Run xfstests on pathrough fs. Signed-off-by: Wei Zhang <[email protected]>
1 parent 4064d1b commit 6cc253d

File tree

6 files changed

+308
-1
lines changed

6 files changed

+308
-1
lines changed

.github/workflows/xfstests.yml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Rust
2+
3+
on:
4+
push:
5+
branches: '*'
6+
pull_request:
7+
branches: [ master ]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
RUST_BACKTRACE: 1
12+
13+
jobs:
14+
xfstests_on_passthrough:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
- name: Install Rust
20+
uses: actions-rs/toolchain@v1
21+
with:
22+
profile: minimal
23+
toolchain: stable
24+
override: true
25+
- name: Build passthrough binary
26+
run: |
27+
cd tests/passthrough
28+
cargo build --release
29+
sudo install -t /usr/sbin/ -m 700 ./target/release/passthrough
30+
- name: Setup and run xfstest
31+
run: |
32+
cd $GITHUB_WORKSPACE
33+
sudo ./tests/scripts/xfstests_pathr.sh
34+

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/target
1+
**/target
22
**/*.rs.bk
33
**/Cargo.lock
44
**/.vscode

tests/passthrough/Cargo.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "passthrough"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
fuse-backend-rs = { path = "../../", features = ["fusedev"] }
10+
log = ">=0.4.6"
11+
libc = ">=0.2.68"
12+
simple_logger = ">=1.13.0"
13+
signal-hook = ">=0.3.10"

tests/passthrough/src/main.rs

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
use log::{error, info, warn, LevelFilter};
2+
use std::env;
3+
use std::fs;
4+
use std::io::{Error, Result};
5+
use std::path::Path;
6+
use std::sync::Arc;
7+
use std::thread;
8+
9+
use signal_hook::{consts::TERM_SIGNALS, iterator::Signals};
10+
11+
use fuse_backend_rs::api::{server::Server, Vfs, VfsOptions};
12+
use fuse_backend_rs::passthrough::{Config, PassthroughFs};
13+
use fuse_backend_rs::transport::{FuseChannel, FuseSession};
14+
15+
use simple_logger::SimpleLogger;
16+
17+
/// A fusedev daemon example
18+
#[allow(dead_code)]
19+
pub struct Daemon {
20+
mountpoint: String,
21+
server: Arc<Server<Arc<Vfs>>>,
22+
thread_cnt: u32,
23+
session: Option<FuseSession>,
24+
}
25+
26+
#[allow(dead_code)]
27+
impl Daemon {
28+
/// Creates a fusedev daemon instance
29+
pub fn new(src: &str, mountpoint: &str, thread_cnt: u32) -> Result<Self> {
30+
// create vfs
31+
let vfs = Vfs::new(VfsOptions {
32+
no_open: false,
33+
no_opendir: false,
34+
..Default::default()
35+
});
36+
37+
// create passthrough fs
38+
let mut cfg = Config::default();
39+
cfg.root_dir = src.to_string();
40+
cfg.do_import = false;
41+
let fs = PassthroughFs::<()>::new(cfg).unwrap();
42+
fs.import().unwrap();
43+
44+
// attach passthrough fs to vfs root
45+
vfs.mount(Box::new(fs), "/").unwrap();
46+
47+
Ok(Daemon {
48+
mountpoint: mountpoint.to_string(),
49+
server: Arc::new(Server::new(Arc::new(vfs))),
50+
thread_cnt,
51+
session: None,
52+
})
53+
}
54+
55+
/// Mounts a fusedev daemon to the mountpoint, then start service threads to handle
56+
/// FUSE requests.
57+
pub fn mount(&mut self) -> Result<()> {
58+
let mut se =
59+
FuseSession::new(Path::new(&self.mountpoint), "testpassthrough", "", false).unwrap();
60+
se.mount().unwrap();
61+
for _ in 0..self.thread_cnt {
62+
let mut server = FuseServer {
63+
server: self.server.clone(),
64+
ch: se.new_channel().unwrap(),
65+
};
66+
let _thread = thread::Builder::new()
67+
.name("fuse_server".to_string())
68+
.spawn(move || {
69+
info!("new fuse thread");
70+
let _ = server.svc_loop();
71+
warn!("fuse service thread exits");
72+
})
73+
.unwrap();
74+
}
75+
self.session = Some(se);
76+
Ok(())
77+
}
78+
79+
/// Umounts and destroies a fusedev daemon
80+
pub fn umount(&mut self) -> Result<()> {
81+
if let Some(mut se) = self.session.take() {
82+
se.umount().unwrap();
83+
se.wake().unwrap();
84+
}
85+
Ok(())
86+
}
87+
}
88+
89+
impl Drop for Daemon {
90+
fn drop(&mut self) {
91+
let _ = self.umount();
92+
}
93+
}
94+
95+
struct FuseServer {
96+
server: Arc<Server<Arc<Vfs>>>,
97+
ch: FuseChannel,
98+
}
99+
100+
impl FuseServer {
101+
fn svc_loop(&mut self) -> Result<()> {
102+
// Given error EBADF, it means kernel has shut down this session.
103+
let _ebadf = std::io::Error::from_raw_os_error(libc::EBADF);
104+
loop {
105+
if let Some((reader, writer)) = self
106+
.ch
107+
.get_request()
108+
.map_err(|_| std::io::Error::from_raw_os_error(libc::EINVAL))?
109+
{
110+
if let Err(e) = self
111+
.server
112+
.handle_message(reader, writer.into(), None, None)
113+
{
114+
match e {
115+
fuse_backend_rs::Error::EncodeMessage(_ebadf) => {
116+
break;
117+
}
118+
_ => {
119+
error!("Handling fuse message failed");
120+
continue;
121+
}
122+
}
123+
}
124+
} else {
125+
info!("fuse server exits");
126+
break;
127+
}
128+
}
129+
Ok(())
130+
}
131+
}
132+
133+
struct Args {
134+
src: String,
135+
dest: String,
136+
}
137+
138+
fn help() {
139+
println!("Usage:\n passthrough <src> <dest>\n");
140+
}
141+
142+
fn parse_args() -> Result<Args> {
143+
let args = env::args().collect::<Vec<String>>();
144+
let cmd_args = Args {
145+
src: args[1].clone(),
146+
dest: args[2].clone(),
147+
};
148+
if cmd_args.src.len() == 0 || cmd_args.dest.len() == 0 {
149+
help();
150+
return Err(Error::from_raw_os_error(libc::EINVAL));
151+
}
152+
Ok(cmd_args)
153+
}
154+
155+
fn main() -> Result<()> {
156+
SimpleLogger::new()
157+
.with_level(LevelFilter::Info)
158+
.init()
159+
.unwrap();
160+
let args = parse_args().unwrap();
161+
162+
// Check if src exists, create dir if not.
163+
let src = Path::new(args.src.as_str());
164+
let src_dir = src.to_str().unwrap();
165+
if src.exists() {
166+
if !src.is_dir() {
167+
error!("src {} is not a directory", src_dir);
168+
return Err(Error::from_raw_os_error(libc::EINVAL));
169+
}
170+
} else {
171+
fs::create_dir_all(src_dir).unwrap();
172+
}
173+
174+
let dest = Path::new(args.dest.as_str());
175+
let dest_dir = dest.to_str().unwrap();
176+
if dest.exists() {
177+
if !dest.is_dir() {
178+
error!("dest {} is not a directory", dest_dir);
179+
return Err(Error::from_raw_os_error(libc::EINVAL));
180+
}
181+
} else {
182+
fs::create_dir_all(dest_dir).unwrap();
183+
}
184+
info!(
185+
"test passthroughfs src {:?} mountpoint {}",
186+
src_dir, dest_dir
187+
);
188+
189+
let mut daemon = Daemon::new(src_dir, dest_dir, 2).unwrap();
190+
daemon.mount().unwrap();
191+
192+
// main thread
193+
let mut signals = Signals::new(TERM_SIGNALS).unwrap();
194+
for _sig in signals.forever() {
195+
break;
196+
}
197+
198+
daemon.umount().unwrap();
199+
200+
Ok(())
201+
}

tests/scripts/xfstests_pathr.exclude

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Exclude list for tests that we know are broken in passthrough fs
2+
#
3+
generic/002
4+
generic/184
5+
generic/426
6+
generic/434
7+
generic/467
8+
generic/471
9+
generic/477
10+
generic/591
11+
generic/633

tests/scripts/xfstests_pathr.sh

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/bin/bash
2+
3+
current_dir=$(dirname $(realpath $0))
4+
5+
sudo apt-get update
6+
sudo apt-get install acl attr automake bc dbench dump e2fsprogs fio gawk \
7+
gcc git indent libacl1-dev libaio-dev libcap-dev libgdbm-dev libtool \
8+
libtool-bin liburing-dev libuuid1 lvm2 make psmisc python3 quota sed \
9+
uuid-dev uuid-runtime xfsprogs linux-headers-$(uname -r) sqlite3 \
10+
exfatprogs f2fs-tools ocfs2-tools udftools xfsdump \
11+
xfslibs-dev
12+
13+
# clone xfstests and install.
14+
cd /tmp/
15+
git clone git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git
16+
cd xfstests-dev
17+
make
18+
sudo make install
19+
# overwrite local config.
20+
cat >local.config <<EOF
21+
export TEST_DEV=testpassthrough
22+
export TEST_DIR=/tmp/pathr_dst
23+
export FSTYP=fuse
24+
export FUSE_SUBTYP=.testpassthrough
25+
EOF
26+
27+
# create fuse overlay mount script.
28+
# /tmp/testoverlay must exists.
29+
sudo cat >/usr/sbin/mount.fuse.testpassthrough <<EOF
30+
#!/bin/bash
31+
32+
ulimit -n 1048576
33+
exec /usr/sbin/passthrough /tmp/pathr_src /tmp/pathr_dst \
34+
1>>/tmp/testpassthrough.log 2>&1 &
35+
sleep 1
36+
EOF
37+
sudo chmod +x /usr/sbin/mount.fuse.testpassthrough
38+
39+
# create related dirs.
40+
mkdir -p /tmp/pathr_src /tmp/pathr_dst
41+
42+
echo "====> Start to run xfstests."
43+
# run tests.
44+
cd /tmp/xfstests-dev
45+
# Some tests are not supported by fuse or cannot pass currently.
46+
sudo ./check -fuse -E $current_dir/xfstests_pathr.exclude
47+
48+

0 commit comments

Comments
 (0)