diff --git a/__fs/fuse/Cargo.lock b/__fs/fuse/Cargo.lock
index a8ab021..50b72a9 100644
--- a/__fs/fuse/Cargo.lock
+++ b/__fs/fuse/Cargo.lock
@@ -11,6 +11,9 @@ name = "fuse"
version = "0.1.0"
dependencies = [
"fuse 0.3.1",
+ "libc",
+ "sha1",
+ "time",
]
[[package]]
@@ -56,6 +59,12 @@ version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+[[package]]
+name = "sha1"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
+
[[package]]
name = "thread-scoped"
version = "1.0.2"
diff --git a/__fs/fuse/Cargo.toml b/__fs/fuse/Cargo.toml
index 78c744c..c27477e 100644
--- a/__fs/fuse/Cargo.toml
+++ b/__fs/fuse/Cargo.toml
@@ -8,4 +8,6 @@ edition = "2018"
[dependencies]
fuse = "0.3"
-
+sha1 = "0.6"
+libc = "0.2"
+time = "0.1"
diff --git a/__fs/fuse/README.md b/__fs/fuse/README.md
index f848d29..422909e 100644
--- a/__fs/fuse/README.md
+++ b/__fs/fuse/README.md
@@ -1,8 +1,14 @@
-
+Mount
+```
+mkdir
+cargo r --
+```
Unmount
```
umount -f
```
+Reference:
+https://github.com/Minoru/plentyfs
diff --git a/__fs/fuse/src/main.rs b/__fs/fuse/src/main.rs
index e7a11a9..5634103 100644
--- a/__fs/fuse/src/main.rs
+++ b/__fs/fuse/src/main.rs
@@ -1,3 +1,193 @@
-fn main() {
- println!("Hello, world!");
+use std::convert::TryInto;
+use std::env;
+use std::ffi::OsStr;
+
+use fuse::{
+ FileAttr, FileType, Filesystem, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, Request,
+};
+use libc::ENOENT;
+use sha1::Sha1;
+use time::Timespec;
+
+const FILES_COUNT: u64 = 1_000;
+const FILE_SIZE: u64 = 1_048_576; // bytes (1 megabyte)
+const BLOCK_SIZE: usize = 20; // bytes (the size of SHA-1 digest)
+
+const TTL: Timespec = Timespec { sec: 1, nsec: 0 };
+
+const ROOT_DIR_ATTR: FileAttr = FileAttr {
+ ino: 1,
+ size: 0,
+ blocks: 0,
+ atime: UNIX_EPOCH, // 1970-01-01 00:00:00
+ mtime: UNIX_EPOCH,
+ ctime: UNIX_EPOCH,
+ crtime: UNIX_EPOCH,
+ kind: FileType::Directory,
+ perm: 0o755,
+ nlink: 2,
+ uid: 0,
+ gid: 0,
+ rdev: 0,
+ flags: 0,
+};
+
+const UNIX_EPOCH: Timespec = Timespec { sec: 0, nsec: 0 };
+
+const FILE_ATTR: FileAttr = FileAttr {
+ ino: 2,
+ size: FILE_SIZE,
+ blocks: 1,
+ atime: UNIX_EPOCH, // 1970-01-01 00:00:00
+ mtime: UNIX_EPOCH,
+ ctime: UNIX_EPOCH,
+ crtime: UNIX_EPOCH,
+ kind: FileType::RegularFile,
+ perm: 0o644,
+ nlink: 1,
+ uid: 0,
+ gid: 0,
+ rdev: 0,
+ flags: 0,
+};
+
+struct PlentyFS {
+ /// Initial value of our bespoke RNG.
+ seed: u64,
+}
+
+impl Filesystem for PlentyFS {
+ fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) {
+ if parent != 1 {
+ reply.error(ENOENT);
+ return;
+ }
+
+ match name.to_str().and_then(|s| s.parse::().ok()) {
+ Some(file_number) => {
+ if file_number < FILES_COUNT {
+ let file_attr = FileAttr {
+ ino: file_number + 2,
+ ..FILE_ATTR
+ };
+ reply.entry(&TTL, &file_attr, 0);
+ } else {
+ reply.error(ENOENT);
+ }
+ }
+
+ None => reply.error(ENOENT),
+ }
+ }
+
+ fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
+ if ino == 1 {
+ reply.attr(&TTL, &ROOT_DIR_ATTR);
+ } else if ino >= 2 && ino <= (FILES_COUNT + 1) {
+ let file_attr = FileAttr { ino, ..FILE_ATTR };
+ reply.attr(&TTL, &file_attr);
+ } else {
+ reply.error(ENOENT);
+ }
+ }
+
+ fn read(
+ &mut self,
+ _req: &Request,
+ ino: u64,
+ _fh: u64,
+ offset: i64,
+ size: u32,
+ reply: ReplyData,
+ ) {
+ if ino >= 2 && ino <= (FILES_COUNT + 1) {
+ let seed = generate_file_seed(self.seed, ino);
+
+ let first_block = (offset as u64) / (BLOCK_SIZE as u64);
+ let blocks_count = (size as u64 + 2 * BLOCK_SIZE as u64) / (BLOCK_SIZE as u64);
+
+ let mut data = vec![];
+ for block_no in first_block..(first_block + blocks_count) {
+ data.extend(&generate_block_data(seed, block_no));
+ }
+
+ let inside_offset = (offset as usize) % (BLOCK_SIZE as usize);
+ reply.data(&data[inside_offset..(inside_offset + size as usize)]);
+ } else {
+ reply.error(ENOENT);
+ }
+ }
+
+ fn readdir(
+ &mut self,
+ _req: &Request,
+ ino: u64,
+ _fh: u64,
+ mut offset: i64,
+ mut reply: ReplyDirectory,
+ ) {
+ if ino != 1 {
+ reply.error(ENOENT);
+ return;
+ }
+
+ if offset == 0 {
+ reply.add(1, 1, FileType::Directory, ".");
+ }
+
+ if offset <= 1 {
+ reply.add(1, 1, FileType::Directory, ".");
+ }
+
+ if offset >= 2 {
+ offset -= 2;
+ }
+
+ for file_number in (0..FILES_COUNT).skip(offset as usize) {
+ let inode = file_number + 2;
+ let next_entry_offset = 2 + file_number + 1;
+ reply.add(
+ inode,
+ next_entry_offset as i64,
+ FileType::RegularFile,
+ file_number.to_string(),
+ );
+ }
+ reply.ok();
+ }
+}
+
+fn main() {
+ let mountpoint = env::args_os().nth(1).unwrap();
+ let options = ["-o", "ro", "-o", "fsname=plentyfs"]
+ .iter()
+ .map(|o| o.as_ref())
+ .collect::>();
+ // TODO: replace PID by a proper source of entropy. Add an option for the user to set their own
+ // seed for reproducibility.
+ fuse::mount(
+ PlentyFS {
+ seed: std::process::id() as u64,
+ },
+ &mountpoint,
+ &options,
+ )
+ .unwrap();
+}
+
+fn generate_file_seed(root_seed: u64, inode: u64) -> u64 {
+ let file_salt = "filesalt".as_bytes();
+ let (file_salt, _) = file_salt.split_at(std::mem::size_of::());
+ // TODO: refactor to make panics impossible.
+ let file_salt: [u8; 8] = file_salt.try_into().unwrap();
+ let file_salt = u64::from_le_bytes(file_salt);
+
+ // TODO: replace XOR with a better mixing technique.
+ root_seed ^ inode ^ file_salt
+}
+
+fn generate_block_data(seed: u64, block_no: u64) -> [u8; BLOCK_SIZE] {
+ let mut sha1 = Sha1::from(seed.to_le_bytes());
+ sha1.update(&block_no.to_le_bytes());
+ sha1.digest().bytes()
}