Skip to content

Commit 7f338a9

Browse files
colinmarccberner
authored andcommitted
1 parent 99aa528 commit 7f338a9

File tree

2 files changed

+214
-1
lines changed

2 files changed

+214
-1
lines changed

Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ clap = { version = "4.4", features = ["cargo", "derive"] }
2929
bincode = "1.3.1"
3030
serde = { version = "1.0.102", features = ["std", "derive"] }
3131
tempfile = "3.10.1"
32-
nix = { version = "0.28.0", features = ["poll", "fs"] }
32+
nix = { version = "0.28.0", features = ["poll", "fs", "ioctl"] }
3333

3434
[build-dependencies]
3535
pkg-config = { version = "0.3.14", optional = true }
@@ -77,3 +77,7 @@ required-features = ["abi-7-12"]
7777
[[example]]
7878
name = "notify_inval_inode"
7979
required-features = ["abi-7-12"]
80+
81+
[[example]]
82+
name = "ioctl"
83+
required-features = ["abi-7-11"]

examples/ioctl.rs

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// This example requires fuse 7.11 or later. Run with:
2+
//
3+
// cargo run --example ioctl --features abi-7-11 /tmp/foobar
4+
5+
use clap::{crate_version, Arg, ArgAction, Command};
6+
use fuser::{
7+
FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry,
8+
Request,
9+
};
10+
use libc::{EINVAL, ENOENT};
11+
use log::debug;
12+
use std::ffi::OsStr;
13+
use std::time::{Duration, UNIX_EPOCH};
14+
15+
const TTL: Duration = Duration::from_secs(1); // 1 second
16+
17+
struct FiocFS {
18+
content: Vec<u8>,
19+
root_attr: FileAttr,
20+
fioc_file_attr: FileAttr,
21+
}
22+
23+
impl FiocFS {
24+
fn new() -> Self {
25+
let uid = unsafe { libc::getuid() };
26+
let gid = unsafe { libc::getgid() };
27+
28+
let root_attr = FileAttr {
29+
ino: 1,
30+
size: 0,
31+
blocks: 0,
32+
atime: UNIX_EPOCH, // 1970-01-01 00:00:00
33+
mtime: UNIX_EPOCH,
34+
ctime: UNIX_EPOCH,
35+
crtime: UNIX_EPOCH,
36+
kind: FileType::Directory,
37+
perm: 0o755,
38+
nlink: 2,
39+
uid,
40+
gid,
41+
rdev: 0,
42+
flags: 0,
43+
blksize: 512,
44+
};
45+
46+
let fioc_file_attr = FileAttr {
47+
ino: 2,
48+
size: 0,
49+
blocks: 1,
50+
atime: UNIX_EPOCH, // 1970-01-01 00:00:00
51+
mtime: UNIX_EPOCH,
52+
ctime: UNIX_EPOCH,
53+
crtime: UNIX_EPOCH,
54+
kind: FileType::RegularFile,
55+
perm: 0o644,
56+
nlink: 1,
57+
uid,
58+
gid,
59+
rdev: 0,
60+
flags: 0,
61+
blksize: 512,
62+
};
63+
64+
Self {
65+
content: vec![],
66+
root_attr,
67+
fioc_file_attr,
68+
}
69+
}
70+
}
71+
72+
impl Filesystem for FiocFS {
73+
fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) {
74+
if parent == 1 && name.to_str() == Some("fioc") {
75+
reply.entry(&TTL, &self.fioc_file_attr, 0);
76+
} else {
77+
reply.error(ENOENT);
78+
}
79+
}
80+
81+
fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option<u64>, reply: ReplyAttr) {
82+
match ino {
83+
1 => reply.attr(&TTL, &self.root_attr),
84+
2 => reply.attr(&TTL, &self.fioc_file_attr),
85+
_ => reply.error(ENOENT),
86+
}
87+
}
88+
89+
fn read(
90+
&mut self,
91+
_req: &Request,
92+
ino: u64,
93+
_fh: u64,
94+
offset: i64,
95+
_size: u32,
96+
_flags: i32,
97+
_lock: Option<u64>,
98+
reply: ReplyData,
99+
) {
100+
if ino == 2 {
101+
reply.data(&self.content[offset as usize..])
102+
} else {
103+
reply.error(ENOENT);
104+
}
105+
}
106+
107+
fn readdir(
108+
&mut self,
109+
_req: &Request,
110+
ino: u64,
111+
_fh: u64,
112+
offset: i64,
113+
mut reply: ReplyDirectory,
114+
) {
115+
if ino != 1 {
116+
reply.error(ENOENT);
117+
return;
118+
}
119+
120+
let entries = vec![
121+
(1, FileType::Directory, "."),
122+
(1, FileType::Directory, ".."),
123+
(2, FileType::RegularFile, "fioc"),
124+
];
125+
126+
for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) {
127+
// i + 1 means the index of the next entry
128+
if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) {
129+
break;
130+
}
131+
}
132+
reply.ok();
133+
}
134+
135+
fn ioctl(
136+
&mut self,
137+
_req: &Request<'_>,
138+
ino: u64,
139+
_fh: u64,
140+
_flags: u32,
141+
cmd: u32,
142+
in_data: &[u8],
143+
_out_size: u32,
144+
reply: fuser::ReplyIoctl,
145+
) {
146+
if ino != 2 {
147+
reply.error(EINVAL);
148+
return;
149+
}
150+
151+
const FIOC_GET_SIZE: u64 = nix::request_code_read!('E', 0, std::mem::size_of::<usize>());
152+
const FIOC_SET_SIZE: u64 = nix::request_code_write!('E', 1, std::mem::size_of::<usize>());
153+
154+
match cmd.into() {
155+
FIOC_GET_SIZE => {
156+
let size_bytes = self.content.len().to_ne_bytes();
157+
reply.ioctl(0, &size_bytes);
158+
}
159+
FIOC_SET_SIZE => {
160+
let new_size = usize::from_ne_bytes(in_data.try_into().unwrap());
161+
self.content = vec![0_u8; new_size];
162+
reply.ioctl(0, &[]);
163+
}
164+
_ => {
165+
debug!("unknown ioctl: {}", cmd);
166+
reply.error(EINVAL);
167+
}
168+
}
169+
}
170+
}
171+
172+
fn main() {
173+
let matches = Command::new("hello")
174+
.version(crate_version!())
175+
.author("Colin Marc")
176+
.arg(
177+
Arg::new("MOUNT_POINT")
178+
.required(true)
179+
.index(1)
180+
.help("Act as a client, and mount FUSE at given path"),
181+
)
182+
.arg(
183+
Arg::new("auto_unmount")
184+
.long("auto_unmount")
185+
.action(ArgAction::SetTrue)
186+
.help("Automatically unmount on process exit"),
187+
)
188+
.arg(
189+
Arg::new("allow-root")
190+
.long("allow-root")
191+
.action(ArgAction::SetTrue)
192+
.help("Allow root user to access filesystem"),
193+
)
194+
.get_matches();
195+
196+
env_logger::init();
197+
198+
let mountpoint = matches.get_one::<String>("MOUNT_POINT").unwrap();
199+
let mut options = vec![MountOption::FSName("fioc".to_string())];
200+
if matches.get_flag("auto_unmount") {
201+
options.push(MountOption::AutoUnmount);
202+
}
203+
if matches.get_flag("allow-root") {
204+
options.push(MountOption::AllowRoot);
205+
}
206+
207+
let fs = FiocFS::new();
208+
fuser::mount2(fs, mountpoint, &options).unwrap();
209+
}

0 commit comments

Comments
 (0)