Initial import

This commit is contained in:
Brian Tarricone 2024-10-23 02:55:27 -07:00
commit 22484a62c4
6 changed files with 1269 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

499
Cargo.lock generated Normal file
View File

@ -0,0 +1,499 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clap"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]]
name = "colorchoice"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "corruptfs-fuse"
version = "0.1.0"
dependencies = [
"clap",
"env_logger",
"fuser",
"log",
"nix",
"rand",
]
[[package]]
name = "env_filter"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
]
[[package]]
name = "fuser"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e697f6f62c20b6fad1ba0f84ae909f25971cf16e735273524e3977c94604cf8"
dependencies = [
"libc",
"log",
"memchr",
"page_size",
"pkg-config",
"smallvec",
"zerocopy",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "libc"
version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "pkg-config"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "regex"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "corruptfs-fuse"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5.20", features = ["derive"] }
env_logger = "0.11.5"
fuser = { version = "0.14.0", features = ["abi-7-30"] }
log = { version = "0.4.22", features = ["std"] }
nix = { version = "0.29.0", features = ["user"] }
rand = "0.8.5"

29
README.md Normal file
View File

@ -0,0 +1,29 @@
This is a FUSE filesystem that always returns random corrupted data when
you read back files you write.
The filesystem will tell the kernel to use its filesystem buffer cache,
so you won't see the corruption unless you read with `O_DIRECT`.
Run with:
```
mkdir ~/corrupt-fs
cargo run -- --allow-direct-io ~/corrupt-fs
```
If you don't specify `--allow-direct-io`, direct I/O will not be
supported.
To test from the cmdline:
```
echo "hello world" > ~/corrupt-fs/hello.txt
cat ~/corrupt-fs/hello.txt # returns good data from fscache
dd if=~/corrupt-fs/hello.txt iflag=direct # returns bad data
```
Limitations:
* Subdirectories are not supported.
* `lseek()` modes other than `SEEK_SET` are not supported.
* There are probably bugs, especially with large files.

683
src/fs.rs Normal file
View File

@ -0,0 +1,683 @@
use std::{
cmp::min, collections::HashMap, ffi::{OsStr, OsString}, iter::repeat, time::{Duration, SystemTime}
};
use fuser::{consts::{FOPEN_DIRECT_IO, FOPEN_KEEP_CACHE, FUSE_WRITEBACK_CACHE}, FileAttr, FileType, Filesystem, TimeOrNow};
use nix::{
libc::{
EBADF, EBUSY, EEXIST, EINVAL, EISDIR, ENOENT, ENOTDIR, EPERM, O_DIRECT, O_RDONLY, SEEK_SET
},
unistd::{getgid, getuid},
};
const TTL: Duration = Duration::from_secs(1);
const BLOCK_SIZE: u32 = 512;
const ROOT_INODE: u64 = 1;
struct OpenFile {
ino: u64,
flags: i32,
}
enum OpenFd {
File(OpenFile),
Directory(OpenFile),
}
struct File {
ino: u64,
kind: FileType,
data: Vec<u8>,
mode: u32,
crtime: SystemTime,
atime: SystemTime,
mtime: SystemTime,
ctime: SystemTime,
}
impl File {
fn as_file_attr(&self) -> FileAttr {
let nlink = if self.ino == ROOT_INODE { 2 } else { 1 };
let attr = FileAttr {
ino: self.ino,
size: self.data.len().try_into().unwrap(),
blocks: block_count(self.data.len()),
atime: self.atime,
mtime: self.mtime,
ctime: self.ctime,
kind: self.kind,
crtime: self.crtime,
perm: self.mode as u16,
nlink,
uid: getuid().as_raw(),
gid: getgid().as_raw(),
rdev: 0,
blksize: BLOCK_SIZE,
flags: 0,
};
debug!("attr: {:?}", attr);
attr
}
}
pub struct CorruptFs {
ino_counter: u64,
fh_counter: u64,
files: HashMap<u64, File>, // inode -> File
inode_map: HashMap<OsString, u64>, // name -> inode
open_fds: HashMap<u64, OpenFd>, // fh -> OpenFd
allow_direct_io: bool,
}
impl CorruptFs {
pub fn new(allow_direct_io: bool) -> Self {
let mut fs = Self {
ino_counter: 2,
fh_counter: 1,
files: HashMap::new(),
inode_map: HashMap::new(),
open_fds: HashMap::new(),
allow_direct_io,
};
let now = SystemTime::now();
let rootdir = File {
ino: ROOT_INODE,
kind: FileType::Directory,
data: Vec::new(),
mode: 0o755,
crtime: now,
atime: now,
mtime: now,
ctime: now,
};
fs.inode_map.insert(OsString::from("."), rootdir.ino);
fs.inode_map.insert(OsString::from(".."), rootdir.ino);
fs.files.insert(rootdir.ino, rootdir);
fs
}
fn next_inode(&mut self) -> u64 {
let ino = self.ino_counter;
self.ino_counter += 1;
ino
}
fn next_fh(&mut self) -> u64 {
let fh = self.fh_counter;
self.fh_counter += 1;
fh
}
fn open_file(&self, fh: u64) -> Option<&OpenFile> {
self.open_fds.get(&fh).and_then(|open_fd| match open_fd {
OpenFd::File(open_file) => Some(open_file),
_ => None,
})
}
fn open_file_with_file_mut(&mut self, fh: u64) -> Option<(&mut File, &OpenFile)> {
self.open_fds
.get(&fh)
.and_then(|open_fd| match open_fd {
OpenFd::File(open_file) => Some(open_file),
_ => None,
})
.and_then(|open_file| {
self.files
.get_mut(&open_file.ino)
.map(|file| (file, open_file))
})
}
fn is_open_dir(&self, fh: u64) -> bool {
self.open_fds
.get(&fh)
.map(|open_fd| match open_fd {
OpenFd::Directory(_) => true,
_ => false,
})
.unwrap_or(false)
}
}
impl Filesystem for CorruptFs {
fn init(&mut self, _req: &fuser::Request<'_>, config: &mut fuser::KernelConfig) -> Result<(), nix::libc::c_int> {
if let Err(unsup) = config.add_capabilities(FUSE_WRITEBACK_CACHE) {
warn!("Unsupported kernel caps: 0x{:08x}", unsup);
}
Ok(())
}
fn statfs(&mut self, _req: &fuser::Request<'_>, _ino: u64, reply: fuser::ReplyStatfs) {
reply.statfs(
u64::MAX,
u64::MAX,
u64::MAX,
self.files.len() as u64,
u64::MAX,
BLOCK_SIZE,
4096,
BLOCK_SIZE,
);
}
fn lookup(
&mut self,
req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
reply: fuser::ReplyEntry,
) {
if req.uid() != getuid().as_raw() {
reply.error(EPERM);
} else if parent != ROOT_INODE {
reply.error(ENOENT);
} else if let Some(file) = self
.inode_map
.get(&name.to_os_string())
.and_then(|inode| self.files.get(inode))
{
reply.entry(&TTL, &file.as_file_attr(), 0);
} else {
reply.error(ENOENT);
}
}
fn getattr(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyAttr) {
debug!("getattr");
if let Some(file) = self.files.get(&ino) {
reply.attr(&TTL, &file.as_file_attr());
} else {
reply.error(ENOENT);
}
}
fn access(&mut self, req: &fuser::Request<'_>, ino: u64, _mask: i32, reply: fuser::ReplyEmpty) {
if req.uid() != getuid().as_raw() {
reply.error(EPERM);
} else if self.files.contains_key(&ino) {
debug!("returning OK to access request");
reply.ok();
} else {
reply.error(ENOENT);
}
}
fn setattr(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<fuser::TimeOrNow>,
mtime: Option<fuser::TimeOrNow>,
ctime: Option<SystemTime>,
fh: Option<u64>,
crtime: Option<SystemTime>,
_chgtime: Option<SystemTime>,
_bkuptime: Option<SystemTime>,
_flags: Option<u32>,
reply: fuser::ReplyAttr,
) {
if req.uid() != getuid().as_raw() {
reply.error(EPERM);
} else if let Some((file, _)) = fh.and_then(|fh| self.open_file_with_file_mut(fh)) {
do_setattr(file, mode, uid, gid, size, atime, mtime, ctime, crtime, reply);
} else if let Some(file) = self.files.get_mut(&ino) {
do_setattr(file, mode, uid, gid, size, atime, mtime, ctime, crtime, reply);
} else {
reply.error(ENOENT);
}
}
fn open(&mut self, req: &fuser::Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) {
if req.uid() != getuid().as_raw() {
reply.error(EPERM);
} else if (flags & O_DIRECT) != 0 && !self.allow_direct_io {
reply.error(EINVAL);
} else if self.files.contains_key(&ino) {
let fh = self.next_fh();
let open_file = OpenFile { ino, flags };
self.open_fds.insert(fh, OpenFd::File(open_file));
let mut o_flags = FOPEN_KEEP_CACHE;
if (flags & O_DIRECT) != 0 {
o_flags |= FOPEN_DIRECT_IO;
}
reply.opened(fh, o_flags);
} else {
reply.error(ENOENT);
}
}
fn create(
&mut self,
req: &fuser::Request<'_>,
_parent: u64,
name: &OsStr,
mode: u32,
_umask: u32,
flags: i32,
reply: fuser::ReplyCreate,
) {
if req.uid() != getuid().as_raw() {
reply.error(EPERM);
} else if self.inode_map.contains_key(&name.to_os_string()) {
reply.error(EEXIST);
} else if (flags & O_DIRECT) != 0 && !self.allow_direct_io {
reply.error(EINVAL);
} else {
debug!("create with mode {:o}", mode);
let now = SystemTime::now();
let file = File {
ino: self.next_inode(),
kind: FileType::RegularFile,
data: Vec::new(),
mode,
crtime: now,
atime: now,
mtime: now,
ctime: now,
};
let attr = file.as_file_attr();
self.inode_map.insert(name.to_os_string(), file.ino);
self.files.insert(file.ino, file);
let fh = self.next_fh();
let open_file = OpenFile {
ino: attr.ino,
flags,
};
self.open_fds.insert(fh, OpenFd::File(open_file));
let mut cr_flags = FOPEN_KEEP_CACHE;
if (flags & O_DIRECT) != 0 {
cr_flags |= FOPEN_DIRECT_IO;
}
reply.created(&TTL, &attr, 0, fh, cr_flags);
}
}
fn lseek(
&mut self,
_req: &fuser::Request<'_>,
_ino: u64,
fh: u64,
offset: i64,
whence: i32,
reply: fuser::ReplyLseek,
) {
if let Some((file, _)) = self.open_file_with_file_mut(fh) {
if whence == SEEK_SET {
if (offset as usize) < file.data.len() {
reply.offset(offset);
} else {
reply.error(EINVAL);
}
} else {
reply.error(EINVAL);
}
} else {
reply.error(EBADF);
}
}
fn read(
&mut self,
_req: &fuser::Request<'_>,
_ino: u64,
fh: u64,
offset: i64,
size: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: fuser::ReplyData,
) {
if let Some(file) = self
.open_file(fh)
.and_then(|open_file| self.files.get(&open_file.ino))
{
if offset as usize >= file.data.len() {
reply.error(EINVAL);
} else {
let bytes = min(size as usize, file.data.len() - (offset as usize)) as usize;
let data = std::iter::repeat_with(|| rand::random());
reply.data(&data.take(bytes).collect::<Vec<u8>>());
//reply.data(&file.data[(offset as usize)..bytes]);
}
} else {
reply.error(EBADF);
}
}
fn write(
&mut self,
_req: &fuser::Request<'_>,
_ino: u64,
fh: u64,
offset: i64,
data: &[u8],
_write_flags: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: fuser::ReplyWrite,
) {
if let Some((file, open_file)) = self.open_file_with_file_mut(fh) {
if (open_file.flags & O_RDONLY) != 0 {
reply.error(EBADF);
} else {
let offset = offset as usize;
if offset == file.data.len() {
file.data.extend_from_slice(data);
reply.written(data.len() as u32);
} else if offset > file.data.len() {
file.data.extend(repeat(0).take(offset - file.data.len()));
file.data.extend_from_slice(data);
reply.written(data.len() as u32);
} else {
let range = offset..min(file.data.len(), offset + data.len());
file.data.splice(range, data.iter().map(|v| *v));
reply.written(data.len() as u32);
}
}
} else {
reply.error(EBADF);
}
}
fn flush(
&mut self,
_req: &fuser::Request<'_>,
_ino: u64,
fh: u64,
_lock_owner: u64,
reply: fuser::ReplyEmpty,
) {
if let Some(open_file) = self.open_file(fh) {
if (open_file.flags & O_RDONLY) != 0 {
reply.error(EBADF);
} else {
reply.ok();
}
} else {
reply.error(EBADF);
}
}
fn release(
&mut self,
_req: &fuser::Request<'_>,
_ino: u64,
fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_flush: bool,
reply: fuser::ReplyEmpty,
) {
if let Some(open_fd) = self.open_fds.remove(&fh) {
match open_fd {
OpenFd::File(_) => reply.ok(),
OpenFd::Directory(open_dir) => {
self.open_fds.insert(fh, OpenFd::Directory(open_dir));
reply.error(EBADF);
}
}
} else {
reply.error(EBADF);
}
}
fn opendir(&mut self, req: &fuser::Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) {
if req.uid() != getuid().as_raw() {
debug!("opendir: bad user");
reply.error(EPERM);
} else if ino != ROOT_INODE {
debug!("opendir: bad ino");
if self.files.contains_key(&ino) {
reply.error(ENOTDIR);
} else {
reply.error(ENOENT);
}
} else {
debug!("opendir: ok");
let fh = self.next_fh();
let open_file = OpenFile {
ino: ROOT_INODE,
flags,
};
self.open_fds.insert(fh, OpenFd::Directory(open_file));
reply.opened(fh, 0);
}
}
fn readdir(
&mut self,
_req: &fuser::Request<'_>,
_ino: u64,
fh: u64,
offset: i64,
mut reply: fuser::ReplyDirectory,
) {
if self.is_open_dir(fh) {
for (i, (name, inode)) in self.inode_map.iter().skip(offset as usize).enumerate() {
let file = self.files.get(inode).unwrap();
debug!("readdir: adding file {:?}", name);
if reply.add(*inode, (i + 1) as i64, file.kind, name) {
break;
}
}
reply.ok();
} else {
reply.error(EBADF);
}
}
fn readdirplus(
&mut self,
_req: &fuser::Request<'_>,
_ino: u64,
fh: u64,
offset: i64,
mut reply: fuser::ReplyDirectoryPlus,
) {
if self.is_open_dir(fh) {
for (i, (name, inode)) in self.inode_map.iter().skip(offset as usize).enumerate() {
let file = self.files.get(inode).unwrap();
debug!("readdirplus: adding file {:?}", name);
if reply.add(*inode, (i + 1) as i64, name, &TTL, &file.as_file_attr(), 0) {
break;
}
}
reply.ok();
} else {
reply.error(EBADF);
}
}
fn releasedir(
&mut self,
_req: &fuser::Request<'_>,
_ino: u64,
fh: u64,
_flags: i32,
reply: fuser::ReplyEmpty,
) {
if let Some(open_fd) = self.open_fds.remove(&fh) {
match open_fd {
OpenFd::Directory(_) => reply.ok(),
OpenFd::File(open_file) => {
self.open_fds.insert(fh, OpenFd::File(open_file));
reply.error(EBADF);
}
}
} else {
reply.error(EBADF);
}
}
fn fsync(
&mut self,
_req: &fuser::Request<'_>,
_ino: u64,
_fh: u64,
_datasync: bool,
reply: fuser::ReplyEmpty,
) {
reply.ok();
}
fn fsyncdir(
&mut self,
_req: &fuser::Request<'_>,
_ino: u64,
_fh: u64,
_datasync: bool,
reply: fuser::ReplyEmpty,
) {
reply.ok();
}
fn unlink(
&mut self,
_req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
reply: fuser::ReplyEmpty,
) {
if parent != ROOT_INODE {
reply.error(ENOENT);
} else if let Some(file) = self
.inode_map
.get(&name.to_os_string())
.and_then(|ino| self.files.get(ino))
{
if file.ino == ROOT_INODE {
reply.error(EISDIR);
} else if file.kind == FileType::RegularFile {
let ino = file.ino;
self.inode_map.remove(&name.to_os_string());
self.files.remove(&ino);
reply.ok();
} else {
reply.error(EPERM);
}
} else {
reply.error(ENOENT);
}
}
fn rmdir(
&mut self,
_req: &fuser::Request<'_>,
_parent: u64,
name: &OsStr,
reply: fuser::ReplyEmpty,
) {
if name == "." {
reply.error(EBUSY);
} else {
reply.error(ENOENT);
}
}
fn rename(
&mut self,
req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
newparent: u64,
newname: &OsStr,
_flags: u32,
reply: fuser::ReplyEmpty,
) {
if req.uid() != getuid().as_raw() {
reply.error(EPERM);
} else if parent != ROOT_INODE || newparent != ROOT_INODE {
reply.error(ENOENT);
} else if let Some(file) = self
.inode_map
.get(&name.to_os_string())
.and_then(|ino| self.files.get(ino))
{
if file.ino == ROOT_INODE {
reply.error(EBUSY);
} else if file.kind == FileType::RegularFile {
self.inode_map.remove(&name.to_os_string());
self.inode_map.insert(newname.to_os_string(), file.ino);
reply.ok();
} else {
reply.error(EPERM);
}
} else {
reply.error(ENOENT);
}
}
}
fn block_count(size: usize) -> u64 {
let rem = if size % (BLOCK_SIZE as usize) != 0 {
1
} else {
0
};
(size / (BLOCK_SIZE as usize) + rem).try_into().unwrap()
}
fn do_setattr(
file: &mut File,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<TimeOrNow>,
mtime: Option<TimeOrNow>,
ctime: Option<SystemTime>,
crtime: Option<SystemTime>,
reply: fuser::ReplyAttr,
) {
if uid.filter(|uid| *uid != getuid().as_raw()).is_some() || gid.filter(|gid| *gid != getgid().as_raw()).is_some() {
reply.error(EPERM);
} else {
if let Some(mode) = mode {
file.mode = mode;
}
if let Some(size) = size {
let size = size as usize;
if file.data.len() < size {
file.data.extend(repeat(0).take(size - file.data.len()))
} else if file.data.len() > size {
file.data.truncate(size as usize);
}
}
if let Some(atime) = parse_timeornow(atime) {
file.atime = atime;
}
if let Some(mtime) = parse_timeornow(mtime) {
file.mtime = mtime;
}
if let Some(ctime) = ctime {
file.ctime = ctime;
}
if let Some(crtime) = crtime {
file.crtime = crtime;
}
reply.attr(&TTL, &file.as_file_attr());
}
}
fn parse_timeornow(t: Option<TimeOrNow>) -> Option<SystemTime> {
t.map(|t| match t {
TimeOrNow::Now => SystemTime::now(),
TimeOrNow::SpecificTime(st) => st,
})
}

45
src/main.rs Normal file
View File

@ -0,0 +1,45 @@
#[macro_use]
extern crate log;
use clap::Parser;
use env_logger::Env;
use fuser::MountOption;
mod fs;
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Args {
/// Pass FUSE options to library
#[arg(short = 'o', value_name = "OPT")]
options: Vec<String>,
/// Allow opening files with O_DIRECT
#[arg(long = "allow-direct-io")]
allow_direct_io: bool,
/// Mount point
#[arg(value_name = "MOUNT_POINT")]
mount_point: String,
}
fn main() {
env_logger::init_from_env(Env::new().filter("CORRUPTFS_LOG"));
let args = Args::parse();
debug!("extra options: {:?}", args.options);
let mut options = vec![
MountOption::FSName("corruptfs".to_string()),
MountOption::NoDev,
MountOption::NoSuid,
MountOption::RW,
];
options.extend(
args.options
.iter()
.map(|opt| MountOption::CUSTOM(opt.clone())),
);
fuser::mount2(fs::CorruptFs::new(args.allow_direct_io), args.mount_point, &options).unwrap();
}