From 4db110073f0a751750943b1b534166ba93174794 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 12 Nov 2024 20:05:12 +0200 Subject: [PATCH 001/150] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 25a83fce..b6eabd91 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ You can also store it in a cloud storage service like Google Drive, Dropbox, etc You can use it as CLI or as a library to build your custom FUSE implementation or other apps that work with encrypted data. +# GUI + +There is a [GUI](https://github.com/radumarias/rencfs-desktop/blob/main/demo.gif) too. + # Motivation Create a `simple,` `performant,` `modular` and `ergonomic` yet `very secure` `encrypted filesystem` to protect your `privacy`, which is also `open source` and is correctly and safely using `well-known audited` crates as `cryptographic primitives.` From 3dcb38b2149c531550877263d3bd2fe73d0fd3f2 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Sat, 16 Nov 2024 10:30:11 +0200 Subject: [PATCH 002/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 19863ebd..cedf1c74 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache License, shall be dual-licensed as above, without any additional terms or conditions. -1. Join [slack](https://join.slack.com/t/rencfs/shared_invite/zt-2o4l1tdkk-VJeWIbO2p6zgeafDISPHbQ) +1. Join [slack](https://bit.ly/3UU1oXi) 2. Become familiar with docs and code by reading the [ramp-up](Ramp-up.md) guide 3. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and be able to create your own branches and not needing to fork the repo 4. Pick an open issue or a task in the corresponding [project](https://github.com/users/radumarias/projects/1) for the repo that you'll be working on. You can see [good for first issues](https://github.com/radumarias/rencfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) that you can pick from From 60e063580b0ebf571041e90efc1d53b94310ba56 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Sat, 16 Nov 2024 11:58:03 +0200 Subject: [PATCH 003/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cedf1c74..9f1d5bd1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache License, shall be dual-licensed as above, without any additional terms or conditions. -1. Join [slack](https://bit.ly/3UU1oXi) +1. Join [slack](https://bit.ly/3UU1oXi) and join `#dev-beginners` channel 2. Become familiar with docs and code by reading the [ramp-up](Ramp-up.md) guide 3. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and be able to create your own branches and not needing to fork the repo 4. Pick an open issue or a task in the corresponding [project](https://github.com/users/radumarias/projects/1) for the repo that you'll be working on. You can see [good for first issues](https://github.com/radumarias/rencfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) that you can pick from From 7780ea047f44294509484798a15aac2c669cb2e3 Mon Sep 17 00:00:00 2001 From: Marios Date: Sun, 17 Nov 2024 17:03:58 +0200 Subject: [PATCH 004/150] Add more examples (#221) --- examples/file_handling.rs | 86 +++++++++++++++++++++++++++++++++++++++ src/crypto.rs | 1 - 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 examples/file_handling.rs diff --git a/examples/file_handling.rs b/examples/file_handling.rs new file mode 100644 index 00000000..3482ef81 --- /dev/null +++ b/examples/file_handling.rs @@ -0,0 +1,86 @@ +use core::str::FromStr; + +use anyhow::Result; +use rencfs::{ + crypto::Cipher, + encryptedfs::{ + write_all_string_to_fs, CreateFileAttr, EncryptedFs, FileType, PasswordProvider, + }, +}; +use shush_rs::SecretString; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +const ROOT_INODE: u64 = 1; + +struct PasswordProviderImpl; + +impl PasswordProvider for PasswordProviderImpl { + fn get_password(&self) -> Option { + Some(SecretString::from_str("password").unwrap()) + } +} + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt().init(); + + let data_dir = Path::new("/tmp/data_test").to_path_buf(); + clean_up_directory(&data_dir)?; + + let cipher = Cipher::ChaCha20Poly1305; + let fs = EncryptedFs::new( + data_dir.clone(), + Box::new(PasswordProviderImpl), + cipher, + false, + ) + .await?; + + let file_name = SecretString::from_str("file1").unwrap(); + let (file_handle, attr) = fs + .create(ROOT_INODE, &file_name, file_attributes(), false, true) + .await?; + + let data = "Hello, world!"; + write_all_string_to_fs(&fs, attr.ino, 0, data, file_handle).await?; + + fs.flush(file_handle).await?; + fs.release(file_handle).await?; + + let file_handle = fs.open(attr.ino, true, false).await?; + let mut buffer = vec![0; data.len()]; + fs.read(attr.ino, 0, &mut buffer, file_handle).await?; + fs.release(file_handle).await?; + + assert_eq!(data, String::from_utf8(buffer)?); + + assert!(fs.exists_by_name(ROOT_INODE, &file_name)?); + fs.remove_file(ROOT_INODE, &file_name).await?; + assert!(!fs.exists_by_name(ROOT_INODE, &file_name)?); + + clean_up_directory(&data_dir)?; + + Ok(()) +} + +const fn file_attributes() -> CreateFileAttr { + CreateFileAttr { + kind: FileType::RegularFile, + perm: 0o644, // Permissions + uid: 0, // User ID + gid: 0, // Group ID + rdev: 0, // Device ID + flags: 0, // File flags + } +} + +fn clean_up_directory(dir: &PathBuf) -> Result<()> { + if dir.exists() { + fs::remove_dir_all(dir)?; + } + + Ok(()) +} diff --git a/src/crypto.rs b/src/crypto.rs index a1e8f8f4..df0f0d35 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -374,7 +374,6 @@ where { let parent = file.parent().ok_or(Error::Generic("file has no parent"))?; let mut file = fs_util::open_atomic_write(file)?; - // println!("file: {:#?}", file.as_file_mut().metadata()?); file = serialize_encrypt_into(file, value, cipher, key)?; file.commit()?; File::open(parent)?.sync_all()?; From 5ab7b48ecd0ac7861969f87f7d2c8385f2c63dab Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 19 Nov 2024 00:33:20 +0200 Subject: [PATCH 005/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f1d5bd1..7fd31788 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted 11. **DON'T INCREASE THE VERSION NUMBER IN `Cargo.toml`, WE WILL DO THAN WHEN RELEASING** 12. Create a `git` `commit hook` file in `.git/hooks/pre-commit` with [this](hooks/linux-macos/pre-commit) content on `Linux` and `MacOS`, and [this](hooks/windows/pre-commit) on `Windows`. Make it executable in Linux and macOS with `chmod +x .git/hooks/pre-commit` .This will run when you do `git commit` and will make the commit to be quite slow, but please give it time to complete as this helps to fix any issues locally and not relying just on running `ci` on GitHub when you create the PR 13. Commit your changes and if there are any errors fix them before you push them -14. Push your changes and create a `PR` back to the `parent` repo targeting the `main` branch and request review from owners of the repository +14. Push your changes and create a `PR` back to the `parent` repo targeting the `main` branch and request review from owners of the repository, by adding them to the `Reviewers` field 15. Monitor the checks (GitHub actions runs) and fix the code if they are failing 16. Respond to any comments 17. **DON'T MERGE THE PR YOURSELF, LEAVE THAT TO REPOSITORY OWNERS** From db61d2298e0051a4437c8b6e4f507aa2b78bb418 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 19 Nov 2024 17:52:24 +0200 Subject: [PATCH 006/150] Update README.md --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b6eabd91..cad27b73 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,12 @@ It was [crate of the week](https://this-week-in-rust.org/blog/2024/08/14/this-we # Talks -- [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) -- [Basics of cryptography and FUSE for building a filesystem in Rust](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323) +- [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust @meetup.com/star-tech-rd-reloaded and @OmniOpenCon](https://startech-rd.io/hitchhikers-guide-to/) +- [Basics of cryptography, Authenticated Encryption, Rust in cryptography and how to build an encrypted filesystem @ITDays](https://www.youtube.com/live/HwmVxOl3pQg), [slides](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323). + + +Talk: https://startech-rd.io/hitchhikers-guide-to/ +Another talk about cryptography and FUSE: https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=919818831100 # Key features @@ -98,7 +102,7 @@ changes at the next start. This makes the write operations atomic. [![rencfs](website/resources/layers.png)](website/resources/layers.png) -For detailed description of the various sequence flows please look into [Flows](docs/flows.md). +For detailed description of the various sequence flows please look into [Flows](docs/flows.md). # Stack From 46085827a6371aab01de9a5f39bc77eed1c45d39 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 19 Nov 2024 18:02:35 +0200 Subject: [PATCH 007/150] Update README.md --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cad27b73..899a30b1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![build-and-tests](https://github.com/radumarias/rencfs/actions/workflows/build_and_tests.yaml/badge.svg)](https://github.com/radumarias/rencfs/actions/workflows/build_and_tests.yaml) [![release](https://github.com/radumarias/rencfs/actions/workflows/release.yaml/badge.svg)](https://github.com/radumarias/rencfs/actions/workflows/release.yaml) [![codecov](https://codecov.io/gh/radumarias/rencfs/graph/badge.svg?token=NUQI6XGF2Y)](https://codecov.io/gh/radumarias/rencfs) - + [![Matrix](https://img.shields.io/matrix/rencfs%3Amatrix.org?label=Matrix)](https://matrix.to/#/#rencfs:matrix.org) [![Discord](https://img.shields.io/discord/1236855443486277653?label=Discord)](https://discord.com/channels/1236855443486277653/1236855448515252306) [![Zulip](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?label=Zulip)](https://rencfs.zulipchat.com) @@ -42,18 +42,14 @@ Create a `simple,` `performant,` `modular` and `ergonomic` yet `very secure` `en There will be a [series](https://medium.com/@xorio42/list/828492b94c23) of articles about the evolution of this project, trying to keep it like a tutorial. This is the [first one](https://systemweakness.com/the-hitchhikers-guide-to-building-an-encrypted-filesystem-in-rust-4d678c57d65c). -# Crate of the week in [This Week in Rust](https://this-week-in-rust.org/) +# Crate of the week in [This Week in Rust](https://this-week-in-rust.org/blog/2024/08/07/this-week-in-rust-559/#cfp-projects) It was [crate of the week](https://this-week-in-rust.org/blog/2024/08/14/this-week-in-rust-560/#crate-of-the-week) in Aug 2024. # Talks -- [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust @meetup.com/star-tech-rd-reloaded and @OmniOpenCon](https://startech-rd.io/hitchhikers-guide-to/) -- [Basics of cryptography, Authenticated Encryption, Rust in cryptography and how to build an encrypted filesystem @ITDays](https://www.youtube.com/live/HwmVxOl3pQg), [slides](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323). - - -Talk: https://startech-rd.io/hitchhikers-guide-to/ -Another talk about cryptography and FUSE: https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=919818831100 +- [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) [@meetup.com/star-tech-rd-reloaded](https://www.meetup.com/star-tech-rd-reloaded/) and [@OmniOpenCon](https://omniopencon.org/) +- [Basics of cryptography, Authenticated Encryption, Rust in cryptography and how to build an encrypted filesystem](https://www.youtube.com/live/HwmVxOl3pQg) @ITDays and [slides](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323). # Key features From f47a0214494fdb1e177d90ea0fd3a39062b06344 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 19 Nov 2024 18:18:40 +0200 Subject: [PATCH 008/150] Update README.md --- README.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/README.md b/README.md index 899a30b1..a96a1dd4 100644 --- a/README.md +++ b/README.md @@ -498,18 +498,7 @@ files. However, the content of the file could be bigger, and we read until the o pick up the new zeros bytes are written on truncating by increasing the size. If content is smaller, the read would stop and end-of-file of the actual content, so this would not be such a big issue -- **What kind of metadata does it leak**: close to none. The filename, actual file size and other file attrs (times, - permissions, other flags) are kept encrypted. What it could possibly leak is the following - - If a directory has children, we keep those children in a directory with name as inode number and encrypted names - of children as files in it. - So we could see how many children a directory has. - However, we can't identify that actual directory name; - We can just see its inode number (internal representation like an ID for each file), but we cannot see the actual - filenames of the directory or children. - Also, we cannot identify which file content corresponds to a directory child - - Each file content is saved in a separate file, so we can see the size of the encrypted content but not the - actual filesize - - We can also see the last time the file was accessed +- **What kind of metadata does it leak**: None, we encrypt filename, content, metadata and we hide files count, size amd all time fields - It's always recommended to use encrypted disks for at least your sensitive data; this project is not a replacement for that - To reduce the risk of the encryption key being exposed from memory, it's recommended to disable memory dumps on the From e639b755cf6f0dd82ded82dddd738338200a6512 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 19 Nov 2024 18:19:37 +0200 Subject: [PATCH 009/150] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a96a1dd4..645046e1 100644 --- a/README.md +++ b/README.md @@ -498,7 +498,7 @@ files. However, the content of the file could be bigger, and we read until the o pick up the new zeros bytes are written on truncating by increasing the size. If content is smaller, the read would stop and end-of-file of the actual content, so this would not be such a big issue -- **What kind of metadata does it leak**: None, we encrypt filename, content, metadata and we hide files count, size amd all time fields +- **What kind of metadata does it leak**: None, we encrypt filename, content, metadata and we hide files count, size and all time fields - It's always recommended to use encrypted disks for at least your sensitive data; this project is not a replacement for that - To reduce the risk of the encryption key being exposed from memory, it's recommended to disable memory dumps on the From b2b880cadefc6bb1e3ae8a115c1cfe03755934c1 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 19 Nov 2024 18:30:11 +0200 Subject: [PATCH 010/150] clippy --- src/encryptedfs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 5179f346..af9293a7 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -2167,7 +2167,7 @@ impl EncryptedFs { if let Some(set) = lock.get(&ino) { for handle in set .iter() - .filter(|h| skip_write_fh.map_or(true, |fh| **h != fh)) + .filter(|h| skip_write_fh != Some(**h)) { let guard = self.read_handles.read().await; let ctx = guard.get(handle).unwrap().lock().await; From 32f95b602d51edf47f88abc873e2374d76539d60 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 19 Nov 2024 18:30:55 +0200 Subject: [PATCH 011/150] clippy --- src/encryptedfs.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index af9293a7..f1a8da97 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -2165,10 +2165,7 @@ impl EncryptedFs { // read let lock = self.opened_files_for_read.read().await; if let Some(set) = lock.get(&ino) { - for handle in set - .iter() - .filter(|h| skip_write_fh != Some(**h)) - { + for handle in set.iter().filter(|h| skip_write_fh != Some(**h)) { let guard = self.read_handles.read().await; let ctx = guard.get(handle).unwrap().lock().await; let set_attr: SetFileAttr = ctx.attr.clone().into(); From 4701f28d669acfe573448619e0b6273d4662f904 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 22 Nov 2024 18:31:00 +0200 Subject: [PATCH 012/150] migrate one test to criterion --- Cargo.lock | 251 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 + benches/crypto_read.rs | 145 ++++++++++++++++++++++ java-bridge/Cargo.lock | 223 ++++++++++++++++++++++++++++++++++ src/crypto/read.rs | 1 - src/crypto/read/bench.rs | 146 ----------------------- 6 files changed, 624 insertions(+), 147 deletions(-) create mode 100644 benches/crypto_read.rs delete mode 100644 src/crypto/read/bench.rs diff --git a/Cargo.lock b/Cargo.lock index ae3d31b8..1f1cd04c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,12 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.15" @@ -483,6 +489,12 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cbc" version = "0.1.2" @@ -525,6 +537,33 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -630,6 +669,42 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -639,12 +714,37 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -1002,6 +1102,16 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if 1.0.0", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.15.0" @@ -1110,12 +1220,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1429,6 +1559,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + [[package]] name = "ordered-stream" version = "0.2.0" @@ -1508,6 +1644,34 @@ dependencies = [ "futures-io", ] +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "polling" version = "2.8.0" @@ -1612,6 +1776,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.7" @@ -1679,6 +1863,7 @@ dependencies = [ "bon", "bytes", "clap", + "criterion", "ctrlc", "fuse3", "futures-util", @@ -1806,6 +1991,21 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1880,6 +2080,18 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -2126,6 +2338,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tokio" version = "1.40.0" @@ -2341,6 +2563,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2402,6 +2634,16 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "which" version = "6.0.3" @@ -2430,6 +2672,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 792f568b..02979562 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,10 +57,15 @@ thread_local = "1.1.8" subtle = "2.6.1" bon = "2.2.0" shush-rs = "0.1.10" +criterion = { version = "0.5.1", features = ["html_reports"] } [target.'cfg(target_os = "linux")'.dependencies] fuse3 = { version = "0.7.2", features = ["tokio-runtime", "unprivileged"] } +[[bench]] +name = "crypto_read" +harness = false + [profile.release] #panic = "abort" # Treat warnings as errors in release builds diff --git a/benches/crypto_read.rs b/benches/crypto_read.rs new file mode 100644 index 00000000..a9ad32c0 --- /dev/null +++ b/benches/crypto_read.rs @@ -0,0 +1,145 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand_core::RngCore; +use rencfs::crypto; +use rencfs::crypto::write::CryptoWrite; +use rencfs::crypto::Cipher; +use shush_rs::SecretVec; +use std::io; +use std::io::Seek; + +fn bench_read_1mb_chacha_file(c: &mut Criterion) { + let cipher = Cipher::ChaCha20Poly1305; + let len = 1024 * 1024; + + let mut key: Vec = vec![0; cipher.key_len()]; + rand::thread_rng().fill_bytes(&mut key); + let key = SecretVec::new(Box::new(key)); + + let file = tempfile::tempfile().unwrap(); + let mut writer = crypto::create_write(file, cipher, &key); + let mut cursor_random = io::Cursor::new(vec![0; len]); + rand::thread_rng().fill_bytes(cursor_random.get_mut()); + cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); + io::copy(&mut cursor_random, &mut writer).unwrap(); + let file = writer.finish().unwrap(); + + c.bench_function("bench_read_1mb_chacha_file", |b| { + b.iter(|| { + let mut file = file.try_clone().unwrap(); + file.seek(io::SeekFrom::Start(0)).unwrap(); + let mut reader = crypto::create_read(file, cipher, &key); + black_box(io::copy(&mut reader, &mut io::sink()).unwrap()) + }) + }); +} + +// #[bench] +// fn bench_read_1mb_aes_file(b: &mut Bencher) { +// use crate::crypto; +// use crate::crypto::write::CryptoWrite; +// use crate::crypto::Cipher; +// use rand::RngCore; +// use shush_rs::SecretVec; +// use std::io; +// use std::io::Seek; +// use test::black_box; +// +// let cipher = Cipher::Aes256Gcm; +// let len = 1024 * 1024; +// +// let mut key: Vec = vec![0; cipher.key_len()]; +// rand::thread_rng().fill_bytes(&mut key); +// let key = SecretVec::new(Box::new(key)); +// +// let file = tempfile::tempfile().unwrap(); +// let mut writer = crypto::create_write(file, cipher, &key); +// let mut cursor_random = io::Cursor::new(vec![0; len]); +// rand::thread_rng().fill_bytes(cursor_random.get_mut()); +// cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); +// io::copy(&mut cursor_random, &mut writer).unwrap(); +// let file = writer.finish().unwrap(); +// +// b.iter(|| { +// black_box({ +// let mut file = file.try_clone().unwrap(); +// file.seek(io::SeekFrom::Start(0)).unwrap(); +// let mut reader = crypto::create_read(file, cipher, &key); +// io::copy(&mut reader, &mut io::sink()).unwrap() +// }); +// }); +// } +// +// #[bench] +// fn bench_read_1mb_chacha_ram(b: &mut Bencher) { +// use crate::crypto; +// use crate::crypto::write::CryptoWrite; +// use crate::crypto::Cipher; +// use rand::RngCore; +// use shush_rs::SecretVec; +// use std::io; +// use std::io::Seek; +// use test::black_box; +// +// let cipher = Cipher::ChaCha20Poly1305; +// let len = 1024 * 1024; +// +// let mut key: Vec = vec![0; cipher.key_len()]; +// rand::thread_rng().fill_bytes(&mut key); +// let key = SecretVec::new(Box::new(key)); +// +// let cursor_write = io::Cursor::new(vec![]); +// let mut writer = crypto::create_write(cursor_write, cipher, &key); +// let mut cursor_random = io::Cursor::new(vec![0; len]); +// rand::thread_rng().fill_bytes(cursor_random.get_mut()); +// cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); +// io::copy(&mut cursor_random, &mut writer).unwrap(); +// let cursor_write = writer.finish().unwrap(); +// +// b.iter(|| { +// black_box({ +// let mut cursor = cursor_write.clone(); +// cursor.seek(io::SeekFrom::Start(0)).unwrap(); +// let mut reader = crypto::create_read(cursor, cipher, &key); +// io::copy(&mut reader, &mut io::sink()).unwrap() +// }); +// }); +// } +// +// #[bench] +// fn bench_read_1mb_aes_ram(b: &mut Bencher) { +// use crate::crypto; +// use crate::crypto::write::CryptoWrite; +// use crate::crypto::Cipher; +// use rand::RngCore; +// use shush_rs::SecretVec; +// use std::io; +// use std::io::Seek; +// use test::black_box; +// +// let cipher = Cipher::Aes256Gcm; +// let len = 1024 * 1024; +// +// let mut key: Vec = vec![0; cipher.key_len()]; +// rand::thread_rng().fill_bytes(&mut key); +// let key = SecretVec::new(Box::new(key)); +// +// let cursor_write = io::Cursor::new(vec![]); +// let mut writer = crypto::create_write(cursor_write, cipher, &key); +// let mut cursor_random = io::Cursor::new(vec![0; len]); +// rand::thread_rng().fill_bytes(cursor_random.get_mut()); +// cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); +// io::copy(&mut cursor_random, &mut writer).unwrap(); +// let cursor_write = writer.finish().unwrap(); +// +// b.iter(|| { +// black_box({ +// let mut cursor = cursor_write.clone(); +// cursor.seek(io::SeekFrom::Start(0)).unwrap(); +// let mut reader = crypto::create_read(cursor, cipher, &key); +// io::copy(&mut reader, &mut io::sink()).unwrap() +// }); +// }); +// } + +criterion_group!(benches, bench_read_1mb_chacha_file); +criterion_main!(benches); diff --git a/java-bridge/Cargo.lock b/java-bridge/Cargo.lock index 5327da0a..f180fc06 100644 --- a/java-bridge/Cargo.lock +++ b/java-bridge/Cargo.lock @@ -55,6 +55,12 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.15" @@ -495,6 +501,12 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cbc" version = "0.1.2" @@ -534,6 +546,33 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -649,6 +688,42 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -658,12 +733,37 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -1026,6 +1126,16 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if 1.0.0", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1133,12 +1243,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1476,6 +1606,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + [[package]] name = "ordered-stream" version = "0.2.0" @@ -1555,6 +1691,34 @@ dependencies = [ "futures-io", ] +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "polling" version = "2.8.0" @@ -1659,6 +1823,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.3" @@ -1726,6 +1910,7 @@ dependencies = [ "bon", "bytes", "clap", + "criterion", "ctrlc", "fuse3", "futures-util", @@ -1853,6 +2038,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "same-file" version = "1.0.6" @@ -1936,6 +2127,18 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -2176,6 +2379,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tokio" version = "1.39.2" @@ -2461,6 +2674,16 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "which" version = "6.0.2" diff --git a/src/crypto/read.rs b/src/crypto/read.rs index 91ad1e35..74368154 100644 --- a/src/crypto/read.rs +++ b/src/crypto/read.rs @@ -13,7 +13,6 @@ use crate::crypto::buf_mut::BufMut; use crate::crypto::write::BLOCK_SIZE; use crate::stream_util; -mod bench; mod test; /// Reads encrypted content from the wrapped Reader. diff --git a/src/crypto/read/bench.rs b/src/crypto/read/bench.rs deleted file mode 100644 index 91b46a30..00000000 --- a/src/crypto/read/bench.rs +++ /dev/null @@ -1,146 +0,0 @@ -#[allow(unused_imports)] -use test::Bencher; - -#[bench] -fn bench_read_1mb_chacha_file(b: &mut Bencher) { - use crate::crypto; - use crate::crypto::write::CryptoWrite; - use crate::crypto::Cipher; - use rand::RngCore; - use shush_rs::SecretVec; - use std::io; - use std::io::Seek; - use test::black_box; - - let cipher = Cipher::ChaCha20Poly1305; - let len = 1024 * 1024; - - let mut key: Vec = vec![0; cipher.key_len()]; - rand::thread_rng().fill_bytes(&mut key); - let key = SecretVec::new(Box::new(key)); - - let file = tempfile::tempfile().unwrap(); - let mut writer = crypto::create_write(file, cipher, &key); - let mut cursor_random = io::Cursor::new(vec![0; len]); - rand::thread_rng().fill_bytes(cursor_random.get_mut()); - cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); - io::copy(&mut cursor_random, &mut writer).unwrap(); - let file = writer.finish().unwrap(); - - b.iter(|| { - black_box({ - let mut file = file.try_clone().unwrap(); - file.seek(io::SeekFrom::Start(0)).unwrap(); - let mut reader = crypto::create_read(file, cipher, &key); - io::copy(&mut reader, &mut io::sink()).unwrap() - }); - }); -} - -#[bench] -fn bench_read_1mb_aes_file(b: &mut Bencher) { - use crate::crypto; - use crate::crypto::write::CryptoWrite; - use crate::crypto::Cipher; - use rand::RngCore; - use shush_rs::SecretVec; - use std::io; - use std::io::Seek; - use test::black_box; - - let cipher = Cipher::Aes256Gcm; - let len = 1024 * 1024; - - let mut key: Vec = vec![0; cipher.key_len()]; - rand::thread_rng().fill_bytes(&mut key); - let key = SecretVec::new(Box::new(key)); - - let file = tempfile::tempfile().unwrap(); - let mut writer = crypto::create_write(file, cipher, &key); - let mut cursor_random = io::Cursor::new(vec![0; len]); - rand::thread_rng().fill_bytes(cursor_random.get_mut()); - cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); - io::copy(&mut cursor_random, &mut writer).unwrap(); - let file = writer.finish().unwrap(); - - b.iter(|| { - black_box({ - let mut file = file.try_clone().unwrap(); - file.seek(io::SeekFrom::Start(0)).unwrap(); - let mut reader = crypto::create_read(file, cipher, &key); - io::copy(&mut reader, &mut io::sink()).unwrap() - }); - }); -} - -#[bench] -fn bench_read_1mb_chacha_ram(b: &mut Bencher) { - use crate::crypto; - use crate::crypto::write::CryptoWrite; - use crate::crypto::Cipher; - use rand::RngCore; - use shush_rs::SecretVec; - use std::io; - use std::io::Seek; - use test::black_box; - - let cipher = Cipher::ChaCha20Poly1305; - let len = 1024 * 1024; - - let mut key: Vec = vec![0; cipher.key_len()]; - rand::thread_rng().fill_bytes(&mut key); - let key = SecretVec::new(Box::new(key)); - - let cursor_write = io::Cursor::new(vec![]); - let mut writer = crypto::create_write(cursor_write, cipher, &key); - let mut cursor_random = io::Cursor::new(vec![0; len]); - rand::thread_rng().fill_bytes(cursor_random.get_mut()); - cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); - io::copy(&mut cursor_random, &mut writer).unwrap(); - let cursor_write = writer.finish().unwrap(); - - b.iter(|| { - black_box({ - let mut cursor = cursor_write.clone(); - cursor.seek(io::SeekFrom::Start(0)).unwrap(); - let mut reader = crypto::create_read(cursor, cipher, &key); - io::copy(&mut reader, &mut io::sink()).unwrap() - }); - }); -} - -#[bench] -fn bench_read_1mb_aes_ram(b: &mut Bencher) { - use crate::crypto; - use crate::crypto::write::CryptoWrite; - use crate::crypto::Cipher; - use rand::RngCore; - use shush_rs::SecretVec; - use std::io; - use std::io::Seek; - use test::black_box; - - let cipher = Cipher::Aes256Gcm; - let len = 1024 * 1024; - - let mut key: Vec = vec![0; cipher.key_len()]; - rand::thread_rng().fill_bytes(&mut key); - let key = SecretVec::new(Box::new(key)); - - let cursor_write = io::Cursor::new(vec![]); - let mut writer = crypto::create_write(cursor_write, cipher, &key); - let mut cursor_random = io::Cursor::new(vec![0; len]); - rand::thread_rng().fill_bytes(cursor_random.get_mut()); - cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); - io::copy(&mut cursor_random, &mut writer).unwrap(); - let cursor_write = writer.finish().unwrap(); - - b.iter(|| { - black_box({ - let mut cursor = cursor_write.clone(); - cursor.seek(io::SeekFrom::Start(0)).unwrap(); - let mut reader = crypto::create_read(cursor, cipher, &key); - io::copy(&mut reader, &mut io::sink()).unwrap() - }); - }); -} From d9b56c756670b54fdbcb9fab805ea9f44873b366 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 22 Nov 2024 18:37:34 +0200 Subject: [PATCH 013/150] migrate one test to criterion --- benches/crypto_read.rs | 59 ++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/benches/crypto_read.rs b/benches/crypto_read.rs index a9ad32c0..2b654bd7 100644 --- a/benches/crypto_read.rs +++ b/benches/crypto_read.rs @@ -33,42 +33,27 @@ fn bench_read_1mb_chacha_file(c: &mut Criterion) { }); } -// #[bench] -// fn bench_read_1mb_aes_file(b: &mut Bencher) { -// use crate::crypto; -// use crate::crypto::write::CryptoWrite; -// use crate::crypto::Cipher; -// use rand::RngCore; -// use shush_rs::SecretVec; -// use std::io; -// use std::io::Seek; -// use test::black_box; -// -// let cipher = Cipher::Aes256Gcm; -// let len = 1024 * 1024; -// -// let mut key: Vec = vec![0; cipher.key_len()]; -// rand::thread_rng().fill_bytes(&mut key); -// let key = SecretVec::new(Box::new(key)); -// -// let file = tempfile::tempfile().unwrap(); -// let mut writer = crypto::create_write(file, cipher, &key); -// let mut cursor_random = io::Cursor::new(vec![0; len]); -// rand::thread_rng().fill_bytes(cursor_random.get_mut()); -// cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); -// io::copy(&mut cursor_random, &mut writer).unwrap(); -// let file = writer.finish().unwrap(); -// -// b.iter(|| { -// black_box({ -// let mut file = file.try_clone().unwrap(); -// file.seek(io::SeekFrom::Start(0)).unwrap(); -// let mut reader = crypto::create_read(file, cipher, &key); -// io::copy(&mut reader, &mut io::sink()).unwrap() -// }); -// }); -// } -// +fn bench_read_1mb_aes_file(c: &mut Criterion) { + let cipher = Cipher::Aes256Gcm; + let len = 1024 * 1024; + + let mut key: Vec = vec![0; cipher.key_len()]; + rand::thread_rng().fill_bytes(&mut key); + let key = SecretVec::new(Box::new(key)); + + c.bench_function("bench_read_1mb_chacha_file", |b| { + b.iter(|| { + let file = tempfile::tempfile().unwrap(); + let mut writer = crypto::create_write(file, cipher, &key); + let mut cursor_random = io::Cursor::new(vec![0; len]); + rand::thread_rng().fill_bytes(cursor_random.get_mut()); + cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); + io::copy(&mut cursor_random, &mut writer).unwrap(); + black_box(writer.finish().unwrap()) + }) + }); +} + // #[bench] // fn bench_read_1mb_chacha_ram(b: &mut Bencher) { // use crate::crypto; @@ -141,5 +126,5 @@ fn bench_read_1mb_chacha_file(c: &mut Criterion) { // }); // } -criterion_group!(benches, bench_read_1mb_chacha_file); +criterion_group!(benches, bench_read_1mb_chacha_file, gs); criterion_main!(benches); From 999035675aedc7dbdccdfa67e95b8db6c0334769 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 22 Nov 2024 18:47:36 +0200 Subject: [PATCH 014/150] migrate one test to criterion --- .idea/rencfs.iml | 1 + benches/crypto_read.rs | 134 ++++++++++++++++++----------------------- 2 files changed, 61 insertions(+), 74 deletions(-) diff --git a/.idea/rencfs.iml b/.idea/rencfs.iml index cfd3cf35..0ecc648e 100644 --- a/.idea/rencfs.iml +++ b/.idea/rencfs.iml @@ -8,6 +8,7 @@ + diff --git a/benches/crypto_read.rs b/benches/crypto_read.rs index 2b654bd7..c04e9d07 100644 --- a/benches/crypto_read.rs +++ b/benches/crypto_read.rs @@ -41,7 +41,7 @@ fn bench_read_1mb_aes_file(c: &mut Criterion) { rand::thread_rng().fill_bytes(&mut key); let key = SecretVec::new(Box::new(key)); - c.bench_function("bench_read_1mb_chacha_file", |b| { + c.bench_function("bench_read_1mb_aes_file", |b| { b.iter(|| { let file = tempfile::tempfile().unwrap(); let mut writer = crypto::create_write(file, cipher, &key); @@ -54,77 +54,63 @@ fn bench_read_1mb_aes_file(c: &mut Criterion) { }); } -// #[bench] -// fn bench_read_1mb_chacha_ram(b: &mut Bencher) { -// use crate::crypto; -// use crate::crypto::write::CryptoWrite; -// use crate::crypto::Cipher; -// use rand::RngCore; -// use shush_rs::SecretVec; -// use std::io; -// use std::io::Seek; -// use test::black_box; -// -// let cipher = Cipher::ChaCha20Poly1305; -// let len = 1024 * 1024; -// -// let mut key: Vec = vec![0; cipher.key_len()]; -// rand::thread_rng().fill_bytes(&mut key); -// let key = SecretVec::new(Box::new(key)); -// -// let cursor_write = io::Cursor::new(vec![]); -// let mut writer = crypto::create_write(cursor_write, cipher, &key); -// let mut cursor_random = io::Cursor::new(vec![0; len]); -// rand::thread_rng().fill_bytes(cursor_random.get_mut()); -// cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); -// io::copy(&mut cursor_random, &mut writer).unwrap(); -// let cursor_write = writer.finish().unwrap(); -// -// b.iter(|| { -// black_box({ -// let mut cursor = cursor_write.clone(); -// cursor.seek(io::SeekFrom::Start(0)).unwrap(); -// let mut reader = crypto::create_read(cursor, cipher, &key); -// io::copy(&mut reader, &mut io::sink()).unwrap() -// }); -// }); -// } -// -// #[bench] -// fn bench_read_1mb_aes_ram(b: &mut Bencher) { -// use crate::crypto; -// use crate::crypto::write::CryptoWrite; -// use crate::crypto::Cipher; -// use rand::RngCore; -// use shush_rs::SecretVec; -// use std::io; -// use std::io::Seek; -// use test::black_box; -// -// let cipher = Cipher::Aes256Gcm; -// let len = 1024 * 1024; -// -// let mut key: Vec = vec![0; cipher.key_len()]; -// rand::thread_rng().fill_bytes(&mut key); -// let key = SecretVec::new(Box::new(key)); -// -// let cursor_write = io::Cursor::new(vec![]); -// let mut writer = crypto::create_write(cursor_write, cipher, &key); -// let mut cursor_random = io::Cursor::new(vec![0; len]); -// rand::thread_rng().fill_bytes(cursor_random.get_mut()); -// cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); -// io::copy(&mut cursor_random, &mut writer).unwrap(); -// let cursor_write = writer.finish().unwrap(); -// -// b.iter(|| { -// black_box({ -// let mut cursor = cursor_write.clone(); -// cursor.seek(io::SeekFrom::Start(0)).unwrap(); -// let mut reader = crypto::create_read(cursor, cipher, &key); -// io::copy(&mut reader, &mut io::sink()).unwrap() -// }); -// }); -// } - -criterion_group!(benches, bench_read_1mb_chacha_file, gs); +fn bench_read_1mb_chacha_ram(c: &mut Criterion) { + let cipher = Cipher::ChaCha20Poly1305; + let len = 1024 * 1024; + + let mut key: Vec = vec![0; cipher.key_len()]; + rand::thread_rng().fill_bytes(&mut key); + let key = SecretVec::new(Box::new(key)); + + let cursor_write = io::Cursor::new(vec![]); + let mut writer = crypto::create_write(cursor_write, cipher, &key); + let mut cursor_random = io::Cursor::new(vec![0; len]); + rand::thread_rng().fill_bytes(cursor_random.get_mut()); + cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); + io::copy(&mut cursor_random, &mut writer).unwrap(); + let cursor_write = writer.finish().unwrap(); + + c.bench_function("bench_read_1mb_chacha_ram", |b| { + b.iter(|| { + let mut cursor = cursor_write.clone(); + cursor.seek(io::SeekFrom::Start(0)).unwrap(); + let mut reader = crypto::create_read(cursor, cipher, &key); + black_box(io::copy(&mut reader, &mut io::sink()).unwrap()) + }) + }); +} + +fn bench_read_1mb_aes_ram(c: &mut Criterion) { + let cipher = Cipher::Aes256Gcm; + let len = 1024 * 1024; + + let mut key: Vec = vec![0; cipher.key_len()]; + rand::thread_rng().fill_bytes(&mut key); + let key = SecretVec::new(Box::new(key)); + + let cursor_write = io::Cursor::new(vec![]); + let mut writer = crypto::create_write(cursor_write, cipher, &key); + let mut cursor_random = io::Cursor::new(vec![0; len]); + rand::thread_rng().fill_bytes(cursor_random.get_mut()); + cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); + io::copy(&mut cursor_random, &mut writer).unwrap(); + let cursor_write = writer.finish().unwrap(); + + c.bench_function("bench_read_1mb_aes_file", |b| { + b.iter(|| { + let mut cursor = cursor_write.clone(); + cursor.seek(io::SeekFrom::Start(0)).unwrap(); + let mut reader = crypto::create_read(cursor, cipher, &key); + black_box(io::copy(&mut reader, &mut io::sink()).unwrap()) + }) + }); +} + +criterion_group!( + benches, + bench_read_1mb_chacha_file, + bench_read_1mb_aes_file, + bench_read_1mb_chacha_ram, + bench_read_1mb_aes_ram +); criterion_main!(benches); From 9dcc0264ae33e4f39d995effd34d9218c523aec5 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 22 Nov 2024 20:21:18 +0200 Subject: [PATCH 015/150] migrate one test to criterion --- benches/crypto_read.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/benches/crypto_read.rs b/benches/crypto_read.rs index c04e9d07..885178c8 100644 --- a/benches/crypto_read.rs +++ b/benches/crypto_read.rs @@ -28,7 +28,8 @@ fn bench_read_1mb_chacha_file(c: &mut Criterion) { let mut file = file.try_clone().unwrap(); file.seek(io::SeekFrom::Start(0)).unwrap(); let mut reader = crypto::create_read(file, cipher, &key); - black_box(io::copy(&mut reader, &mut io::sink()).unwrap()) + black_box(&reader); + io::copy(&mut reader, &mut io::sink()).unwrap(); }) }); } @@ -49,7 +50,8 @@ fn bench_read_1mb_aes_file(c: &mut Criterion) { rand::thread_rng().fill_bytes(cursor_random.get_mut()); cursor_random.seek(io::SeekFrom::Start(0)).unwrap(); io::copy(&mut cursor_random, &mut writer).unwrap(); - black_box(writer.finish().unwrap()) + black_box(&writer); + writer.finish().unwrap(); }) }); } @@ -75,7 +77,8 @@ fn bench_read_1mb_chacha_ram(c: &mut Criterion) { let mut cursor = cursor_write.clone(); cursor.seek(io::SeekFrom::Start(0)).unwrap(); let mut reader = crypto::create_read(cursor, cipher, &key); - black_box(io::copy(&mut reader, &mut io::sink()).unwrap()) + black_box(&reader); + io::copy(&mut reader, &mut io::sink()).unwrap(); }) }); } @@ -101,7 +104,8 @@ fn bench_read_1mb_aes_ram(c: &mut Criterion) { let mut cursor = cursor_write.clone(); cursor.seek(io::SeekFrom::Start(0)).unwrap(); let mut reader = crypto::create_read(cursor, cipher, &key); - black_box(io::copy(&mut reader, &mut io::sink()).unwrap()) + black_box(&reader); + io::copy(&mut reader, &mut io::sink()).unwrap(); }) }); } From 148e753eddf6c378535bb122946eb73177ee15a7 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Mon, 25 Nov 2024 16:37:16 +0200 Subject: [PATCH 016/150] move git commit hook to push --- CONTRIBUTING.md | 26 ++++++++++++++----- .../linux-macos/pre-push | 0 .../pre-commit => git-hooks/windows/pre-push | 0 3 files changed, 19 insertions(+), 7 deletions(-) rename hooks/linux-macos/pre-commit => git-hooks/linux-macos/pre-push (100%) rename hooks/windows/pre-commit => git-hooks/windows/pre-push (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7fd31788..4337ed78 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,18 @@ ## How to contribute -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache License, shall be dual-licensed as above, without any additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as +defined in the Apache License, shall be dual-licensed as above, without any additional terms or conditions. 1. Join [slack](https://bit.ly/3UU1oXi) and join `#dev-beginners` channel 2. Become familiar with docs and code by reading the [ramp-up](Ramp-up.md) guide -3. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and be able to create your own branches and not needing to fork the repo -4. Pick an open issue or a task in the corresponding [project](https://github.com/users/radumarias/projects/1) for the repo that you'll be working on. You can see [good for first issues](https://github.com/radumarias/rencfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) that you can pick from -5. **Assign the issues you are working on to you and move them to the corresponding status column as you are progressing on them. If the taks is not an issue yet, convert it to issue first** +3. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and + be able to create your own branches and not needing to fork the repo +4. Pick an open issue or a task in the corresponding [project](https://github.com/users/radumarias/projects/1) for the + repo that you'll be working on. You can + see [good for first issues](https://github.com/radumarias/rencfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) + that you can pick from +5. **Assign the issues you are working on to you and move them to the corresponding status column as you are progressing + on them. If the taks is not an issue yet, convert it to issue first** 6. Make the changes in your branch 7. Add docs as they apply 8. Add tests, benchmarks and examples for your changes, if applicable @@ -15,9 +21,15 @@ Unless you explicitly state otherwise, any contribution intentionally submitted and [VSCode](https://code.visualstudio.com/docs/languages/rust#_formatting) 10. `cargo clippy --all --release` and fix any errors 11. **DON'T INCREASE THE VERSION NUMBER IN `Cargo.toml`, WE WILL DO THAN WHEN RELEASING** -12. Create a `git` `commit hook` file in `.git/hooks/pre-commit` with [this](hooks/linux-macos/pre-commit) content on `Linux` and `MacOS`, and [this](hooks/windows/pre-commit) on `Windows`. Make it executable in Linux and macOS with `chmod +x .git/hooks/pre-commit` .This will run when you do `git commit` and will make the commit to be quite slow, but please give it time to complete as this helps to fix any issues locally and not relying just on running `ci` on GitHub when you create the PR -13. Commit your changes and if there are any errors fix them before you push them -14. Push your changes and create a `PR` back to the `parent` repo targeting the `main` branch and request review from owners of the repository, by adding them to the `Reviewers` field +12. Create a `git` `push hook` file in `.git/hooks/pre-push` with [this](git-hooks/linux-macos/pre-push) content + on `Linux` and `MacOS`, and [this](git-hooks/windows/pre-push) on `Windows`. + Make it executable in Linux and macOS + with `chmod +x .git/hooks/pre-push` .This will run when you do `git push` and will make the push to be quite + slow, but please give it time to complete as this helps to fix any issues locally and not relying just on + running `ci` on GitHub when you create the PR +13. Commit and push your changes and if there are any errors, fix them before you push them +14. Create a `PR` back to the `parent` repo targeting the `main` branch and request review from + owners of the repository, by adding them to the `Reviewers` field 15. Monitor the checks (GitHub actions runs) and fix the code if they are failing 16. Respond to any comments 17. **DON'T MERGE THE PR YOURSELF, LEAVE THAT TO REPOSITORY OWNERS** diff --git a/hooks/linux-macos/pre-commit b/git-hooks/linux-macos/pre-push similarity index 100% rename from hooks/linux-macos/pre-commit rename to git-hooks/linux-macos/pre-push diff --git a/hooks/windows/pre-commit b/git-hooks/windows/pre-push similarity index 100% rename from hooks/windows/pre-commit rename to git-hooks/windows/pre-push From 5a695f93447542fa6744b4db3543d6ea32595465 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 09:00:29 +0200 Subject: [PATCH 017/150] Update README.md (#244) --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 645046e1..4bfd5dc9 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,7 @@ [![release](https://github.com/radumarias/rencfs/actions/workflows/release.yaml/badge.svg)](https://github.com/radumarias/rencfs/actions/workflows/release.yaml) [![codecov](https://codecov.io/gh/radumarias/rencfs/graph/badge.svg?token=NUQI6XGF2Y)](https://codecov.io/gh/radumarias/rencfs) -[![Matrix](https://img.shields.io/matrix/rencfs%3Amatrix.org?label=Matrix)](https://matrix.to/#/#rencfs:matrix.org) -[![Discord](https://img.shields.io/discord/1236855443486277653?label=Discord)](https://discord.com/channels/1236855443486277653/1236855448515252306) -[![Zulip](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?label=Zulip)](https://rencfs.zulipchat.com) + [![Open Source Helpers](https://www.codetriage.com/radumarias/rencfs/badges/users.svg)](https://www.codetriage.com/radumarias/rencfs) > [!WARNING] From 9808be58b3b136e708a2e50fc0a1fc93bda34236 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 09:00:59 +0200 Subject: [PATCH 018/150] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bfd5dc9..8b9c14e5 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ [![release](https://github.com/radumarias/rencfs/actions/workflows/release.yaml/badge.svg)](https://github.com/radumarias/rencfs/actions/workflows/release.yaml) [![codecov](https://codecov.io/gh/radumarias/rencfs/graph/badge.svg?token=NUQI6XGF2Y)](https://codecov.io/gh/radumarias/rencfs) - [![Open Source Helpers](https://www.codetriage.com/radumarias/rencfs/badges/users.svg)](https://www.codetriage.com/radumarias/rencfs) + > [!WARNING] > **This crate hasn't been audited; it's using `ring` crate, which is a well-known audited library, so in principle, at From 657612d191fd270b58410eafa48dc27054251c5e Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 09:29:30 +0200 Subject: [PATCH 019/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4337ed78..17c8cf05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,21 +1,24 @@ ## How to contribute -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as -defined in the Apache License, shall be dual-licensed as above, without any additional terms or conditions. +> [!IMPORTANT] +> **These steps are more particular to this repo but mainly apply to all repositories; change the specifics.** + +Unless you explicitly state otherwise, any Contribution intentionally submitted for inclusion in this project by you, as +defined in the Apache License shall be dual-licensed as above, without any additional terms or conditions. 1. Join [slack](https://bit.ly/3UU1oXi) and join `#dev-beginners` channel 2. Become familiar with docs and code by reading the [ramp-up](Ramp-up.md) guide 3. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and - be able to create your own branches and not needing to fork the repo + be able to create your own branches and not need to fork the repo 4. Pick an open issue or a task in the corresponding [project](https://github.com/users/radumarias/projects/1) for the repo that you'll be working on. You can see [good for first issues](https://github.com/radumarias/rencfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) that you can pick from 5. **Assign the issues you are working on to you and move them to the corresponding status column as you are progressing - on them. If the taks is not an issue yet, convert it to issue first** + on them. If the task is not an issue yet, convert it to issue first** 6. Make the changes in your branch 7. Add docs as they apply -8. Add tests, benchmarks and examples for your changes, if applicable +8. Add tests, benchmarks, and examples for your changes, if applicable 9. `cargo fmt --all` to format the code. You can configure your `IDE` to do this on save, [RustRover](https://www.jetbrains.com/help/rust/rustfmt.html) and [VSCode](https://code.visualstudio.com/docs/languages/rust#_formatting) @@ -24,13 +27,13 @@ defined in the Apache License, shall be dual-licensed as above, without any addi 12. Create a `git` `push hook` file in `.git/hooks/pre-push` with [this](git-hooks/linux-macos/pre-push) content on `Linux` and `MacOS`, and [this](git-hooks/windows/pre-push) on `Windows`. Make it executable in Linux and macOS - with `chmod +x .git/hooks/pre-push` .This will run when you do `git push` and will make the push to be quite - slow, but please give it time to complete as this helps to fix any issues locally and not relying just on + with `chmod +x .git/hooks/pre-push` .This will run when you do `git push` and will make the push quite + slow, but please give it time to complete as this helps to fix any issues locally and not rely just on running `ci` on GitHub when you create the PR -13. Commit and push your changes and if there are any errors, fix them before you push them +13. Commit and push your changes, and if there are any errors, fix them before you push them 14. Create a `PR` back to the `parent` repo targeting the `main` branch and request review from - owners of the repository, by adding them to the `Reviewers` field + owners of the repository by adding them to the `Reviewers` field 15. Monitor the checks (GitHub actions runs) and fix the code if they are failing 16. Respond to any comments -17. **DON'T MERGE THE PR YOURSELF, LEAVE THAT TO REPOSITORY OWNERS** -18. In the end, ideally, it will be merged to `main` +17. **DON'T MERGE THE PR YOURSELF. LEAVE THAT TO REPOSITORY OWNERS** +18. In the end, ideally, it will be merged into `main` From 5a15e82b138269334a95850f25c42edbdefb8b63 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 13:50:02 +0200 Subject: [PATCH 020/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17c8cf05..9b4cc34c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ ## How to contribute > [!IMPORTANT] -> **These steps are more particular to this repo but mainly apply to all repositories; change the specifics.** +> **These steps are a bit particular to this repo but you can mainly apply them to all repositories by changing the specifics.** Unless you explicitly state otherwise, any Contribution intentionally submitted for inclusion in this project by you, as defined in the Apache License shall be dual-licensed as above, without any additional terms or conditions. @@ -14,8 +14,8 @@ defined in the Apache License shall be dual-licensed as above, without any addit repo that you'll be working on. You can see [good for first issues](https://github.com/radumarias/rencfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) that you can pick from -5. **Assign the issues you are working on to you and move them to the corresponding status column as you are progressing - on them. If the task is not an issue yet, convert it to issue first** +5. **Assign the issues you are working on to you and move them to the corresponding status column as you progress + on them. If the task is not an issue yet, convert it to an issue first** 6. Make the changes in your branch 7. Add docs as they apply 8. Add tests, benchmarks, and examples for your changes, if applicable @@ -24,12 +24,13 @@ defined in the Apache License shall be dual-licensed as above, without any addit and [VSCode](https://code.visualstudio.com/docs/languages/rust#_formatting) 10. `cargo clippy --all --release` and fix any errors 11. **DON'T INCREASE THE VERSION NUMBER IN `Cargo.toml`, WE WILL DO THAN WHEN RELEASING** -12. Create a `git` `push hook` file in `.git/hooks/pre-push` with [this](git-hooks/linux-macos/pre-push) content - on `Linux` and `MacOS`, and [this](git-hooks/windows/pre-push) on `Windows`. +12. Create a `git` `push hook` file in `.git/hooks/pre-push` with [pre-push](scripts/git-hooks/linux-macos/pre-push) content + on `Linux` and `macOS`, and [pre-push](scripts/git-hooks/windows/pre-push) on `Windows`. Make it executable in Linux and macOS with `chmod +x .git/hooks/pre-push` .This will run when you do `git push` and will make the push quite slow, but please give it time to complete as this helps to fix any issues locally and not rely just on - running `ci` on GitHub when you create the PR + running `ci` on GitHub when you create the PR. + If you don't want to create this git hook then make sure before you push you run `check-before-push.sh` on Linux and `macOS` or `check-before-push.bat` on `Windows` 13. Commit and push your changes, and if there are any errors, fix them before you push them 14. Create a `PR` back to the `parent` repo targeting the `main` branch and request review from owners of the repository by adding them to the `Reviewers` field From c636e1599dc10789040ddd854188dd5c0a799c5c Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 13:56:35 +0200 Subject: [PATCH 021/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b4cc34c..28d6685f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Unless you explicitly state otherwise, any Contribution intentionally submitted for inclusion in this project by you, as defined in the Apache License shall be dual-licensed as above, without any additional terms or conditions. -1. Join [slack](https://bit.ly/3UU1oXi) and join `#dev-beginners` channel +1. Join [slack](https://bit.ly/3UU1oXi) and join `#dev-beginners` channel and become familiar with the info in there 2. Become familiar with docs and code by reading the [ramp-up](Ramp-up.md) guide 3. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and be able to create your own branches and not need to fork the repo From ded15280575ca2cbe842dc2db0e31503207a3ef5 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 13:59:26 +0200 Subject: [PATCH 022/150] Update Ramp-up.md --- Ramp-up.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Ramp-up.md b/Ramp-up.md index abffbbbf..bab2a609 100644 --- a/Ramp-up.md +++ b/Ramp-up.md @@ -1,14 +1,14 @@ # Ramp-up guide 1. Read an [article](https://medium.com/system-weakness/hitchhikers-guide-to-building-a-distributed-filesystem-in-rust-the-very-beginning-2c02eb7313e7) and an [one pager](The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust-1.pdf) to get more details about the project -2. Become familiar with the [concepts and features](https://github.com/radumarias/rencfs) and [lib docs](https://docs.rs/rencfs/latest/rencfs) -3. Understand the [layers](https://github.com/radumarias/rencfs/blob/main/website/resources/layers.png) -4. Detailed [sequence flows](docs/flows.md) -5. [Talks](https://startech-rd.io/hitchhikers-guide-to/) -6. Another presentation about [cryptography and FUSE](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=919818831100) +2. Read [Basics of cryptography for building a filesystem in Rust](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=962517464374) and [Building an Encrypted Filesystem in Rust](https://miro.com/app/board/uXjVLa8i1h0=/?share_link_id=745134849333) +3. Become familiar with the [concepts and features](https://github.com/radumarias/rencfs) and [lib docs](https://docs.rs/rencfs/latest/rencfs) +4. Understand the [layers](https://github.com/radumarias/rencfs/blob/main/website/resources/layers.png) +5. Detailed [sequence flows](docs/flows.md) +6. [Talks](https://startech-rd.io/hitchhikers-guide-to/) 7. Give it a [quick try](https://github.com/radumarias/rencfs#give-it-a-quick-try-with-docker) with Docker 8. Or run it as [CLI](https://github.com/radumarias/rencfs?tab=readme-ov-file#command-line-tool) app -9. Clone or fork the repo. You can work in your branches there after you're added to the repo. No need to fork it if you don't want to +9. Clone or fork the repo. After being added to it, you can work in your branches in the original repo. No need to fork it if you don't want to 10. [Build](https://github.com/radumarias/rencfs?tab=readme-ov-file#build-from-source) from source and start it. If you don't have Linux, you can [develop inside a container](https://github.com/radumarias/rencfs?tab=readme-ov-file#developing-inside-a-container). This will start a new Linux container and remotely connecting the local IDE to the container, you can also connect with IDE's terminal to it and run the code. On Windows, you can use [WSL](https://harsimranmaan.medium.com/install-and-setup-rust-development-environment-on-wsl2-dccb4bf63700). As a last resort, you can [develop in browser](https://github.com/radumarias/rencfs/blob/main/README.md#browser). -11. Run and understand the [examples](examples). You can write some new ones to better understand the flow and code. If you do, please create a `PR` back to the parent repo targeting the `main` branch to include those for others too -12. Become familiar with some [tests](https://github.com/radumarias/rencfs/blob/main/src/encryptedfs/test.rs) and benchmarks. You can write some new ones to better understand the flow and code. If you do, please create a `PR` back to the parent repo targeting the `main` branch to include those for others too +11. Run and understand [examples](examples). You can write some new ones to understand the flow and code better. If you do, please create a `PR` back to the parent repo targeting the `main` branch to include those for others too +12. Become familiar with [tests](https://github.com/radumarias/rencfs/blob/main/src/encryptedfs/test.rs) (and in other files) and benchmarks. You can write some new ones to understand the flow and code better. If you do, please create a `PR` back to the parent repo targeting the `main` branch to include those for others too From ae749319fe425b7b57c1542927ae2837758da8b5 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 09:40:40 +0200 Subject: [PATCH 023/150] Add linter conf but commented out for now. --- src/expire_value.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/expire_value.rs b/src/expire_value.rs index 80669340..b64937bb 100644 --- a/src/expire_value.rs +++ b/src/expire_value.rs @@ -1,6 +1,5 @@ use std::error::Error; use std::marker::PhantomData; -use std::string::ToString; use std::sync::{Arc, Weak}; use std::time::Duration; @@ -63,7 +62,7 @@ impl< let value = self.provider.provide().await?; let v = Arc::new(value); self.cache - .insert(KEY.to_string(), v.clone(), self.duration) + .insert(KEY.to_owned(), v.clone(), self.duration) .await; let mut weak = self.weak.write().await; *weak = Some(Arc::downgrade(&v)); @@ -78,7 +77,7 @@ impl< return Some(v.clone()); } // try to take it from cache - if let Some(v) = self.cache.get(&KEY.to_string()).await { + if let Some(v) = self.cache.get(&KEY.to_owned()).await { return Some(v.clone()); } } @@ -114,7 +113,7 @@ mod tests { impl ValueProvider for TestProvider { async fn provide(&self) -> Result { self.called.fetch_add(1, Ordering::SeqCst); - Ok("test".to_string()) + Ok("test".to_owned()) } } @@ -149,6 +148,6 @@ mod tests { let _ = expire_value.get().await.unwrap(); // ensure provider was called again let called = called.clone(); - assert_eq!(called.load(Ordering::SeqCst), 3) + assert_eq!(called.load(Ordering::SeqCst), 3); } } From 9521760bf7dea53988e532d196900ec55215313c Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 09:41:53 +0200 Subject: [PATCH 024/150] Add linter conf but commented out for now. --- Cargo.toml | 160 ++++++++++++++++++++++++++++++ benches/crypto_read.rs | 8 +- clippy.toml | 58 +++++++++++ examples/magic_of_blanket_impl.rs | 4 +- src/crypto.rs | 18 ++-- src/crypto/write.rs | 4 +- src/encryptedfs.rs | 14 +-- src/encryptedfs/test.rs | 22 ++-- src/test_common.rs | 2 +- tests/linux_mount_setup/mod.rs | 12 ++- tests/rencfs_linux_itest.rs | 2 +- 11 files changed, 262 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 02979562..53b4fdb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,166 @@ fuse3 = { version = "0.7.2", features = ["tokio-runtime", "unprivileged"] } name = "crypto_read" harness = false +[lints.rust] +#unsafe_code = "deny" + +[lints.rustdoc] +#all = "warn" +#missing_crate_level_docs = "warn" + +# See also clippy.toml +[lints.clippy] +#as_ptr_cast_mut = "warn" +#await_holding_lock = "warn" +#bool_to_int_with_if = "warn" +#char_lit_as_u8 = "warn" +#checked_conversions = "warn" +#clear_with_drain = "warn" +#cloned_instead_of_copied = "warn" +#dbg_macro = "warn" +#debug_assert_with_mut_call = "warn" +#derive_partial_eq_without_eq = "warn" +#disallowed_macros = "warn" # See clippy.toml +#disallowed_methods = "warn" # See clippy.toml +#disallowed_names = "warn" # See clippy.toml +#disallowed_script_idents = "warn" # See clippy.toml +#disallowed_types = "warn" # See clippy.toml +#doc_link_with_quotes = "warn" +#doc_markdown = "warn" +#empty_enum = "warn" +#empty_enum_variants_with_brackets = "warn" +#enum_glob_use = "warn" +#equatable_if_let = "warn" +#exit = "warn" +#expl_impl_clone_on_copy = "warn" +#explicit_deref_methods = "warn" +#explicit_into_iter_loop = "warn" +#explicit_iter_loop = "warn" +#fallible_impl_from = "warn" +#filter_map_next = "warn" +#flat_map_option = "warn" +#float_cmp_const = "warn" +#fn_params_excessive_bools = "warn" +#fn_to_numeric_cast_any = "warn" +#from_iter_instead_of_collect = "warn" +#get_unwrap = "warn" +#if_let_mutex = "warn" +#implicit_clone = "warn" +#imprecise_flops = "warn" +#index_refutable_slice = "warn" +#inefficient_to_string = "warn" +#infinite_loop = "warn" +#into_iter_without_iter = "warn" +#invalid_upcast_comparisons = "warn" +#iter_filter_is_ok = "warn" +#iter_filter_is_some = "warn" +#iter_not_returning_iterator = "warn" +#iter_on_empty_collections = "warn" +#iter_on_single_items = "warn" +#iter_without_into_iter = "warn" +#large_digit_groups = "warn" +#large_include_file = "warn" +#large_stack_arrays = "warn" +#large_stack_frames = "warn" +#large_types_passed_by_value = "warn" +#let_unit_value = "warn" +#linkedlist = "warn" +#lossy_float_literal = "warn" +#macro_use_imports = "warn" +#manual_assert = "warn" +#manual_clamp = "warn" +#manual_instant_elapsed = "warn" +#manual_is_variant_and = "warn" +#manual_let_else = "warn" +#manual_ok_or = "warn" +#manual_string_new = "warn" +#map_err_ignore = "warn" +#map_flatten = "warn" +#match_on_vec_items = "warn" +#match_same_arms = "warn" +#match_wild_err_arm = "warn" +#match_wildcard_for_single_variants = "warn" +#mem_forget = "warn" +#mismatched_target_os = "warn" +#mismatching_type_param_order = "warn" +#missing_enforced_import_renames = "warn" +#missing_safety_doc = "warn" +#mixed_attributes_style = "warn" +#mut_mut = "warn" +#mutex_integer = "warn" +#needless_borrow = "warn" +#needless_continue = "warn" +#needless_for_each = "warn" +#needless_pass_by_ref_mut = "warn" +#needless_pass_by_value = "warn" +#negative_feature_names = "warn" +#nonstandard_macro_braces = "warn" +#option_as_ref_cloned = "warn" +#option_option = "warn" +#path_buf_push_overwrite = "warn" +#ptr_as_ptr = "warn" +#ptr_cast_constness = "warn" +#pub_underscore_fields = "warn" +#pub_without_shorthand = "warn" +#rc_mutex = "warn" +#readonly_write_lock = "warn" +#redundant_type_annotations = "warn" +#ref_as_ptr = "warn" +#ref_option_ref = "warn" +#rest_pat_in_fully_bound_structs = "warn" +#same_functions_in_if_condition = "warn" +#semicolon_if_nothing_returned = "warn" +#should_panic_without_expect = "warn" +#single_char_pattern = "warn" +#single_match_else = "warn" +#str_split_at_newline = "warn" +#str_to_string = "warn" +#string_add = "warn" +#string_add_assign = "warn" +#string_lit_as_bytes = "warn" +#string_lit_chars_any = "warn" +#string_to_string = "warn" +#suspicious_command_arg_space = "warn" +#suspicious_xor_used_as_pow = "warn" +#todo = "warn" +#too_many_lines = "warn" +#trailing_empty_array = "warn" +#trait_duplication_in_bounds = "warn" +#tuple_array_conversions = "warn" +#unchecked_duration_subtraction = "warn" +#undocumented_unsafe_blocks = "warn" +#unimplemented = "warn" +#uninhabited_references = "warn" +#uninlined_format_args = "warn" +#unnecessary_box_returns = "warn" +#unnecessary_safety_doc = "warn" +#unnecessary_struct_initialization = "warn" +#unnecessary_wraps = "warn" +#unnested_or_patterns = "warn" +#unused_peekable = "warn" +#unused_rounding = "warn" +#unused_self = "warn" +#unwrap_used = "warn" +#use_self = "warn" +#useless_transmute = "warn" +#verbose_file_reads = "warn" +#wildcard_dependencies = "warn" +#wildcard_imports = "warn" +#zero_sized_map_values = "warn" +## Disabled waiting on https://github.com/rust-lang/rust-clippy/issues/9602 +##self_named_module_files = "warn" +# +#assigning_clones = "allow" # Too much for too little +#manual_range_contains = "allow" # this one is just worse imho +#map_unwrap_or = "allow" # so is this one +#ref_patterns = "allow" # It's nice to avoid ref pattern, but there are some situations that are hard (impossible?) to express without. +# +#iter_over_hash_type = "allow" +#let_underscore_untyped = "allow" +#missing_assert_message = "allow" +#missing_errors_doc = "allow" +#significant_drop_tightening = "allow" # An update of parking_lot made this trigger in a lot of places. + [profile.release] #panic = "abort" # Treat warnings as errors in release builds diff --git a/benches/crypto_read.rs b/benches/crypto_read.rs index 885178c8..edb326c3 100644 --- a/benches/crypto_read.rs +++ b/benches/crypto_read.rs @@ -30,7 +30,7 @@ fn bench_read_1mb_chacha_file(c: &mut Criterion) { let mut reader = crypto::create_read(file, cipher, &key); black_box(&reader); io::copy(&mut reader, &mut io::sink()).unwrap(); - }) + }); }); } @@ -52,7 +52,7 @@ fn bench_read_1mb_aes_file(c: &mut Criterion) { io::copy(&mut cursor_random, &mut writer).unwrap(); black_box(&writer); writer.finish().unwrap(); - }) + }); }); } @@ -79,7 +79,7 @@ fn bench_read_1mb_chacha_ram(c: &mut Criterion) { let mut reader = crypto::create_read(cursor, cipher, &key); black_box(&reader); io::copy(&mut reader, &mut io::sink()).unwrap(); - }) + }); }); } @@ -106,7 +106,7 @@ fn bench_read_1mb_aes_ram(c: &mut Criterion) { let mut reader = crypto::create_read(cursor, cipher, &key); black_box(&reader); io::copy(&mut reader, &mut io::sink()).unwrap(); - }) + }); }); } diff --git a/clippy.toml b/clippy.toml index 381888bc..6e413f56 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,60 @@ allowed-duplicate-crates = ["syn", "socket2", "windows-sys", "windows-targets", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", 'windows_x86_64_gnu', "windows_x86_64_gnullvm", "windows_x86_64_msvc", "memoffset", "nix", "parking_lot_core", "polling", "redox_syscall", "regex-automata", "regex-syntax", "rustix", "sha2", "bitflags", "block-buffer", "crypto-common", "digest", "event-listener", "event-listener-strategy", "fastrand", "futures-lite", "async-io", "async-lock", "heck", "linux-raw-sys", "arrayvec", "cfg-if", "hermit-abi"] too-many-arguments-threshold = 10 +# +##msrv = "1.75" + +allow-unwrap-in-tests = true + +## https://doc.rust-lang.org/nightly/clippy/lint_configuration.html#avoid-breaking-exported-api +# We want suggestions, even if it changes public API. +#avoid-breaking-exported-api = false + +excessive-nesting-threshold = 16 + +#max-fn-params-bools = 2 + +# https://rust-lang.github.io/rust-clippy/master/index.html#/large_include_file +#max-include-file-size = 1000000 + +# https://rust-lang.github.io/rust-clippy/master/index.html#/large_stack_frames +stack-size-threshold = 512000 + +#too-many-lines-threshold = 600 + +# ----------------------------------------------------------------------------- + +# https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros +#disallowed-macros = [ +# 'dbg', +# +# # 'std::eprint', +# # 'std::eprintln', +# # 'std::print', +# # 'std::println', +# +# # 'std::unimplemented', # generated by ArrowDeserialize derive-macro :( +#] + +# https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names +disallowed-names = [] + +# Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown +doc-valid-idents = [ + "GitHub", + "GLB", + "GLTF", + "iOS", + "macOS", + "MessagePack", + "MiMalloc", + "NaN", + "OBJ", + "OpenGL", + "PyPI", + "sRGB", + "sRGBA", + "WebGL", + "WebGPU", + "WebSocket", + "WebSockets", +] diff --git a/examples/magic_of_blanket_impl.rs b/examples/magic_of_blanket_impl.rs index 3c98ab4b..0d6b8cfc 100644 --- a/examples/magic_of_blanket_impl.rs +++ b/examples/magic_of_blanket_impl.rs @@ -1,8 +1,8 @@ /// This demonstrates the magic of blanket implementation. /// -/// If we have a wrapper that wraps [Read] we don't have access to [Seek::seek] method, but +/// If we have a wrapper that wraps [Read] we don't have access to [`Seek::seek`] method, but /// if we wrap [Read] + [Seek] we do. -/// We use this in [rencfs::crypto::read] and also in [rencfs::crypto::write]. +/// We use this in [`rencfs::crypto::read`] and also in [`rencfs::crypto::write`]. use std::io; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/crypto.rs b/src/crypto.rs index df0f0d35..72ca489a 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -539,7 +539,7 @@ mod tests { #[test] fn test_encrypt_decrypt_empty_string() { let key = SecretVec::from(vec![0; 32]); - let secret = SecretString::new(Box::new("".to_string())); + let secret = SecretString::new(Box::new(String::new())); let encrypted = encrypt(&secret, Cipher::ChaCha20Poly1305, &key).unwrap(); let decrypted = decrypt(&encrypted, Cipher::ChaCha20Poly1305, &key).unwrap(); @@ -549,24 +549,24 @@ mod tests { #[test] fn test_hash_file_name_special_cases() { - let expected = "$.".to_string(); + let expected = "$.".to_owned(); let name = SecretString::new(Box::new(expected.clone())); let result = hash_file_name(&name); assert_eq!(result, expected); - let expected = "$..".to_string(); + let expected = "$..".to_owned(); let name = SecretString::new(Box::new(expected.clone())); let result = hash_file_name(&name); assert_eq!(result, expected); - let input = ".".to_string(); - let expected = "$.".to_string(); + let input = ".".to_owned(); + let expected = "$.".to_owned(); let name = SecretString::new(Box::new(input)); let result = hash_file_name(&name); assert_eq!(result, expected); - let input = "..".to_string(); - let expected = "$..".to_string(); + let input = "..".to_owned(); + let expected = "$..".to_owned(); let name = SecretString::new(Box::new(input)); let result = hash_file_name(&name); assert_eq!(result, expected); @@ -574,7 +574,7 @@ mod tests { #[test] fn test_hash_file_name_regular_case() { - let name = SecretString::new(Box::new("filename.txt".to_string())); + let name = SecretString::new(Box::new("filename.txt".to_owned())); let result = hash_file_name(&name); let expected_hash = hex::encode(hash_secret_string(&name)); assert_eq!(result, expected_hash); @@ -582,7 +582,7 @@ mod tests { #[test] fn test_hash_secret_string() { - let secret = SecretString::new(Box::new("hash this secret".to_string())); + let secret = SecretString::new(Box::new("hash this secret".to_owned())); let expected_hash_hex = "d820cbf278fc742d8ec30e43947674689cd06d5aa9b71a2f9afe162a4ce408dc"; let hash_hex = hex::encode(hash_secret_string(&secret)); diff --git a/src/crypto/write.rs b/src/crypto/write.rs index 9de2785d..1a673ec0 100644 --- a/src/crypto/write.rs +++ b/src/crypto/write.rs @@ -24,13 +24,13 @@ pub(crate) const BLOCK_SIZE: usize = 100; // round value easier for debugging #[cfg(not(test))] pub(crate) const BLOCK_SIZE: usize = 256 * 1024; // 256 KB block size -/// If you have your custom [Write] + [Seek] you want to pass to [CryptoWrite] it needs to implement this trait. +/// If you have your custom [Write] + [Seek] you want to pass to [`CryptoWrite`] it needs to implement this trait. /// It has a blanket implementation for [Write] + [Seek] + [Read]. pub trait WriteSeekRead: Write + Seek + Read {} impl WriteSeekRead for T {} -/// If you have your custom implementation for [Write] you want to pass to [CryptoWrite] it needs to implement this trait. +/// If you have your custom implementation for [Write] you want to pass to [`CryptoWrite`] it needs to implement this trait. /// /// It has a blanket implementation for [Write] + [Seek] + [Read] + [`'static`] but in case your implementation is only [Write] it needs to implement this. pub trait CryptoInnerWriter: Write + Any { diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index f1a8da97..88b16bde 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -838,7 +838,7 @@ impl EncryptedFs { } let lock = self .serialize_dir_entries_hash_locks - .get_or_insert_with(hash_path.to_str().unwrap().to_string(), || { + .get_or_insert_with(hash_path.to_str().unwrap().to_owned(), || { RwLock::new(false) }); let guard = lock.read().await; @@ -1150,7 +1150,7 @@ impl EncryptedFs { } } }; - let file_path = entry.path().to_str().unwrap().to_string(); + let file_path = entry.path().to_str().unwrap().to_owned(); // try from cache let lock = self.dir_entries_meta_cache.get().await?; let mut cache = lock.lock().await; @@ -2038,7 +2038,7 @@ impl EncryptedFs { attr.ino, &DirectoryEntry { ino: new_parent, - name: SecretBox::new(Box::new("$..".to_string())), + name: SecretBox::new(Box::new("$..".to_owned())), kind: FileType::Directory, }, ) @@ -2347,7 +2347,7 @@ impl EncryptedFs { .join(encrypted_name_clone.clone()); let lock = self_clone .serialize_dir_entries_ls_locks - .get_or_insert_with(file_path.to_str().unwrap().to_string(), || { + .get_or_insert_with(file_path.to_str().unwrap().to_owned(), || { RwLock::new(false) }); let _guard = lock.write().await; @@ -2376,7 +2376,7 @@ impl EncryptedFs { let file_path = parent_path.join(HASH_DIR).join(name); let lock = self_clone .serialize_dir_entries_hash_locks - .get_or_insert_with(file_path.to_str().unwrap().to_string(), || { + .get_or_insert_with(file_path.to_str().unwrap().to_owned(), || { RwLock::new(false) }); let _guard = lock.write().await; @@ -2411,7 +2411,7 @@ impl EncryptedFs { let path = parent_path.join(HASH_DIR).join(name); let lock = self .serialize_dir_entries_hash_locks - .get_or_insert_with(path.to_str().unwrap().to_string(), || RwLock::new(false)); + .get_or_insert_with(path.to_str().unwrap().to_owned(), || RwLock::new(false)); let guard = lock.write().await; let (_, _, name): (u64, FileType, String) = bincode::deserialize_from(crypto::create_read( @@ -2425,7 +2425,7 @@ impl EncryptedFs { let path = parent_path.join(LS_DIR).join(name); let lock = self .serialize_dir_entries_ls_locks - .get_or_insert_with(path.to_str().unwrap().to_string(), || RwLock::new(false)); + .get_or_insert_with(path.to_str().unwrap().to_owned(), || RwLock::new(false)); let _guard = lock.write().await; fs::remove_file(path)?; Ok(()) diff --git a/src/encryptedfs/test.rs b/src/encryptedfs/test.rs index 783cab77..6816029b 100644 --- a/src/encryptedfs/test.rs +++ b/src/encryptedfs/test.rs @@ -966,7 +966,7 @@ async fn test_find_by_name() { ); }, ) - .await + .await; } #[tokio::test] @@ -1002,7 +1002,7 @@ async fn test_exists_by_name() { } }, ) - .await + .await; } #[tokio::test] @@ -1046,7 +1046,7 @@ async fn test_remove_dir() { } }, ) - .await + .await; } #[tokio::test] @@ -1092,7 +1092,7 @@ async fn test_remove_file() { } }, ) - .await + .await; } #[tokio::test] @@ -1149,7 +1149,7 @@ async fn test_find_by_name_exists_by_name100files() { .is_some()); }, ) - .await + .await; } #[tokio::test] @@ -1185,7 +1185,7 @@ async fn test_create_structure_and_root() { assert!(fs.data_dir.join(CONTENTS_DIR).join(ROOT_INODE_STR).is_dir()); }, ) - .await + .await; } #[tokio::test] @@ -1376,7 +1376,7 @@ async fn test_create() { )); }, ) - .await + .await; } #[tokio::test] @@ -2271,7 +2271,7 @@ async fn test_rename() { )); }, ) - .await + .await; } #[tokio::test] @@ -2311,7 +2311,7 @@ async fn test_open() { )); }, ) - .await + .await; } // #[tokio::test] @@ -2356,7 +2356,7 @@ async fn test_read_only_create() { assert!(matches!(create_file_result, Err(FsError::ReadOnly))); }, ) - .await + .await; } #[tokio::test] @@ -2480,5 +2480,5 @@ async fn test_read_only_write() { assert!(matches!(flush_result, Err(FsError::ReadOnly))); }, ) - .await + .await; } diff --git a/src/test_common.rs b/src/test_common.rs index bd047378..f8e18611 100644 --- a/src/test_common.rs +++ b/src/test_common.rs @@ -30,7 +30,7 @@ pub static TESTS_DATA_DIR: LazyLock = LazyLock::new(|| { fs::remove_file(tmp.to_str().unwrap()).expect("cannot remove tmp file"); tmp.to_path_buf() }; - println!("tmp {}", tmp.to_path_buf().to_string_lossy()); + println!("tmp {}", tmp.clone().to_string_lossy()); tmp.parent() .expect("oops, we don't have a parent") .join("rencfs-test-data") diff --git a/tests/linux_mount_setup/mod.rs b/tests/linux_mount_setup/mod.rs index a89899ee..e5a06b7c 100644 --- a/tests/linux_mount_setup/mod.rs +++ b/tests/linux_mount_setup/mod.rs @@ -62,10 +62,12 @@ impl Drop for TestResource { let res = self.runtime.block_on(async { mh.umount().await }); match res { Ok(_) => println!("Succesfully unmounted"), - Err(e) => panic!( - "Something went wrong when unmounting {}.You may need to manually unmount", - e - ), + Err(e) => { + panic!( + "Something went wrong when unmounting {}.You may need to manually unmount", + e + ) + } } } } @@ -86,7 +88,7 @@ impl TestGuard { }); } RESOURCE_COUNT.fetch_add(1, Ordering::SeqCst); - TestGuard + Self } } diff --git a/tests/rencfs_linux_itest.rs b/tests/rencfs_linux_itest.rs index 23f07cdb..ca3ff27b 100644 --- a/tests/rencfs_linux_itest.rs +++ b/tests/rencfs_linux_itest.rs @@ -27,7 +27,7 @@ fn it_create_and_write_file() { { let res = File::create_new(path); assert!(res.is_ok(), "failed to create [{}]", res.err().unwrap()); - let bytes_written = res.unwrap().write_all("test".as_bytes()); + let bytes_written = res.unwrap().write_all(b"test"); assert!( bytes_written.is_ok(), "failed to write [{}]", From fd5ac4a7ad944c51a272b0988e19208da5d407f4 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 09:43:54 +0200 Subject: [PATCH 025/150] Add linter conf but commented out for now. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 53b4fdb4..e1c92c64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -187,7 +187,7 @@ harness = false #string_to_string = "warn" #suspicious_command_arg_space = "warn" #suspicious_xor_used_as_pow = "warn" -#todo = "warn" +#ktodo = "warn" #too_many_lines = "warn" #trailing_empty_array = "warn" #trait_duplication_in_bounds = "warn" From ef281f9baa523e1f4be966b36eb27eb3e98cba4b Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 13:43:57 +0200 Subject: [PATCH 026/150] add -act file --- check-before-push-act.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100755 check-before-push-act.sh diff --git a/check-before-push-act.sh b/check-before-push-act.sh new file mode 100755 index 00000000..1f26ae9d --- /dev/null +++ b/check-before-push-act.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +export CARGO_TERM_COLOR=always +export RUSTFLAGS="-Dwarnings" +export RUSTDOCFLAGS="-Dwarnings" + +cargo fmt --all +cargo clippy --release --all-targets --fix --allow-dirty --allow-staged +sudo act --action-offline-mode -W .github/workflows/build_and_tests.yaml + From 22cd1ef3c89dee9992113824d33a96510c124f63 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 14:00:46 +0200 Subject: [PATCH 027/150] docs # Conflicts: # CONTRIBUTING.md --- check-before-push.bat => scripts/check-before-push.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename check-before-push.bat => scripts/check-before-push.bat (98%) diff --git a/check-before-push.bat b/scripts/check-before-push.bat similarity index 98% rename from check-before-push.bat rename to scripts/check-before-push.bat index 42f6eb42..be29d52f 100755 --- a/check-before-push.bat +++ b/scripts/check-before-push.bat @@ -2,7 +2,7 @@ setlocal set CARGO_TERM_COLOR=always -set RUSTFLAGS=-Dwarnings +gsset RUSTFLAGS=-Dwarnings set RUSTDOCFLAGS=-Dwarnings if %errorlevel% neq 0 exit /b %errorlevel% From 58e70a76187a10c2d5a18c6c20bd8ebae9d806cf Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 26 Nov 2024 14:01:04 +0200 Subject: [PATCH 028/150] docs # Conflicts: # CONTRIBUTING.md --- CONTRIBUTING.md | 11 +++++------ .../check-before-push-act.sh | 0 check-before-push.sh => scripts/check-before-push.sh | 0 {git-hooks => scripts/git-hooks}/linux-macos/pre-push | 0 {git-hooks => scripts/git-hooks}/windows/pre-push | 0 5 files changed, 5 insertions(+), 6 deletions(-) rename check-before-push-act.sh => scripts/check-before-push-act.sh (100%) rename check-before-push.sh => scripts/check-before-push.sh (100%) rename {git-hooks => scripts/git-hooks}/linux-macos/pre-push (100%) rename {git-hooks => scripts/git-hooks}/windows/pre-push (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28d6685f..7baaca43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,12 @@ ## How to contribute > [!IMPORTANT] -> **These steps are a bit particular to this repo but you can mainly apply them to all repositories by changing the specifics.** +> **These steps are more particular to this repo but mainly apply to all repositories; change the specifics.** Unless you explicitly state otherwise, any Contribution intentionally submitted for inclusion in this project by you, as defined in the Apache License shall be dual-licensed as above, without any additional terms or conditions. -1. Join [slack](https://bit.ly/3UU1oXi) and join `#dev-beginners` channel and become familiar with the info in there +1. Join [slack](https://bit.ly/3UU1oXi) and join `#dev-beginners` channel 2. Become familiar with docs and code by reading the [ramp-up](Ramp-up.md) guide 3. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and be able to create your own branches and not need to fork the repo @@ -14,8 +14,8 @@ defined in the Apache License shall be dual-licensed as above, without any addit repo that you'll be working on. You can see [good for first issues](https://github.com/radumarias/rencfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) that you can pick from -5. **Assign the issues you are working on to you and move them to the corresponding status column as you progress - on them. If the task is not an issue yet, convert it to an issue first** +5. **Assign the issues you are working on to you and move them to the corresponding status column as you are progressing + on them. If the task is not an issue yet, convert it to issue first** 6. Make the changes in your branch 7. Add docs as they apply 8. Add tests, benchmarks, and examples for your changes, if applicable @@ -29,8 +29,7 @@ defined in the Apache License shall be dual-licensed as above, without any addit Make it executable in Linux and macOS with `chmod +x .git/hooks/pre-push` .This will run when you do `git push` and will make the push quite slow, but please give it time to complete as this helps to fix any issues locally and not rely just on - running `ci` on GitHub when you create the PR. - If you don't want to create this git hook then make sure before you push you run `check-before-push.sh` on Linux and `macOS` or `check-before-push.bat` on `Windows` + running `ci` on GitHub when you create the PR 13. Commit and push your changes, and if there are any errors, fix them before you push them 14. Create a `PR` back to the `parent` repo targeting the `main` branch and request review from owners of the repository by adding them to the `Reviewers` field diff --git a/check-before-push-act.sh b/scripts/check-before-push-act.sh similarity index 100% rename from check-before-push-act.sh rename to scripts/check-before-push-act.sh diff --git a/check-before-push.sh b/scripts/check-before-push.sh similarity index 100% rename from check-before-push.sh rename to scripts/check-before-push.sh diff --git a/git-hooks/linux-macos/pre-push b/scripts/git-hooks/linux-macos/pre-push similarity index 100% rename from git-hooks/linux-macos/pre-push rename to scripts/git-hooks/linux-macos/pre-push diff --git a/git-hooks/windows/pre-push b/scripts/git-hooks/windows/pre-push similarity index 100% rename from git-hooks/windows/pre-push rename to scripts/git-hooks/windows/pre-push From 2c9af0db19c8fba4c2c6f269d2de60525638bcdd Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Mon, 2 Dec 2024 17:43:17 +0200 Subject: [PATCH 029/150] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8b9c14e5..2a910d1b 100644 --- a/README.md +++ b/README.md @@ -466,18 +466,18 @@ on most CPUs via AES-NI. However, where hardware acceleration is not available, - `AES-GCM` can target multiple security levels (`128-bit`, `192-bit`, `256-bit`), whereas `ChaCha20-Poly1305` is only defined at the `256-bit` security level. - Nonce size: - - `AES-GCM`: Varies, but the standard is `96 bits` (`12 bytes`). + - `AES-GCM`: Varies, but the standard is `96-bit` (`12 bytes`). If you supply a longer nonce, this gets hashed down to `16 bytes`. - `ChaCha20-Poly1305`: The standardized version uses `96-bit` nonce (`12 bytes`), but the original used `64-bit` nonce (`8 bytes`). - Wear-out of a single (key, nonce) pair: - - `AES-GCM`: Messages must be less than `2^32 – 2` blocks (a.k.a. `2^36 – 32 bytes`, a.k.a. `2^39 – 256 bits`), that's + - `AES-GCM`: Messages must be less than `2^32 – 2` blocks (a.k.a. `2^36 – 32 bytes`, a.k.a. `2^39 – 256-bit`), that's roughly `64GB`. This also makes the security analysis of `AES-GCM` with long nonces complicated since the hashed nonce doesn’t start with the lower `4 bytes` set to `00 00 00 02`. - - `ChaCha20-Poly1305`: `ChaCha` has an internal counter (`32 bits` in the standardized IETF variant, `64 bits` in the - original design). Max message length is `2^39 - 256 bits`, about `256GB` + - `ChaCha20-Poly1305`: `ChaCha` has an internal counter (`32-bit` in the standardized IETF variant, `64-bit` in the + original design). Max message length is `2^39 - 256-bit`, about `256GB` - Neither algorithm is **nonce misuse-resistant**. - `ChaChaPoly1305` is better at `SIMD` @@ -496,7 +496,7 @@ files. However, the content of the file could be bigger, and we read until the o pick up the new zeros bytes are written on truncating by increasing the size. If content is smaller, the read would stop and end-of-file of the actual content, so this would not be such a big issue -- **What kind of metadata does it leak**: None, we encrypt filename, content, metadata and we hide files count, size and all time fields +- **What kind of metadata does it leak**: None, we encrypt filename, content, and metadata and we hide file count, size, and all-time fields - It's always recommended to use encrypted disks for at least your sensitive data; this project is not a replacement for that - To reduce the risk of the encryption key being exposed from memory, it's recommended to disable memory dumps on the From 6f1ea900eb13b1c0aebd4d86100d0fc76418c8e3 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 4 Dec 2024 03:43:20 +0200 Subject: [PATCH 030/150] panic abort on release, update mount name --- Cargo.toml | 2 +- src/mount/linux.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e1c92c64..945555e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -227,7 +227,7 @@ harness = false #significant_drop_tightening = "allow" # An update of parking_lot made this trigger in a lot of places. [profile.release] -#panic = "abort" +panic = "abort" # This leads to better optimizations and smaller binaries (and is the default in Wasm anyways). # Treat warnings as errors in release builds rustflags = ["-Dwarnings"] lto = true diff --git a/src/mount/linux.rs b/src/mount/linux.rs index f65c779b..37aa7946 100644 --- a/src/mount/linux.rs +++ b/src/mount/linux.rs @@ -1446,6 +1446,7 @@ async fn mount_fuse( .read_only(read_only) .allow_root(allow_root) .allow_other(allow_other) + .fs_name("rencfs") .clone(); let mount_path = OsStr::new(mountpoint.to_str().unwrap()); From 3008789bcb08a1d67474b6a4083daf2e7c128c67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 22:47:51 +0200 Subject: [PATCH 031/150] Bump hashbrown in the cargo group across 1 directory (#248) Bumps the cargo group with 1 update in the / directory: [hashbrown](https://github.com/rust-lang/hashbrown). Updates `hashbrown` from 0.15.0 to 0.15.2 - [Changelog](https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/hashbrown/commits) --- updated-dependencies: - dependency-name: hashbrown dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f1cd04c..e556801b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1114,9 +1114,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", From 930322f03dcfd8973344d73003722b3d4c7ce59f Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 5 Dec 2024 14:12:49 +0200 Subject: [PATCH 032/150] Update README.md --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2a910d1b..aaaec3f1 100644 --- a/README.md +++ b/README.md @@ -153,19 +153,21 @@ docker pull xorio42/rencfs Start a container to set up mount in it ```bash -docker run -it --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined xorio42/rencfs:latest /bin/sh +docker run -v ~/Downloads:/Downloads -it --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined xorio42/rencfs:latest /bin/sh ``` +**Replace `~/Downloads` with a path you want to share with the container.** + In the container, create mount and data directories ```bash -mkdir fsmnt && mkdir fsdata +mkdir mnt && mkdir data ``` Start `rencfs` ```bash -rencfs mount --mount-point fsmnt --data-dir fsdata +rencfs mount --mount-point mnt --data-dir data -l WARN ``` Enter a password for encryption. @@ -179,13 +181,13 @@ docker ps In another terminal, attach to the running container with the above ID ```bash -docker exec -it /bin/sh +docker exec -it /bin/sh ``` -From here, you can play with it by creating files in `fsmnt` directory +From here, you can play with it by creating files in `mnt` directory ```bash -cd fsmnt +cd mnt mkdir 1 ls echo "test" > 1/test From 5b26c647954aeb98dcf8d210d61c3ade40bab234 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 5 Dec 2024 14:15:05 +0200 Subject: [PATCH 033/150] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aaaec3f1..4b6f26b3 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ docker pull xorio42/rencfs Start a container to set up mount in it ```bash -docker run -v ~/Downloads:/Downloads -it --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined xorio42/rencfs:latest /bin/sh +docker run -v ~/Downloads:/share -it --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined xorio42/rencfs:latest /bin/sh ``` **Replace `~/Downloads` with a path you want to share with the container.** @@ -194,6 +194,14 @@ echo "test" > 1/test cat 1/test ``` +You can also copy files from `/share`. + +```bash +cd mnt +cp /share/file1.txt . +file file1.txt +``` + ## As a library For the library, you can follow the [documentation](https://docs.rs/rencfs/latest/rencfs/). From 9acc7ae6939f41ce1c79e70ba5352cc4f29f9475 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 5 Dec 2024 14:23:31 +0200 Subject: [PATCH 034/150] change info to debug in write --- src/encryptedfs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 88b16bde..70c7c3f4 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -1677,7 +1677,7 @@ impl EncryptedFs { buf.len() ); } - info!( + warn!( "written uncommited for {ino} size {}", self.sizes_write .lock() From 364bcfd36887c9b3234380287d8543174e33cf88 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 5 Dec 2024 17:52:50 +0200 Subject: [PATCH 035/150] fix log level. add new Docker file for deb to be compatible with macOS too --- Dockerfile-deb | 28 ++++++++++++++++++++++++++++ src/encryptedfs.rs | 18 +++++++++--------- 2 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 Dockerfile-deb diff --git a/Dockerfile-deb b/Dockerfile-deb new file mode 100644 index 00000000..c05f040b --- /dev/null +++ b/Dockerfile-deb @@ -0,0 +1,28 @@ +# Use the official Rust image as a base +FROM rust:latest + +# Set the working directory inside the container +WORKDIR /usr/src/app + +# Copy the current directory's contents to the container +COPY . . + +# Install dependencies (if needed) +RUN apt-get update && apt-get upgrade -y && apt-get install -y \ + binutils \ + build-essential \ + ca-certificates \ + curl \ + file \ + g++ \ + gcc \ + make \ + patch \ + --no-install-recommends && \ + rm -rf /var/lib/apt/lists/* + +# Build the Rust project +RUN cargo build --release + +# Set the command to run the built binary +CMD ["./target/release/rencfs"] diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 70c7c3f4..0722f173 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -1677,15 +1677,15 @@ impl EncryptedFs { buf.len() ); } - warn!( - "written uncommited for {ino} size {}", - self.sizes_write - .lock() - .await - .get(&ino) - .unwrap() - .load(Ordering::SeqCst) - ); + // warn!( + // "written uncommited for {ino} size {}", + // self.sizes_write + // .lock() + // .await + // .get(&ino) + // .unwrap() + // .load(Ordering::SeqCst) + // ); Ok(len) } From eae4f9f87c2b31ff4669a5f0c846b4614cbd54af Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 5 Dec 2024 19:02:39 +0200 Subject: [PATCH 036/150] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4b6f26b3..da27b650 100644 --- a/README.md +++ b/README.md @@ -114,14 +114,14 @@ For detailed description of the various sequence flows please look into [Flows]( # Alternatives - [Alternatives](https://www.libhunt.com/r/rencfs) -- [EncFS](https://vgough.github.io/encfs/) and [alternatives](https://alternativeto.net/software/encfs/) -- [CryFS](https://www.cryfs.org/) +- [Cryptomator](https://cryptomator.org/) - [gocryptfs](https://nuetzlich.net/gocryptfs/) -- [fscrypt](https://www.kernel.org/doc/html/v4.18/filesystems/fscrypt.html) - [VeraCrypt](https://www.veracrypt.fr/code/VeraCrypt/?h=NewSysEncWizard) -- [Cryptomator](https://cryptomator.org/) - [TrueCrypt](https://truecrypt.sourceforge.net/) - [DroidFS, F-Droid](https://f-droid.org/en/packages/sushi.hardcore.droidfs/) +- [EncFS](https://vgough.github.io/encfs/) and [alternatives](https://alternativeto.net/software/encfs/) +- [CryFS](https://www.cryfs.org/) +- [fscrypt](https://www.kernel.org/doc/html/v4.18/filesystems/fscrypt.html) - [LUKS, dm-crypt](https://guardianproject.info/archive/luks/) - [AES Crypt](https://www.aescrypt.com/) - [Windows BitLocker](https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/) @@ -299,7 +299,7 @@ You can see more [here](https://crates.io/crates/rencfs) ## Browser -If you want to give it a quick try and not setup anything locally you can +If you want to give it a quick try and not setup anything locally, you can [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/radumarias/rencfs) [![Open Rustlings On Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/?repo=radumarias%2Frencfs&ref=main) @@ -307,7 +307,7 @@ If you want to give it a quick try and not setup anything locally you can You can compile it, run it, and give it a quick try in the browser. After you start it from above ```bash -sudo apt-get update && sudo apt-get install fuse3 +apt-get update && apt-get install fuse3 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh mkdir mnt && mkdir data cargo run --release -- mount -m mnt -d data From 9db3a60d7219d4e955466332faff853191d2cd55 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 6 Dec 2024 12:09:20 +0200 Subject: [PATCH 037/150] remove some logs --- src/encryptedfs.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 0722f173..cc01ae9d 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -1529,7 +1529,7 @@ impl EncryptedFs { .load(Ordering::SeqCst); info!("written for {ino} {write_size}"); if attr.size != write_size { - error!("size mismatch write {} {}", write_size, attr.size); + // error!("size mismatch write {} {}", write_size, attr.size); } let requested_read = self .requested_read @@ -1672,10 +1672,10 @@ impl EncryptedFs { .unwrap() .fetch_add(len as u64, Ordering::SeqCst); if buf.len() != len { - error!( - "size mismatch in write(), size {size} offset {offset} buf_len {} len {len}", - buf.len() - ); + // error!( + // "size mismatch in write(), size {size} offset {offset} buf_len {} len {len}", + // buf.len() + // ); } // warn!( // "written uncommited for {ino} size {}", From 268e8b5114ff239311fb1f60a6c6a15d21e0012c Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 6 Dec 2024 13:38:54 +0200 Subject: [PATCH 038/150] comment unused var --- src/encryptedfs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index cc01ae9d..0da4cda2 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -1650,7 +1650,7 @@ impl EncryptedFs { (writer.stream_position()?, len) }; - let size = ctx.attr.size; + // let size = ctx.attr.size; if pos > ctx.attr.size { // if we write pass file size set the new size debug!("setting new file size {}", pos); From 48b4769559527a4be067a21c69b4e920922d30d5 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Sat, 7 Dec 2024 12:34:06 +0200 Subject: [PATCH 039/150] move checks for read-only. fix check-before-push --- scripts/check-before-push.bat | 6 ---- scripts/check-before-push.sh | 46 +------------------------- scripts/git-hooks/linux-macos/pre-push | 4 +-- scripts/git-hooks/windows/pre-push | 4 +-- src/encryptedfs.rs | 30 ++++++++--------- 5 files changed, 20 insertions(+), 70 deletions(-) diff --git a/scripts/check-before-push.bat b/scripts/check-before-push.bat index be29d52f..f4ff8206 100755 --- a/scripts/check-before-push.bat +++ b/scripts/check-before-push.bat @@ -31,15 +31,9 @@ if %errorlevel% neq 0 exit /b %errorlevel% cargo doc --workspace --all-features --no-deps if %errorlevel% neq 0 exit /b %errorlevel% -call :add_dryrun_to_version -if %errorlevel% neq 0 exit /b %errorlevel% - REM cargo publish --dry-run --allow-dirty REM if %errorlevel% neq 0 exit /b %errorlevel% -call :revert_version -if %errorlevel% neq 0 exit /b %errorlevel% - cd java-bridge cargo fmt --all if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/check-before-push.sh b/scripts/check-before-push.sh index 90d7204f..c5f54083 100755 --- a/scripts/check-before-push.sh +++ b/scripts/check-before-push.sh @@ -6,42 +6,6 @@ export CARGO_TERM_COLOR=always export RUSTFLAGS="-Dwarnings" export RUSTDOCFLAGS="-Dwarnings" -# Filepath to the Cargo.toml file -CARGO_FILE="Cargo.toml" -BACKUP_FILE="Cargo_backup.txt" - -# Function to add -dryRun to the version -add_dryrun_to_version() { - if [[ -f $CARGO_FILE ]]; then - # Backup the original Cargo.toml before modification - cp "$CARGO_FILE" "$BACKUP_FILE" - - # Extract the current version from the file - ORIGINAL_VERSION=$(grep -oP '^version\s*=\s*"\K[^\"]+' "$CARGO_FILE") - - if [[ -n $ORIGINAL_VERSION ]]; then - # Modify the version and write it back to the Cargo.toml file - sed -i "s/version = \"$ORIGINAL_VERSION\"/version = \"$ORIGINAL_VERSION-dryRun\"/" "$CARGO_FILE" - echo "Version modified to: $ORIGINAL_VERSION-dryRun" - else - echo "No version found in the file." - fi - else - echo "Cargo.toml file not found!" - fi -} - -# Function to revert the version to its original state by restoring from backup -revert_version() { - if [[ -f $BACKUP_FILE ]]; then - # Restore the original Cargo.toml from the backup - mv "$BACKUP_FILE" "$CARGO_FILE" - echo "Cargo.toml reverted to the original version." - else - echo "Backup file not found! Cannot revert." - fi -} - cargo fmt --all cargo build --all-targets --all-features --target x86_64-unknown-linux-gnu @@ -59,15 +23,7 @@ cargo clippy --all-targets --release --target x86_64-unknown-linux-gnu -- \ cargo test --release --all --all-features --target x86_64-unknown-linux-gnu cargo doc --workspace --all-features --no-deps --target x86_64-unknown-linux-gnu -pwd -ls -l -add_dryrun_to_version -cargo publish --dry-run --allow-dirty --target x86_64-unknown-linux-gnu -pwd -ls -l -revert_version -pwd -ls -l +# cargo publish --dry-run --allow-dirty --target x86_64-unknown-linux-gnu cargo aur cargo generate-rpm diff --git a/scripts/git-hooks/linux-macos/pre-push b/scripts/git-hooks/linux-macos/pre-push index 7ce60a58..8f16a5f6 100644 --- a/scripts/git-hooks/linux-macos/pre-push +++ b/scripts/git-hooks/linux-macos/pre-push @@ -48,6 +48,6 @@ EOF # exec git diff-index --check --cached $against -- fi -if [ -e "./check-before-push.sh" ]; then - ./check-before-push.sh +if [ -e "./scripts/check-before-push.sh" ]; then + ./scripts/check-before-push.sh fi diff --git a/scripts/git-hooks/windows/pre-push b/scripts/git-hooks/windows/pre-push index 842f923d..d6bec32d 100644 --- a/scripts/git-hooks/windows/pre-push +++ b/scripts/git-hooks/windows/pre-push @@ -38,8 +38,8 @@ if ($allownonascii -ne $true) { # Call an additional script for checks before pushing (if applicable) # Ensure that `check-before-push.ps1` exists in the repo -if (Test-Path "./check-before-push.bat") { - & "./check-before-push.bat" +if (Test-Path ".\scripts\check-before-push.bat") { + & ".\scripts\check-before-push.bat" } exit 0 diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 0da4cda2..c32d878c 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -669,6 +669,9 @@ impl EncryptedFs { read: bool, write: bool, ) -> FsResult<(u64, FileAttr)> { + if self.read_only { + return Err(FsError::ReadOnly); + } if *name.expose_secret() == "." || *name.expose_secret() == ".." { return Err(FsError::InvalidInput("name cannot be '.' or '..'")); } @@ -678,9 +681,6 @@ impl EncryptedFs { if self.exists_by_name(parent, name)? { return Err(FsError::AlreadyExists); } - if self.read_only { - return Err(FsError::ReadOnly); - } // spawn on a dedicated runtime to not interfere with other higher priority tasks let self_clone = self @@ -872,12 +872,12 @@ impl EncryptedFs { #[allow(clippy::missing_panics_doc)] #[allow(clippy::missing_errors_doc)] pub async fn remove_dir(&self, parent: u64, name: &SecretString) -> FsResult<()> { - if !self.is_dir(parent) { - return Err(FsError::InvalidInodeType); - } if self.read_only { return Err(FsError::ReadOnly); } + if !self.is_dir(parent) { + return Err(FsError::InvalidInodeType); + } if !self.exists_by_name(parent, name)? { return Err(FsError::NotFound("name not found")); @@ -949,15 +949,15 @@ impl EncryptedFs { #[allow(clippy::missing_panics_doc)] #[allow(clippy::missing_errors_doc)] pub async fn remove_file(&self, parent: u64, name: &SecretString) -> FsResult<()> { + if self.read_only { + return Err(FsError::ReadOnly); + } if !self.is_dir(parent) { return Err(FsError::InvalidInodeType); } if !self.exists_by_name(parent, name)? { return Err(FsError::NotFound("name not found")); } - if self.read_only { - return Err(FsError::ReadOnly); - } let attr = self .find_by_name(parent, name) @@ -1693,13 +1693,13 @@ impl EncryptedFs { /// Flush the data to the underlying storage. #[allow(clippy::missing_panics_doc)] pub async fn flush(&self, handle: u64) -> FsResult<()> { + if self.read_only { + return Err(FsError::ReadOnly); + } if handle == 0 { // in the case of directory or if the file was crated without being opened we don't use a handle return Ok(()); } - if self.read_only { - return Err(FsError::ReadOnly); - } let lock = self.read_handles.read().await; let mut valid_fh = lock.get(&handle).is_some(); let lock = self.write_handles.read().await; @@ -1732,12 +1732,12 @@ impl EncryptedFs { file_range_req: &CopyFileRangeReq, size: usize, ) -> FsResult { - if self.is_dir(file_range_req.src_ino) || self.is_dir(file_range_req.dest_ino) { - return Err(FsError::InvalidInodeType); - } if self.read_only { return Err(FsError::ReadOnly); } + if self.is_dir(file_range_req.src_ino) || self.is_dir(file_range_req.dest_ino) { + return Err(FsError::InvalidInodeType); + } let mut buf = vec![0; size]; let len = self From 72a7b3948af1268c423ad2080ebce64c23de5285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan=20Simion?= <101945097+RagnarokMew@users.noreply.github.com> Date: Mon, 9 Dec 2024 20:06:09 +0200 Subject: [PATCH 040/150] Resolves #125: Use ```FROM SCRATCH``` for Docker image (#258) * Change Dockerfile for minimal docker image with scratch Signed-off-by: RagnarokMew <101945097+RagnarokMew@users.noreply.github.com> * Restore old Dockerfile and move new one to Dockerfile_from_scratch Signed-off-by: RagnarokMew <101945097+RagnarokMew@users.noreply.github.com> --------- Signed-off-by: RagnarokMew <101945097+RagnarokMew@users.noreply.github.com> --- Dockerfile_from_scratch | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 Dockerfile_from_scratch diff --git a/Dockerfile_from_scratch b/Dockerfile_from_scratch new file mode 100644 index 00000000..018a3de3 --- /dev/null +++ b/Dockerfile_from_scratch @@ -0,0 +1,53 @@ +################ +##### Builder +FROM alpine:3.19.1 AS builder + +RUN apk update && apk upgrade && apk add binutils build-base ca-certificates curl file g++ gcc make patch fuse3 + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +RUN . ~/.cargo/env && rustup target add x86_64-unknown-linux-musl + +RUN . ~/.cargo/env && rustup default nightly && rustup update + +# Cache downloaded+built dependencies +#COPY Cargo.toml Cargo.lock /usr/src/rencfs/ +#RUN mkdir /usr/src/rencfs/src && \ +# echo 'fn main() {}' > /usr/src/rencfs/src/main.rs +# +#RUN . ~/.cargo/env && cd /usr/src/rencfs/ && cargo build --release && \ +# rm -Rvf /usr/src/rencfs/src + +# Build our actual code +#COPY Cargo.toml Cargo.lock /usr/src/rencfs/ +#COPY src /usr/src/rencfs +COPY . /usr/src/rencfs +#COPY examples /usr/src/rencfs/examples +RUN . ~/.cargo/env && \ + cd /usr/src/rencfs/ && \ + cargo build --target x86_64-unknown-linux-musl --release + +#Copy the fusermount3 binary and libraries into a directory +RUN mkdir /fusermount3dep && \ + cp $(which fusermount3) /fusermount3dep/ && \ + ldd $(which fusermount3) | awk '{ print $3 }' | xargs -I {} cp {} /fusermount3dep/ + + +################ +##### Runtime +FROM scratch AS runtime + +# Copy fusermount3 +COPY --from=builder /fusermount3dep/fusermount3 /usr/bin/ + +# Copy busybox +COPY --from=builder /bin/ /bin/ + +# Copy ld-musl (fusermount3 & busybox dep) +COPY --from=builder /fusermount3dep/ld* /lib/ + +# Copy application binary from builder image +COPY --from=builder /usr/src/rencfs/target/x86_64-unknown-linux-musl/release/rencfs /usr/bin/ + +# Run the application +CMD ["rencfs", "--help"] From f2221a706efbd25db8f609351def19b5bebbc2fb Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 10 Dec 2024 15:11:27 +0200 Subject: [PATCH 041/150] Create Testing.md --- docs/readme/Testing.md | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 docs/readme/Testing.md diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md new file mode 100644 index 00000000..788d7067 --- /dev/null +++ b/docs/readme/Testing.md @@ -0,0 +1,65 @@ +# Testing + +We'd appreciate it if you could help test the app. For now, the filesystem mounting works only on Linux, so the cleanest way is to test on Linux. + +Here are some ways you can do it. + +## Testing in VSCode + +If you want to test quickly and have just VSCode installed locally. + +### First setup + +1. Install VSCode based on your OS +2. Open the [repo](https://github.com/radumarias/rencfs) +3. Press `Code` button + ![image](https://github.com/user-attachments/assets/7c0e8872-fe1f-44b9-a833-2586ade4f618) +4. Create codespace on main + ![image](https://github.com/user-attachments/assets/5fee55f6-ef54-427c-b790-c135312d3355) +5. This will create the container on GitHub. If it asks you to setup config, select minimum possible CPU and RAM +6. Start it and leave it to finish +7. Go back to the repo root. You can close the current tab +8. Press `Code` button + ![image](https://github.com/user-attachments/assets/0baec7da-cbbd-4186-a82b-887e18c0c85d) +9. Press ```...``` right to the instance in the list + ![image](https://github.com/user-attachments/assets/c621c258-009d-46bf-adb7-f81a3d7131f6) +10 Press `Open in Visual Studio Code` +11. Allow it to finish +12. Open a terminal in VSCode from the menu `Terminal -> New Terminal` +13. If you start it for the first time, install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: + ```bash + mkdir tmp + + apt-get update && apt-get install fuse3 + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + mkdir mnt && mkdir data + ``` + Press enter on Rust installation, accepting all defaults + +### Each resume and after Fist setup + +Do steps 2, 8, 9, 10, 11, 12. + +1. Type this in terminal, which will fetch the changes from the repo (if there are conflicts, accept Theirs): + ```bash + git pull + cargo run --release -- mount -m mnt -d data + ``` +2. Input a password and confirm it the first time.**** +3. Copy test files from your machine to `tmp` folder in `VSCode`, by `Ctrl + C / Ctrl + V` or by Drag and Drop +4. Copy files and folders from `tmp` to `mnt` and do all kinds of operations on `nnt` folder +5. Make sure files were copied successfully by right-clicking a file and then `Download...` and save it to local machine +6. Make sure files opens correctly +7. Do all kinds of operations in `mnt` folder and make sure are ok + +## Testing on Linux + +TODO + +## Testing on macOS + +TODO + +## Testing on Windows + +TODO From 43166fdd13441f5dd42207d6ce5391380506beda Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Tue, 10 Dec 2024 15:12:11 +0200 Subject: [PATCH 042/150] Update Testing.md --- docs/readme/Testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 788d7067..7610fd9c 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -45,7 +45,7 @@ Do steps 2, 8, 9, 10, 11, 12. git pull cargo run --release -- mount -m mnt -d data ``` -2. Input a password and confirm it the first time.**** +2. Input a password and confirm it the first time 3. Copy test files from your machine to `tmp` folder in `VSCode`, by `Ctrl + C / Ctrl + V` or by Drag and Drop 4. Copy files and folders from `tmp` to `mnt` and do all kinds of operations on `nnt` folder 5. Make sure files were copied successfully by right-clicking a file and then `Download...` and save it to local machine From 4bd473b009e49460b26ba803278b39729c9f6d23 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 11:39:11 +0200 Subject: [PATCH 043/150] Update Testing.md --- docs/readme/Testing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 7610fd9c..0396496e 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -16,14 +16,14 @@ If you want to test quickly and have just VSCode installed locally. ![image](https://github.com/user-attachments/assets/7c0e8872-fe1f-44b9-a833-2586ade4f618) 4. Create codespace on main ![image](https://github.com/user-attachments/assets/5fee55f6-ef54-427c-b790-c135312d3355) -5. This will create the container on GitHub. If it asks you to setup config, select minimum possible CPU and RAM +5. This will create the container on GitHub. If it asks you to setup config, select the minimum possible CPU and RAM 6. Start it and leave it to finish 7. Go back to the repo root. You can close the current tab 8. Press `Code` button ![image](https://github.com/user-attachments/assets/0baec7da-cbbd-4186-a82b-887e18c0c85d) 9. Press ```...``` right to the instance in the list ![image](https://github.com/user-attachments/assets/c621c258-009d-46bf-adb7-f81a3d7131f6) -10 Press `Open in Visual Studio Code` +10. Press `Open in Visual Studio Code` 11. Allow it to finish 12. Open a terminal in VSCode from the menu `Terminal -> New Terminal` 13. If you start it for the first time, install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: @@ -49,8 +49,8 @@ Do steps 2, 8, 9, 10, 11, 12. 3. Copy test files from your machine to `tmp` folder in `VSCode`, by `Ctrl + C / Ctrl + V` or by Drag and Drop 4. Copy files and folders from `tmp` to `mnt` and do all kinds of operations on `nnt` folder 5. Make sure files were copied successfully by right-clicking a file and then `Download...` and save it to local machine -6. Make sure files opens correctly -7. Do all kinds of operations in `mnt` folder and make sure are ok +6. Make sure files open correctly +7. Do all kinds of operations in `mnt` folder and make sure they are ok ## Testing on Linux From d40f33dabdb749a232226f049831ec407ced5903 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 12:10:51 +0200 Subject: [PATCH 044/150] move docker files to dedicated folder --- .dockerignore | 19 +++++++++++++++++++ Dockerfile => docker/Dockerfile | 0 Dockerfile-deb => docker/Dockerfile-deb | 0 .../Dockerfile_from_scratch | 2 +- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 .dockerignore rename Dockerfile => docker/Dockerfile (100%) rename Dockerfile-deb => docker/Dockerfile-deb (100%) rename Dockerfile_from_scratch => docker/Dockerfile_from_scratch (98%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..7561a06b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +target +.git +.github +.devcontainer +.fleet +.vscode +.gitignore +.gitmodules +.gitattributes +.dockerignore +.editorconfig +.env +.env.example +.envrc +.envrc.example +.envrc.local +.envrc.local.example +.envrc.local.template +.envrc.template \ No newline at end of file diff --git a/Dockerfile b/docker/Dockerfile similarity index 100% rename from Dockerfile rename to docker/Dockerfile diff --git a/Dockerfile-deb b/docker/Dockerfile-deb similarity index 100% rename from Dockerfile-deb rename to docker/Dockerfile-deb diff --git a/Dockerfile_from_scratch b/docker/Dockerfile_from_scratch similarity index 98% rename from Dockerfile_from_scratch rename to docker/Dockerfile_from_scratch index 018a3de3..66d76419 100644 --- a/Dockerfile_from_scratch +++ b/docker/Dockerfile_from_scratch @@ -50,4 +50,4 @@ COPY --from=builder /fusermount3dep/ld* /lib/ COPY --from=builder /usr/src/rencfs/target/x86_64-unknown-linux-musl/release/rencfs /usr/bin/ # Run the application -CMD ["rencfs", "--help"] +CMD ["rencfs", "--help"] \ No newline at end of file From 65ac598bba2fb278f92613afbb27e9e515342fb4 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 12:59:59 +0200 Subject: [PATCH 045/150] Update Testing.md --- docs/readme/Testing.md | 49 +++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 0396496e..ae173d45 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -4,41 +4,50 @@ We'd appreciate it if you could help test the app. For now, the filesystem mount Here are some ways you can do it. -## Testing in VSCode - -If you want to test quickly and have just VSCode installed locally. +## Testing in VSCode in browser and locally ### First setup -1. Install VSCode based on your OS -2. Open the [repo](https://github.com/radumarias/rencfs) -3. Press `Code` button +1. Open the [repo](https://github.com/radumarias/rencfs) +2. Press `Code` button ![image](https://github.com/user-attachments/assets/7c0e8872-fe1f-44b9-a833-2586ade4f618) -4. Create codespace on main +3. Create codespace on main ![image](https://github.com/user-attachments/assets/5fee55f6-ef54-427c-b790-c135312d3355) -5. This will create the container on GitHub. If it asks you to setup config, select the minimum possible CPU and RAM -6. Start it and leave it to finish -7. Go back to the repo root. You can close the current tab -8. Press `Code` button +4. This will create the container on GitHub. If it asks you to setup config, select the minimum possible CPU and RAM +5. Start it and leave it to finish +6. Go back to the repo root. You can close the current tab +7. Press `Code` button ![image](https://github.com/user-attachments/assets/0baec7da-cbbd-4186-a82b-887e18c0c85d) -9. Press ```...``` right to the instance in the list +8. Press ```...``` right to the instance in the list ![image](https://github.com/user-attachments/assets/c621c258-009d-46bf-adb7-f81a3d7131f6) -10. Press `Open in Visual Studio Code` -11. Allow it to finish -12. Open a terminal in VSCode from the menu `Terminal -> New Terminal` -13. If you start it for the first time, install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: +9. Press `Open in Browser` +10. Allow it to finish +11. Open a terminal in VSCode from the menu `Terminal -> New Terminal` +12. Install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: ```bash - mkdir tmp - apt-get update && apt-get install fuse3 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh mkdir mnt && mkdir data ``` Press enter on Rust installation, accepting all defaults -### Each resume and after Fist setup +### Each resume and after fist setup + +Do steps 1, 7, 8 from above. + +#### In Browser + +Do step 9 from above. This will open VSCode in browwser. + +#### In local VSCode + +Make sure you have VSCode installed locally, based on your OS + +After step 8 press `Open in Visual Studio Code`. + +#### Continue -Do steps 2, 8, 9, 10, 11, 12. +Do step 11 from above. 1. Type this in terminal, which will fetch the changes from the repo (if there are conflicts, accept Theirs): ```bash From c0a9d92ec971701eb8c3be6caeee87e031256078 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 13:03:18 +0200 Subject: [PATCH 046/150] Update Testing.md --- docs/readme/Testing.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index ae173d45..6b40600a 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -37,7 +37,8 @@ Do steps 1, 7, 8 from above. #### In Browser -Do step 9 from above. This will open VSCode in browwser. +Do step 9 from above. This will open VSCode in browwser. +Instead of taking step 8 and the above, you can directly click on the container name. #### In local VSCode From 92c67b2a5abe99a90a2add5eeea27b55c0023790 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 14:01:06 +0200 Subject: [PATCH 047/150] use SecretString --- src/encryptedfs.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index c32d878c..31b1448b 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -407,7 +407,7 @@ impl From for SetFileAttr { #[derive(Debug, Clone)] pub struct DirectoryEntry { pub ino: u64, - pub name: SecretBox, + pub name: SecretString, pub kind: FileType, } @@ -423,7 +423,7 @@ impl PartialEq for DirectoryEntry { #[derive(Debug)] pub struct DirectoryEntryPlus { pub ino: u64, - pub name: SecretBox, + pub name: SecretString, pub kind: FileType, pub attr: FileAttr, } @@ -567,7 +567,7 @@ pub struct EncryptedFs { self_weak: std::sync::Mutex>>, attr_cache: ExpireValue>, FsError, AttrCacheProvider>, dir_entries_name_cache: - ExpireValue>>, FsError, DirEntryNameCacheProvider>, + ExpireValue>, FsError, DirEntryNameCacheProvider>, dir_entries_meta_cache: ExpireValue, FsError, DirEntryMetaCacheProvider>, sizes_write: Mutex>, @@ -1976,9 +1976,9 @@ impl EncryptedFs { pub async fn rename( &self, parent: u64, - name: &SecretBox, + name: &SecretString, new_parent: u64, - new_name: &SecretBox, + new_name: &SecretString, ) -> FsResult<()> { if self.read_only { return Err(FsError::ReadOnly); @@ -2115,8 +2115,8 @@ impl EncryptedFs { /// Change the password of the filesystem used to access the encryption key. pub async fn passwd( data_dir: &Path, - old_password: SecretBox, - new_password: SecretBox, + old_password: SecretString, + new_password: SecretString, cipher: Cipher, ) -> FsResult<()> { check_structure(data_dir, false).await?; From 4127184a282b88f39e77f01f0f45dd0d284abb3f Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 18:32:22 +0200 Subject: [PATCH 048/150] Update Testing.md --- docs/readme/Testing.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 6b40600a..f4980ca0 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -4,7 +4,10 @@ We'd appreciate it if you could help test the app. For now, the filesystem mount Here are some ways you can do it. -## Testing in VSCode in browser and locally +## Testing in browser or in local VSCode + +This will create a Codespace instance on GitHub, which is a Linux container, so we will be able to test in it. +Instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month free for Codespace, which means 60 hours for that instance. We will connect to it from yhe browser of local VSCode. ### First setup From 6be20e593392363798edb94d373ab0fa3994c6bf Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 21:51:31 +0200 Subject: [PATCH 049/150] Update Testing.md --- docs/readme/Testing.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index f4980ca0..1dadaf96 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -26,6 +26,9 @@ Instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month free fo 9. Press `Open in Browser` 10. Allow it to finish 11. Open a terminal in VSCode from the menu `Terminal -> New Terminal` + On the browser you can find the menu in the top left 3 lines icon + ![image](https://github.com/user-attachments/assets/48681023-e450-49b3-8526-ec0323be0d40) + 12. Install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: ```bash apt-get update && apt-get install fuse3 From db0c62506bbee081098cc055db07efcc29f1d8b0 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 21:52:09 +0200 Subject: [PATCH 050/150] Update Testing.md --- docs/readme/Testing.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 1dadaf96..5d6fdf08 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -28,7 +28,6 @@ Instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month free fo 11. Open a terminal in VSCode from the menu `Terminal -> New Terminal` On the browser you can find the menu in the top left 3 lines icon ![image](https://github.com/user-attachments/assets/48681023-e450-49b3-8526-ec0323be0d40) - 12. Install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: ```bash apt-get update && apt-get install fuse3 From 16e7b1214110e10c9240a312918a4cf4a10447ae Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 21:53:32 +0200 Subject: [PATCH 051/150] Update Testing.md --- docs/readme/Testing.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 5d6fdf08..b49da369 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -4,10 +4,10 @@ We'd appreciate it if you could help test the app. For now, the filesystem mount Here are some ways you can do it. -## Testing in browser or in local VSCode +## Testing in the browser or in local VSCode -This will create a Codespace instance on GitHub, which is a Linux container, so we will be able to test in it. -Instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month free for Codespace, which means 60 hours for that instance. We will connect to it from yhe browser of local VSCode. +This will create a Codespace instance on GitHub, which is a Linux container, so we will be able to test it. +The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month free for Codespace, which means 60 hours for that instance. We will connect to it from the local VSCode browser. ### First setup @@ -26,7 +26,7 @@ Instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month free fo 9. Press `Open in Browser` 10. Allow it to finish 11. Open a terminal in VSCode from the menu `Terminal -> New Terminal` - On the browser you can find the menu in the top left 3 lines icon + On the browser, you can find the menu in the top left 3 lines icon ![image](https://github.com/user-attachments/assets/48681023-e450-49b3-8526-ec0323be0d40) 12. Install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: ```bash @@ -36,20 +36,20 @@ Instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month free fo ``` Press enter on Rust installation, accepting all defaults -### Each resume and after fist setup +### Each resume and after the first setup -Do steps 1, 7, 8 from above. +Do steps 1, 7, and 8 from above. #### In Browser -Do step 9 from above. This will open VSCode in browwser. +Do step 9 from above. This will open VSCode in the browser. Instead of taking step 8 and the above, you can directly click on the container name. #### In local VSCode Make sure you have VSCode installed locally, based on your OS -After step 8 press `Open in Visual Studio Code`. +After step 8, press `Open in Visual Studio Code`. #### Continue From c561637266fa29e8bbdd594ca2dbc525c05d0c2f Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 22:17:17 +0200 Subject: [PATCH 052/150] Update package_reusable.yaml --- .github/workflows/package_reusable.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/package_reusable.yaml b/.github/workflows/package_reusable.yaml index 4b9c8621..1673ce4d 100644 --- a/.github/workflows/package_reusable.yaml +++ b/.github/workflows/package_reusable.yaml @@ -65,6 +65,8 @@ jobs: IMAGE_NAME: ${{ github.event.repository.name }} IMAGE_TAG: latest SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + with: + file: docker/Dockerfile steps: - uses: actions/checkout@v4 From f9cdd7f4a5a2bd19b3bed5927580bf8d14fbc74d Mon Sep 17 00:00:00 2001 From: Ioana Alexandra <148649854+IoanaAlexandraO@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:22:13 +0200 Subject: [PATCH 053/150] Split README file (#253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Tried to zeroize pass after use using SecretVec (#250) * Zeroized pass using SecretVec * Turn pass to bytes before zeroize --------- -- these should not be but the're present due to some merge issue * Update README.md * Update README.md * change info to debug in write * fix log level. add new Docker file for deb to be compatible with macOS too * Update README.md --------- Co-authored-by: Radu Marias * Added crate, removed redundant methods (#252) * Zeroized pass using SecretVec * Turn pass to bytes before zeroize * Added use secrets::SecretVec, removed redundant to_vec * Changed as_bytes to into_bytes * Split README file * Resolves #125: Use ```FROM SCRATCH``` for Docker image (#257) * Change Dockerfile for minimal docker image with scratch Signed-off-by: RagnarokMew <101945097+RagnarokMew@users.noreply.github.com> * Restore old Dockerfile and move new one to Dockerfile_from_scratch Signed-off-by: RagnarokMew <101945097+RagnarokMew@users.noreply.github.com> --------- Signed-off-by: RagnarokMew <101945097+RagnarokMew@users.noreply.github.com> * comments * implemented 3 requested changes * Add name validation function (#259) Signed-off-by: Mercea Robert-Andrei * Text reordering --------- Signed-off-by: RagnarokMew <101945097+RagnarokMew@users.noreply.github.com> Signed-off-by: Mercea Robert-Andrei Co-authored-by: Năstasie Raul-Ionuț <133516623+misespuneraul@users.noreply.github.com> Co-authored-by: Radu Marias Co-authored-by: Ștefan Simion <101945097+RagnarokMew@users.noreply.github.com> Co-authored-by: Mercea Robert-Andrei <101945115+Ropler6@users.noreply.github.com> --- Dockerfile_from_scratch | 53 ++++ README.md | 517 +++++--------------------------------- docs/Alternatives.md | 22 ++ docs/Build_from_Source.md | 152 +++++++++++ docs/Cipher_comparison.md | 31 +++ docs/Considerations.md | 12 + docs/Functionality.md | 20 ++ docs/Key_features.md | 21 ++ docs/Security.md | 23 ++ docs/Stack.md | 12 + docs/Usage.md | 160 ++++++++++++ java-bridge/src/lib.rs | 12 +- src/encryptedfs.rs | 17 ++ 13 files changed, 596 insertions(+), 456 deletions(-) create mode 100644 Dockerfile_from_scratch create mode 100644 docs/Alternatives.md create mode 100644 docs/Build_from_Source.md create mode 100644 docs/Cipher_comparison.md create mode 100644 docs/Considerations.md create mode 100644 docs/Functionality.md create mode 100644 docs/Key_features.md create mode 100644 docs/Security.md create mode 100644 docs/Stack.md create mode 100644 docs/Usage.md diff --git a/Dockerfile_from_scratch b/Dockerfile_from_scratch new file mode 100644 index 00000000..018a3de3 --- /dev/null +++ b/Dockerfile_from_scratch @@ -0,0 +1,53 @@ +################ +##### Builder +FROM alpine:3.19.1 AS builder + +RUN apk update && apk upgrade && apk add binutils build-base ca-certificates curl file g++ gcc make patch fuse3 + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +RUN . ~/.cargo/env && rustup target add x86_64-unknown-linux-musl + +RUN . ~/.cargo/env && rustup default nightly && rustup update + +# Cache downloaded+built dependencies +#COPY Cargo.toml Cargo.lock /usr/src/rencfs/ +#RUN mkdir /usr/src/rencfs/src && \ +# echo 'fn main() {}' > /usr/src/rencfs/src/main.rs +# +#RUN . ~/.cargo/env && cd /usr/src/rencfs/ && cargo build --release && \ +# rm -Rvf /usr/src/rencfs/src + +# Build our actual code +#COPY Cargo.toml Cargo.lock /usr/src/rencfs/ +#COPY src /usr/src/rencfs +COPY . /usr/src/rencfs +#COPY examples /usr/src/rencfs/examples +RUN . ~/.cargo/env && \ + cd /usr/src/rencfs/ && \ + cargo build --target x86_64-unknown-linux-musl --release + +#Copy the fusermount3 binary and libraries into a directory +RUN mkdir /fusermount3dep && \ + cp $(which fusermount3) /fusermount3dep/ && \ + ldd $(which fusermount3) | awk '{ print $3 }' | xargs -I {} cp {} /fusermount3dep/ + + +################ +##### Runtime +FROM scratch AS runtime + +# Copy fusermount3 +COPY --from=builder /fusermount3dep/fusermount3 /usr/bin/ + +# Copy busybox +COPY --from=builder /bin/ /bin/ + +# Copy ld-musl (fusermount3 & busybox dep) +COPY --from=builder /fusermount3dep/ld* /lib/ + +# Copy application binary from builder image +COPY --from=builder /usr/src/rencfs/target/x86_64-unknown-linux-musl/release/rencfs /usr/bin/ + +# Run the application +CMD ["rencfs", "--help"] diff --git a/README.md b/README.md index da27b650..54733840 100644 --- a/README.md +++ b/README.md @@ -24,520 +24,131 @@ You can also store it in a cloud storage service like Google Drive, Dropbox, etc You can use it as CLI or as a library to build your custom FUSE implementation or other apps that work with encrypted data. -# GUI -There is a [GUI](https://github.com/radumarias/rencfs-desktop/blob/main/demo.gif) too. -# Motivation -Create a `simple,` `performant,` `modular` and `ergonomic` yet `very secure` `encrypted filesystem` to protect your `privacy`, which is also `open source` and is correctly and safely using `well-known audited` crates as `cryptographic primitives.` -# A short story -[The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust_2.pdf) -# Blog and tutorial -There will be a [series](https://medium.com/@xorio42/list/828492b94c23) of articles about the evolution of this project, trying to keep it like a tutorial. This is the [first one](https://systemweakness.com/the-hitchhikers-guide-to-building-an-encrypted-filesystem-in-rust-4d678c57d65c). -# Crate of the week in [This Week in Rust](https://this-week-in-rust.org/blog/2024/08/07/this-week-in-rust-559/#cfp-projects) +# Introduction -It was [crate of the week](https://this-week-in-rust.org/blog/2024/08/14/this-week-in-rust-560/#crate-of-the-week) in Aug 2024. +- Motivation -# Talks + Create a `simple,` `performant,` `modular` and `ergonomic` yet `very secure` `encrypted filesystem` to protect your `privacy`, which is also `open source` and is correctly and safely using `well-known audited` crates as `cryptographic primitives.` -- [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) [@meetup.com/star-tech-rd-reloaded](https://www.meetup.com/star-tech-rd-reloaded/) and [@OmniOpenCon](https://omniopencon.org/) -- [Basics of cryptography, Authenticated Encryption, Rust in cryptography and how to build an encrypted filesystem](https://www.youtube.com/live/HwmVxOl3pQg) @ITDays and [slides](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323). +- A short story -# Key features + [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust_2.pdf) -Some of these are still being worked on and marked with `[WIP]`. -- `Security` using well-known audited `AEAD` cryptography primitives; -- `[WIP]` `Data integrity`, data is written with `WAL` to ensure integrity even on crash or power loss; -- `[WIP]` Hide all info for enhanced `privacy`, all `metadata`, `content`, `file name`, `file size`, `*time` fields, `files count`, and directory structure is encrypted; -- `Safely` manage `credentials` in memory with `mlock(2)`, `mprotect`, `zeroize`, and `expiry` to mitigate cold boot attacks; -- `Memory safety`, `performance`, and `optimized` for `concurrency` with Rust; -- Simplicity; -- Encryption key generated from password; -- Password saved in OS's `keyring`; -- `Change password` without re-encrypting all data; -- `[WIP]` Generate `unique nonce` in `offline mode`; -- `Fast seek` on both reads and writes; -- `Writes in parallel`; -- Exposed with `FUSE`; -- Fully `concurrent` for all operations; -- `[WIP]` Handle `long file names`; -- `[WIP]` Abstraction layer for `Rust File` and `fs` API to use it as lib to `switch to using encrypted files` by just `changing the use statements`; -- `[WIP]` Abstraction layer to `access the storage` with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation. +- Talks -# Functionality + - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) [@meetup.com/star-tech-rd-reloaded](https://www.meetup.com/star-tech-rd-reloaded/) and [@OmniOpenCon](https://omniopencon.org/) + + - [Basics of cryptography, Authenticated Encryption, Rust in cryptography and how to build an encrypted filesystem](https://www.youtube.com/live/HwmVxOl3pQg) @ITDays and [slides](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323). -Some of these are still being worked on and marked with `[WIP]`. -- It keeps all `encrypted` data and `master encryption key` in a dedicated directory with files structured on `inodes` (with - metadata info), files for binary content, and directories with files/directories entries. All data, metadata, and filenames - are encrypted. It generates unique inodes for new files in a multi-instance run and offline mode. -- The password is collected from CLI and saved in the OS's `keyring` while the app runs. This is because, for security concerns, we - clear the password from memory on inactivity, and we derive it again from the password just when needed. -- Master encryption key is also encrypted with another key derived from the password. This gives the ability to change - the - password without re-encrypting all data, we just `re-encrypt` the `master key`. -- Files are `encrypted` in `chunks` of `256KB`, so when making a change, we just re-encrypt that chunks. -- `Fast seek` on read and write, so if you're watching a movie, you can seek any position, and that would be instant. - This is because we can seek a particular chunk. -- The encryption key is `zeroize` in the mem when disposing and idle. Also, it's `mlock`ed while used to prevent being moved to swap. It's - also `mprotect`ed while not in use. -- `[WIP]` Ensure file integrity by saving each change to WAL, so for crashes or power loss, we apply the pending -changes at the next start. This makes the write operations atomic. -- Multiple writes in parallel to the same file, ideal for torrent-like applications. + - Crate of the week in [This Week in Rust](https://this-week-in-rust.org/blog/2024/08/07/this-week-in-rust-559/#cfp-projects) -# Docs +- It was [crate of the week](https://this-week-in-rust.org/blog/2024/08/14/this-week-in-rust-560/#crate-of-the-week) in Aug 2024. -[![rencfs](website/resources/layers.png)](website/resources/layers.png) - -For detailed description of the various sequence flows please look into [Flows](docs/flows.md). - -# Stack - -- it's fully async built upon [tokio](https://crates.io/crates/tokio) and [fuse3](https://crates.io/crates/fuse3) -- [ring](https://crates.io/crates/ring) for encryption and [argon2](https://crates.io/crates/argon2) for key derivation - function (generating key from password used to encrypt the master encryption key) -- [rand_chacha](https://crates.io/crates/rand_chacha) for random generators -- [shush-rs](https://crates.io/crates/shush-rs) keeps pass and encryption keys safe in memory and zero them when - not used. It keeps encryption keys in memory only while being used, and when not active, it will release and zeroing - them in memory. It locks the memory page as well, preventing it from being written to swap. -- [blake3](https://crates.io/crates/blake3) for hashing -- password saved in OS keyring using [keyring](https://crates.io/crates/keyring) -- [tracing](https://crates.io/crates/tracing) for logs - -# Alternatives - -- [Alternatives](https://www.libhunt.com/r/rencfs) -- [Cryptomator](https://cryptomator.org/) -- [gocryptfs](https://nuetzlich.net/gocryptfs/) -- [VeraCrypt](https://www.veracrypt.fr/code/VeraCrypt/?h=NewSysEncWizard) -- [TrueCrypt](https://truecrypt.sourceforge.net/) -- [DroidFS, F-Droid](https://f-droid.org/en/packages/sushi.hardcore.droidfs/) -- [EncFS](https://vgough.github.io/encfs/) and [alternatives](https://alternativeto.net/software/encfs/) -- [CryFS](https://www.cryfs.org/) -- [fscrypt](https://www.kernel.org/doc/html/v4.18/filesystems/fscrypt.html) -- [LUKS, dm-crypt](https://guardianproject.info/archive/luks/) -- [AES Crypt](https://www.aescrypt.com/) -- [Windows BitLocker](https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/) -- [File Lock PEA](https://eck.cologne/peafactory/en/html/file_pea.html) -- [ZenCrypt](https://play.google.com/store/apps/details?id=com.zestas.cryptmyfiles&hl=en) -- [Hat.sh](https://hat.sh/) - -## What separates us - -[Asked](https://chatgpt.com/share/66e7a5a5-d254-8003-9359-9b1556b75fe9) ChatGPT if there are other solutions out there which offer all the key functionalities we do, seems like there are none :) -You can see the [key features](README.md#key-features) that separate us. - -# Usage - - - -## Give it a quick try with Docker - -Get the image - -```bash -docker pull xorio42/rencfs -``` - -Start a container to set up mount in it - -```bash -docker run -v ~/Downloads:/share -it --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined xorio42/rencfs:latest /bin/sh -``` - -**Replace `~/Downloads` with a path you want to share with the container.** - -In the container, create mount and data directories - -```bash -mkdir mnt && mkdir data -``` - -Start `rencfs` - -```bash -rencfs mount --mount-point mnt --data-dir data -l WARN -``` - -Enter a password for encryption. - -Get the container ID - -```bash -docker ps -``` - -In another terminal, attach to the running container with the above ID - -```bash -docker exec -it /bin/sh -``` - -From here, you can play with it by creating files in `mnt` directory - -```bash -cd mnt -mkdir 1 -ls -echo "test" > 1/test -cat 1/test -``` - -You can also copy files from `/share`. - -```bash -cd mnt -cp /share/file1.txt . -file file1.txt -``` - -## As a library - -For the library, you can follow the [documentation](https://docs.rs/rencfs/latest/rencfs/). - -## Command Line Tool - -### Dependencies - -To use the encrypted file system, you need to have FUSE installed on your system. You can install it by running the -following command (or based on your distribution). - -Arch - -```bash -sudo pacman -Syu && sudo pacman -S fuse3 -``` - -Ubuntu - -```bash -sudo apt-get update && sudo apt-get -y install fuse3 -``` - -### Install from AUR - -You can install the encrypted file system binary using the following command - -```bash -yay -Syu && yay -S rencfs -``` - -### Install with cargo - -You can install the encrypted file system binary using the following command - -```bash -cargo install rencfs -``` - -### Usage - -A basic example of how to use the encrypted file system is shown below - -``` -rencfs mount --mount-point MOUNT_POINT --data-dir DATA_DIR -``` - -- `MOUNT_POINT` act as a client, and mount FUSE at the given path -- `DATA_DIR` where to store the encrypted data - with the sync provider. But it needs to be on the same filesystem as the data-dir -It will prompt you to enter a password to encrypt/decrypt the data. -### Change Password -The master encryption key is stored in a file and encrypted with a key derived from the password. -This offers the possibility to change the password without needing to re-encrypt the whole data. This is done by -decrypting the master key with the old password and re-encrypting it with the new password. +# Features -To change the password, you can run the following command +- Key features -```bash -rencfs passwd --data-dir DATA_DIR -``` + Some of these are still being worked on and marked with `[WIP]`. + - `Security` using well-known audited `AEAD` cryptography primitives; + - [WIP] [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48) + - [WIP] [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53) + - `Safely` manage `credentials` in memory with `mlock(2)`, `mprotect`, `zeroize`, and `expiry` to mitigate cold boot attacks; + - `Memory safety`, `performance`, and `optimized` for `concurrency` with Rust; + - Simplicity; + - Encryption key generated from password; + - Password saved in OS's `keyring`; + - `Change password` without re-encrypting all data; + - [WIP] [Generate unique nonce in offline mode](https://github.com/radumarias/rencfs/issues/47) + - [WIP] [Add file inode and chunk index to AAD](https://github.com/radumarias/rencfs/issues/49) This prevents blocks from being copied between or within files by an attacker. + - `Fast seek` on both reads and writes; + - `Writes in parallel`; + - Exposed with `FUSE`; + - Fully `concurrent` for all operations; + - [WIP] [Handle long file names](https://github.com/radumarias/rencfs/issues/47) + - [WIP] [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/radumarias/rencfs/issues/97) + - [WIP] [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/radumarias/rencfs/issues/111) -`DATA_DIR` where the encrypted data is stored +- [Alternatives](docs/Alternatives.md) -It will prompt you to enter the old password and then the new password. -### Encryption info -You can specify the encryption algorithm by adding this argument to the command line +# Implementation -```bash ---cipher CIPHER ... -``` +- [Functionality](docs/Functionality.md) -Where `CIPHER` is the encryption algorithm. You can check the available ciphers with `rencfs --help`. -The default value is `ChaCha20Poly1305`. +- [Stack](docs/Stack.md) -### Log level -You can specify the log level by adding the `--log-level` argument to the command line. Possible -values: `TRACE`, `DEBUG`, `INFO` (default), `WARN`, `ERROR`. -```bash -rencfs --log-level LEVEL ... -``` +# Documentation -## Use it in Rust +- [Docs](docs/) -You can see more [here](https://crates.io/crates/rencfs) - -# Build from source - -## Browser - -If you want to give it a quick try and not setup anything locally, you can -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/radumarias/rencfs) - -[![Open Rustlings On Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/?repo=radumarias%2Frencfs&ref=main) - -You can compile it, run it, and give it a quick try in the browser. After you start it from above - -```bash -apt-get update && apt-get install fuse3 -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -mkdir mnt && mkdir data -cargo run --release -- mount -m mnt -d data -``` - -Open another terminal - -```bash -cd mnt -mkdir a && cd a -echo "test" > test.txt -cat test.txt -``` - -## Locally - -For now, the `FUSE` (`fuse3` crate) only works on `Linux`, so to start the project, you will need to be on Linux. -Instead, you can [Develop inside a Container](#developing-inside-a-container), which will start a local Linux container, the IDE will connect to it, -and you can build and start the app there and also use the terminal to test it. -On Windows, you can start it in [WSL](https://harsimranmaan.medium.com/install-and-setup-rust-development-environment-on-wsl2-dccb4bf63700). - -### Getting the sources - -```bash -git clone git@github.com:radumarias/rencfs.git && cd rencfs -```` - -### Dependencies - -#### Rust - -To build from source, you need to have Rust installed, you can see more details on how to install -it [here](https://www.rust-lang.org/tools/install). - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -```` - -Accordingly, it is customary for Rust developers to include this directory in their `PATH` environment variable. -During installation `rustup` will attempt to configure the `PATH`. Because of differences between platforms, command -shells, -and bugs in `rustup`, the modifications to `PATH` may not take effect until the console is restarted, or the user is -logged out, or it may not succeed at all. - -If, after installation, running `rustc --version` in the console fails, this is the most likely reason. -In that case please add it to the `PATH` manually. - -The project is set up to use the `nightly` toolchain in `rust-toolchain. tool`; on the first build, you will see it fetch the nightly. - -Make sure to add this to your `$PATH` too - -```bash -export PATH="$PATH::$HOME/.cargo/bin" -``` - -```bash -cargo install cargo-aur -cargo install cargo-generate-rpm -``` - -### Other dependencies - -Also, these dependencies are required (or based on your distribution): - -#### Arch - -```bash -sudo pacman -Syu && sudo pacman -S fuse3 base-devel act -``` - -#### Ubuntu - -```bash -sudo apt-get update && sudo apt-get install fuse3 build-essential act -``` - -#### Fedora - -```bash -sudo dnf update && sudo dnf install fuse3 && dnf install @development-tools act -``` - -### Build for debug - -```bash -cargo build -``` - -### Build release - -```bash -cargo build --release -``` - -### Run - -```bash -cargo run --release -- mount --mount-point MOUNT_POINT --data-dir DATA_DIR -``` - -#### Dev settings +[![rencfs](website/resources/layers.png)](website/resources/layers.png) -If you don't want to be prompted for a password, you can set this env var and run it like this: +For detailed description of the various sequence flows please look into [Flows](docs/flows.md). -```bash -RENCFS_PASSWORD=PASS cargo run --release -- mount --mount-point MOUNT_POINT --data-dir DATA_DIR -``` -For dev mode it is recommended to run with `DEBUG` log level: -```bash -cargo run --release -- --log-level DEBUG mount --mount-point MOUNT_POINT --data-dir DATA_DIR -``` + -### Build local RPM for Fedora -This is using [cargo-generate-rpm](https://crates.io/crates/cargo-generate-rpm) -```bash -cargo install cargo-generate-rpm -cargo build --release -cargo generate-rpm -``` +# Usage and Development -The generated RPM will be located here: `target/generate-rpm`. +- [Usage](docs/Usage.md) -#### Install and run local RPM +- [Build from Source](docs/Build_from_Source.md) -```bash -cd target/generate-rpm/ -sudo dnf localinstall rencfs-xxx.x86_64.rpm -``` +- Minimum Supported Rust Version (MSRV).The minimum supported version is `1.75`. -## Developing inside a Container -See here how to configure for [RustRover](https://www.jetbrains.com/help/rust/connect-to-devcontainer.html) and for [VsCode](https://code.visualstudio.com/docs/devcontainers/containers). -You can use the `.devcontainer` directory from the project to start a container with all the necessary tools to build -and run the app. -# Minimum Supported Rust Version (MSRV) -The minimum supported version is `1.75`. +# Next steps -# Future -The plan is to implement it also on macOS and Windows -- **Systemd service** is being worked on [rencfs-daemon](https://github.com/radumarias/rencfs-daemon) -- **GUI** is being worked on [rencfs-desktop](https://github.com/radumarias/rencfs-desktop) and [rencfs-kotlin](https://github.com/radumarias/rencfs-kotlin) -- **Mobile apps** for **Android** and **iOS** are being worked on [rencfs-kotlin](https://github.com/radumarias/rencfs-kotlin) + - The plan is to implement it also on macOS and Windows + - **Systemd service** is being worked on [rencfs-daemon](https://github.com/radumarias/rencfs-daemon) + - **GUI** is being worked on [rencfs-desktop](https://github.com/radumarias/rencfs-desktop) and [rencfs-kotlin](https://github.com/radumarias/rencfs-kotlin) + - **Mobile apps** for **Android** and **iOS** are being worked on [rencfs-kotlin](https://github.com/radumarias/rencfs-kotlin) -# Performance +# Considerations + - Performance -`Aes256Gcm` is slightly faster than `ChaCha20Poly1305` by a factor of **1.28** on average. This is because of the hardware acceleration of AES + `Aes256Gcm` is slightly faster than `ChaCha20Poly1305` by a factor of **1.28** on average. This is because of the hardware acceleration of AES on most CPUs via AES-NI. However, where hardware acceleration is not available, `ChaCha20Poly1305` is faster. Also `ChaChaPoly1305` is better at `SIMD`. -# Cipher comparison - -## AES-GCM vs. ChaCha20-Poly1305 - -- If you have hardware acceleration (e.g. `AES-NI`), then `AES-GCM` provides better performance. On my benchmarks, it was - faster by a factor of **1.28** on average. - If you do not have hardware acceleration, `AES-GCM` is either slower than `ChaCha20-Poly1305`, or it leaks your - encryption - keys in cache timing. -- `AES-GCM` can target multiple security levels (`128-bit`, `192-bit`, `256-bit`), whereas `ChaCha20-Poly1305` is only defined at - the `256-bit` security level. -- Nonce size: - - `AES-GCM`: Varies, but the standard is `96-bit` (`12 bytes`). - If you supply a longer nonce, this gets hashed down to `16 bytes`. - - `ChaCha20-Poly1305`: The standardized version uses `96-bit` nonce (`12 bytes`), but the original used `64-bit` - nonce (`8 bytes`). -- Wear-out of a single (key, nonce) pair: - - `AES-GCM`: Messages must be less than `2^32 – 2` blocks (a.k.a. `2^36 – 32 bytes`, a.k.a. `2^39 – 256-bit`), that's - roughly `64GB`. - This also makes the security analysis of `AES-GCM` with long nonces complicated since the hashed nonce doesn’t - start - with the lower `4 bytes` set to `00 00 00 02`. - - `ChaCha20-Poly1305`: `ChaCha` has an internal counter (`32-bit` in the standardized IETF variant, `64-bit` in the - original design). Max message length is `2^39 - 256-bit`, about `256GB` -- Neither algorithm is **nonce misuse-resistant**. -- `ChaChaPoly1305` is better at `SIMD` - -### Conclusion - -Both are good options. `AES-GCM` can be faster with **hardware support**, but **pure-software** implementations of -`ChaCha20-Poly1305` are almost always **fast** and **constant-time**. - -# ⚠️ Security Warning: Hazmat! - -- **Phantom reads**: Reading older content from a file is not possible. Data is written with WAL and periodically - flushed to file. This ensures data integrity and maintains change order. - One problem that may occur is if we do a truncation, we change the content of the file, but the process is killed before - we write the metadata with the new file size. In this case, the next time we mount the system, we will still see the old -files. However, the content of the file could be bigger, and we read until the old size offset, so we would not - pick up - the new zeros bytes are written on truncating by increasing the size. If content is smaller, the read would stop and - end-of-file of the actual content, so this would not be such a big issue -- **What kind of metadata does it leak**: None, we encrypt filename, content, and metadata and we hide file count, size, and all-time fields -- It's always recommended to use encrypted disks for at least your sensitive data; this project is not a replacement for - that -- To reduce the risk of the encryption key being exposed from memory, it's recommended to disable memory dumps on the - OS level. Please see [here](https://www.cyberciti.biz/faq/disable-core-dumps-in-linux-with-systemd-sysctl/) how to do - it on Linux -- **Cold boot attacks**: to reduce the risk of this, we keep the encryption key in memory just as long as we really - need it to encrypt/decrypt data, and we are zeroing it after that. We also remove it from memory after a period of - inactivity -- Please note that no security expert audited this project. It's built with security in mind and tries to - follow all the best practices, but it's not guaranteed to be secure -- **Also, please back up your data; the project is still in development, and there might be bugs that can lead to data - loss** +- [⚠️ Security ](docs/Security.md) +- [Cipher comparison](docs/Cipher_comparison.md) +- [Others](docs/Considerations.md) -# Considerations -- Please note that this project doesn't try to reinvent the wheel or be better than already proven implementations -- This project doesn't want to be a replacement in any way for already proven file encryption solutions. If you really - want to be close to bulletproof solutions, then maybe this is not the ideal one for you. But is trying to offer a simple use - of an encryption solution that should be used, taking into consideration all the security concerns from above -- It started as a learning project of Rust programming language, and I feel like I keep building more on it -- It's a fairly simple and standard implementation that tries to respect all security standards and correctly use secure and robust - primitives so that it can be extended from this. Indeed, it doesn't have the maturity yet to "fight" other well-known - implementations. - But it can be a project from which others can learn or build upon, or why not for some to actually use it, keeping in - mind all the above # Contribute -Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing +- Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing pull requests are always appreciated. -## How to contribute +- How to contribute + + Please see [CONTRIBUTING.md](CONTRIBUTING.md). + +# Follow us +- Blog and tutorial -Please see [CONTRIBUTING.md](CONTRIBUTING.md). + There will be a [series](https://medium.com/@xorio42/list/828492b94c23) of articles about the evolution of this project, trying to keep it like a tutorial. This is the [first one](https://systemweakness.com/the-hitchhikers-guide-to-building-an-encrypted-filesystem-in-rust-4d678c57d65c). \ No newline at end of file diff --git a/docs/Alternatives.md b/docs/Alternatives.md new file mode 100644 index 00000000..c08bee0d --- /dev/null +++ b/docs/Alternatives.md @@ -0,0 +1,22 @@ +# Alternatives + +- [Alternatives](https://www.libhunt.com/r/rencfs) +- [Cryptomator](https://cryptomator.org/) +- [gocryptfs](https://nuetzlich.net/gocryptfs/) +- [VeraCrypt](https://www.veracrypt.fr/code/VeraCrypt/?h=NewSysEncWizard) +- [TrueCrypt](https://truecrypt.sourceforge.net/) +- [DroidFS, F-Droid](https://f-droid.org/en/packages/sushi.hardcore.droidfs/) +- [EncFS](https://vgough.github.io/encfs/) and [alternatives](https://alternativeto.net/software/encfs/) +- [CryFS](https://www.cryfs.org/) +- [fscrypt](https://www.kernel.org/doc/html/v4.18/filesystems/fscrypt.html) +- [LUKS, dm-crypt](https://guardianproject.info/archive/luks/) +- [AES Crypt](https://www.aescrypt.com/) +- [Windows BitLocker](https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/) +- [File Lock PEA](https://eck.cologne/peafactory/en/html/file_pea.html) +- [ZenCrypt](https://play.google.com/store/apps/details?id=com.zestas.cryptmyfiles&hl=en) +- [Hat.sh](https://hat.sh/) + +- What separates us + + [Asked](https://chatgpt.com/share/66e7a5a5-d254-8003-9359-9b1556b75fe9) ChatGPT if there are other solutions out there which offer all the key functionalities we do, seems like there are none :) +You can see the [key features](README.md#key-features) that separate us. \ No newline at end of file diff --git a/docs/Build_from_Source.md b/docs/Build_from_Source.md new file mode 100644 index 00000000..4b6189cb --- /dev/null +++ b/docs/Build_from_Source.md @@ -0,0 +1,152 @@ +# Build from source + +## Browser + +If you want to give it a quick try and not setup anything locally, you can +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/radumarias/rencfs) + +[![Open Rustlings On Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/?repo=radumarias%2Frencfs&ref=main) + +You can compile it, run it, and give it a quick try in the browser. After you start it from above + +```bash +apt-get update && apt-get install fuse3 +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +mkdir mnt && mkdir data +cargo run --release -- mount -m mnt -d data +``` + +Open another terminal + +```bash +cd mnt +mkdir a && cd a +echo "test" > test.txt +cat test.txt +``` + +## Locally + +For now, the `FUSE` (`fuse3` crate) only works on `Linux`, so to start the project, you will need to be on Linux. +Instead, you can [Develop inside a Container](#developing-inside-a-container), which will start a local Linux container, the IDE will connect to it, +and you can build and start the app there and also use the terminal to test it. +On Windows, you can start it in [WSL](https://harsimranmaan.medium.com/install-and-setup-rust-development-environment-on-wsl2-dccb4bf63700). + +### Getting the sources + +```bash +git clone git@github.com:radumarias/rencfs.git && cd rencfs +```` + +### Dependencies + +#### Rust + +To build from source, you need to have Rust installed, you can see more details on how to install +it [here](https://www.rust-lang.org/tools/install). + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +```` + +Accordingly, it is customary for Rust developers to include this directory in their `PATH` environment variable. +During installation `rustup` will attempt to configure the `PATH`. Because of differences between platforms, command +shells, +and bugs in `rustup`, the modifications to `PATH` may not take effect until the console is restarted, or the user is +logged out, or it may not succeed at all. + +If, after installation, running `rustc --version` in the console fails, this is the most likely reason. +In that case please add it to the `PATH` manually. + +The project is set up to use the `nightly` toolchain in `rust-toolchain. tool`; on the first build, you will see it fetch the nightly. + +Make sure to add this to your `$PATH` too + +```bash +export PATH="$PATH::$HOME/.cargo/bin" +``` + +```bash +cargo install cargo-aur +cargo install cargo-generate-rpm +``` + +### Other dependencies + +Also, these dependencies are required (or based on your distribution): + +#### Arch + +```bash +sudo pacman -Syu && sudo pacman -S fuse3 base-devel act +``` + +#### Ubuntu + +```bash +sudo apt-get update && sudo apt-get install fuse3 build-essential act +``` + +#### Fedora + +```bash +sudo dnf update && sudo dnf install fuse3 && dnf install @development-tools act +``` + +### Build for debug + +```bash +cargo build +``` + +### Build release + +```bash +cargo build --release +``` + +### Run + +```bash +cargo run --release -- mount --mount-point MOUNT_POINT --data-dir DATA_DIR +``` + +#### Dev settings + +If you don't want to be prompted for a password, you can set this env var and run it like this: + +```bash +RENCFS_PASSWORD=PASS cargo run --release -- mount --mount-point MOUNT_POINT --data-dir DATA_DIR +``` + +For dev mode it is recommended to run with `DEBUG` log level: + +```bash +cargo run --release -- --log-level DEBUG mount --mount-point MOUNT_POINT --data-dir DATA_DIR +``` + +### Build local RPM for Fedora + +This is using [cargo-generate-rpm](https://crates.io/crates/cargo-generate-rpm) + +```bash +cargo install cargo-generate-rpm +cargo build --release +cargo generate-rpm +``` + +The generated RPM will be located here: `target/generate-rpm`. + +#### Install and run local RPM + +```bash +cd target/generate-rpm/ +sudo dnf localinstall rencfs-xxx.x86_64.rpm +``` + +## Developing inside a Container + +See here how to configure for [RustRover](https://www.jetbrains.com/help/rust/connect-to-devcontainer.html) and for [VsCode](https://code.visualstudio.com/docs/devcontainers/containers). + +You can use the `.devcontainer` directory from the project to start a container with all the necessary tools to build +and run the app. \ No newline at end of file diff --git a/docs/Cipher_comparison.md b/docs/Cipher_comparison.md new file mode 100644 index 00000000..4681235c --- /dev/null +++ b/docs/Cipher_comparison.md @@ -0,0 +1,31 @@ +# Cipher comparison + +## AES-GCM vs. ChaCha20-Poly1305 + +- If you have hardware acceleration (e.g. `AES-NI`), then `AES-GCM` provides better performance. On my benchmarks, it was + faster by a factor of **1.28** on average. + If you do not have hardware acceleration, `AES-GCM` is either slower than `ChaCha20-Poly1305`, or it leaks your + encryption + keys in cache timing. +- `AES-GCM` can target multiple security levels (`128-bit`, `192-bit`, `256-bit`), whereas `ChaCha20-Poly1305` is only defined at + the `256-bit` security level. +- Nonce size: + - `AES-GCM`: Varies, but the standard is `96-bit` (`12 bytes`). + If you supply a longer nonce, this gets hashed down to `16 bytes`. + - `ChaCha20-Poly1305`: The standardized version uses `96-bit` nonce (`12 bytes`), but the original used `64-bit` + nonce (`8 bytes`). +- Wear-out of a single (key, nonce) pair: + - `AES-GCM`: Messages must be less than `2^32 – 2` blocks (a.k.a. `2^36 – 32 bytes`, a.k.a. `2^39 – 256-bit`), that's + roughly `64GB`. + This also makes the security analysis of `AES-GCM` with long nonces complicated since the hashed nonce doesn’t + start + with the lower `4 bytes` set to `00 00 00 02`. + - `ChaCha20-Poly1305`: `ChaCha` has an internal counter (`32-bit` in the standardized IETF variant, `64-bit` in the + original design). Max message length is `2^39 - 256-bit`, about `256GB` +- Neither algorithm is **nonce misuse-resistant**. +- `ChaChaPoly1305` is better at `SIMD` + +### Conclusion + +Both are good options. `AES-GCM` can be faster with **hardware support**, but **pure-software** implementations of +`ChaCha20-Poly1305` are almost always **fast** and **constant-time**. \ No newline at end of file diff --git a/docs/Considerations.md b/docs/Considerations.md new file mode 100644 index 00000000..4b6f680d --- /dev/null +++ b/docs/Considerations.md @@ -0,0 +1,12 @@ +# Considerations + +- Please note that this project doesn't try to reinvent the wheel or be better than already proven implementations +- This project doesn't want to be a replacement in any way for already proven file encryption solutions. If you really + want to be close to bulletproof solutions, then maybe this is not the ideal one for you. But is trying to offer a simple use + of an encryption solution that should be used, taking into consideration all the security concerns from above +- It started as a learning project of Rust programming language, and I feel like I keep building more on it +- It's a fairly simple and standard implementation that tries to respect all security standards and correctly use secure and robust + primitives so that it can be extended from this. Indeed, it doesn't have the maturity yet to "fight" other well-known + implementations. + But it can be a project from which others can learn or build upon, or why not for some to actually use it, keeping in + mind all the above \ No newline at end of file diff --git a/docs/Functionality.md b/docs/Functionality.md new file mode 100644 index 00000000..2d814f07 --- /dev/null +++ b/docs/Functionality.md @@ -0,0 +1,20 @@ +# Functionality + +Some of these are still being worked on and marked with `[WIP]`. + +- It keeps all `encrypted` data and `master encryption key` in a dedicated directory with files structured on `inodes` (with + metadata info), files for binary content, and directories with files/directories entries. All data, metadata, and filenames + are encrypted. It generates unique inodes for new files in a multi-instance run and offline mode. +- The password is collected from CLI and saved in the OS's `keyring` while the app runs. This is because, for security concerns, we + clear the password from memory on inactivity, and we derive it again from the password just when needed. +- Master encryption key is also encrypted with another key derived from the password. This gives the ability to change + the + password without re-encrypting all data, we just `re-encrypt` the `master key`. +- Files are `encrypted` in `chunks` of `256KB`, so when making a change, we just re-encrypt that chunks. +- `Fast seek` on read and write, so if you're watching a movie, you can seek any position, and that would be instant. + This is because we can seek a particular chunk. +- The encryption key is `zeroize` in the mem when disposing and idle. Also, it's `mlock`ed while used to prevent being moved to swap. It's + also `mprotect`ed while not in use. +- `[WIP]` Ensure file integrity by saving each change to WAL, so for crashes or power loss, we apply the pending +changes at the next start. This makes the write operations atomic. +- Multiple writes in parallel to the same file, ideal for torrent-like applications. \ No newline at end of file diff --git a/docs/Key_features.md b/docs/Key_features.md new file mode 100644 index 00000000..ded8589c --- /dev/null +++ b/docs/Key_features.md @@ -0,0 +1,21 @@ +## Key features + +Some of these are still being worked on and marked with `[WIP]`. +- `Security` using well-known audited `AEAD` cryptography primitives; +- [WIP] [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48) +- [WIP] [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53) +- `Safely` manage `credentials` in memory with `mlock(2)`, `mprotect`, `zeroize`, and `expiry` to mitigate cold boot attacks; +- `Memory safety`, `performance`, and `optimized` for `concurrency` with Rust; +- Simplicity; +- Encryption key generated from password; +- Password saved in OS's `keyring`; +- `Change password` without re-encrypting all data; +- [WIP] [Generate unique nonce in offline mode](https://github.com/radumarias/rencfs/issues/47) +- [WIP] [Add file inode and chunk index to AAD](https://github.com/radumarias/rencfs/issues/49) This prevents blocks from being copied between or within files by an attacker. +- `Fast seek` on both reads and writes; +- `Writes in parallel`; +- Exposed with `FUSE`; +- Fully `concurrent` for all operations; +- [WIP] [Handle long file names](https://github.com/radumarias/rencfs/issues/47) +- [WIP] [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/radumarias/rencfs/issues/97) +- [WIP] [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/radumarias/rencfs/issues/111) \ No newline at end of file diff --git a/docs/Security.md b/docs/Security.md new file mode 100644 index 00000000..7c71fd89 --- /dev/null +++ b/docs/Security.md @@ -0,0 +1,23 @@ +# ⚠️ Security Warning: Hazmat! + +- **Phantom reads**: Reading older content from a file is not possible. Data is written with WAL and periodically + flushed to file. This ensures data integrity and maintains change order. + One problem that may occur is if we do a truncation, we change the content of the file, but the process is killed before + we write the metadata with the new file size. In this case, the next time we mount the system, we will still see the old +files. However, the content of the file could be bigger, and we read until the old size offset, so we would not + pick up + the new zeros bytes are written on truncating by increasing the size. If content is smaller, the read would stop and + end-of-file of the actual content, so this would not be such a big issue +- **What kind of metadata does it leak**: None, we encrypt filename, content, and metadata and we hide file count, size, and all-time fields +- It's always recommended to use encrypted disks for at least your sensitive data; this project is not a replacement for + that +- To reduce the risk of the encryption key being exposed from memory, it's recommended to disable memory dumps on the + OS level. Please see [here](https://www.cyberciti.biz/faq/disable-core-dumps-in-linux-with-systemd-sysctl/) how to do + it on Linux +- **Cold boot attacks**: to reduce the risk of this, we keep the encryption key in memory just as long as we really + need it to encrypt/decrypt data, and we are zeroing it after that. We also remove it from memory after a period of + inactivity +- Please note that no security expert audited this project. It's built with security in mind and tries to + follow all the best practices, but it's not guaranteed to be secure +- **Also, please back up your data; the project is still in development, and there might be bugs that can lead to data + loss** \ No newline at end of file diff --git a/docs/Stack.md b/docs/Stack.md new file mode 100644 index 00000000..b58d62b1 --- /dev/null +++ b/docs/Stack.md @@ -0,0 +1,12 @@ +# Stack + +- it's fully async built upon [tokio](https://crates.io/crates/tokio) and [fuse3](https://crates.io/crates/fuse3) +- [ring](https://crates.io/crates/ring) for encryption and [argon2](https://crates.io/crates/argon2) for key derivation + function (generating key from password used to encrypt the master encryption key) +- [rand_chacha](https://crates.io/crates/rand_chacha) for random generators +- [shush-rs](https://crates.io/crates/shush-rs) keeps pass and encryption keys safe in memory and zero them when + not used. It keeps encryption keys in memory only while being used, and when not active, it will release and zeroing + them in memory. It locks the memory page as well, preventing it from being written to swap. +- [blake3](https://crates.io/crates/blake3) for hashing +- password saved in OS keyring using [keyring](https://crates.io/crates/keyring) +- [tracing](https://crates.io/crates/tracing) for logs \ No newline at end of file diff --git a/docs/Usage.md b/docs/Usage.md new file mode 100644 index 00000000..bbc69dff --- /dev/null +++ b/docs/Usage.md @@ -0,0 +1,160 @@ +# Usage + + + +## Give it a quick try with Docker + +Get the image + +```bash +docker pull xorio42/rencfs +``` + +Start a container to set up mount in it + +```bash +docker run -v ~/Downloads:/share -it --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined xorio42/rencfs:latest /bin/sh +``` + +**Replace `~/Downloads` with a path you want to share with the container.** + +In the container, create mount and data directories + +```bash +mkdir mnt && mkdir data +``` + +Start `rencfs` + +```bash +rencfs mount --mount-point mnt --data-dir data -l WARN +``` + +Enter a password for encryption. + +Get the container ID + +```bash +docker ps +``` + +In another terminal, attach to the running container with the above ID + +```bash +docker exec -it /bin/sh +``` + +From here, you can play with it by creating files in `mnt` directory + +```bash +cd mnt +mkdir 1 +ls +echo "test" > 1/test +cat 1/test +``` + +You can also copy files from `/share`. + +```bash +cd mnt +cp /share/file1.txt . +file file1.txt +``` + +## As a library + +For the library, you can follow the [documentation](https://docs.rs/rencfs/latest/rencfs/). + +## Command Line Tool + +### Dependencies + +To use the encrypted file system, you need to have FUSE installed on your system. You can install it by running the +following command (or based on your distribution). + +Arch + +```bash +sudo pacman -Syu && sudo pacman -S fuse3 +``` + +Ubuntu + +```bash +sudo apt-get update && sudo apt-get -y install fuse3 +``` + +### Install from AUR + +You can install the encrypted file system binary using the following command + +```bash +yay -Syu && yay -S rencfs +``` + +### Install with cargo + +You can install the encrypted file system binary using the following command + +```bash +cargo install rencfs +``` + +### Usage + +A basic example of how to use the encrypted file system is shown below + +``` +rencfs mount --mount-point MOUNT_POINT --data-dir DATA_DIR +``` + +- `MOUNT_POINT` act as a client, and mount FUSE at the given path +- `DATA_DIR` where to store the encrypted data + with the sync provider. But it needs to be on the same filesystem as the data-dir + +It will prompt you to enter a password to encrypt/decrypt the data. + +### Change Password + +The master encryption key is stored in a file and encrypted with a key derived from the password. +This offers the possibility to change the password without needing to re-encrypt the whole data. This is done by +decrypting the master key with the old password and re-encrypting it with the new password. + +To change the password, you can run the following command + +```bash +rencfs passwd --data-dir DATA_DIR +``` + +`DATA_DIR` where the encrypted data is stored + +It will prompt you to enter the old password and then the new password. + +### Encryption info + +You can specify the encryption algorithm by adding this argument to the command line + +```bash +--cipher CIPHER ... +``` + +Where `CIPHER` is the encryption algorithm. You can check the available ciphers with `rencfs --help`. +The default value is `ChaCha20Poly1305`. + +### Log level + +You can specify the log level by adding the `--log-level` argument to the command line. Possible +values: `TRACE`, `DEBUG`, `INFO` (default), `WARN`, `ERROR`. + +```bash +rencfs --log-level LEVEL ... +``` + +## Use it in Rust + +You can see more [here](https://crates.io/crates/rencfs) \ No newline at end of file diff --git a/java-bridge/src/lib.rs b/java-bridge/src/lib.rs index 030f5810..7090fd76 100644 --- a/java-bridge/src/lib.rs +++ b/java-bridge/src/lib.rs @@ -13,6 +13,7 @@ use rencfs::crypto::Cipher; use rencfs::encryptedfs::PasswordProvider; use rencfs::log::log_init; use rencfs::mount::{create_mount_point, umount, MountHandle}; +use secrets::SecretVec; use shush_rs::SecretString; use std::collections::BTreeMap; use std::ops::Add; @@ -119,6 +120,8 @@ pub extern "system" fn Java_RustLibrary_mount( let mount_path: String = env.get_string(&mnt).unwrap().into(); let data_dir_path: String = env.get_string(&data_dir).unwrap().into(); let password: String = env.get_string(&password).unwrap().into(); + let new_pass = SecretVec::::new(password.into_bytes()); // create new pass using secretvec + // drop(password); // drop password after info!("mount_path: {}", mount_path); info!("data_dir_path: {}", data_dir_path); @@ -171,21 +174,24 @@ pub extern "system" fn Java_RustLibrary_mount( }); } - struct PasswordProviderImpl(String); + struct PasswordProviderImpl(SecretVec); // use secretvec instead of string impl PasswordProvider for PasswordProviderImpl { fn get_password(&self) -> Option { - Some(SecretString::from_str(&self.0).unwrap()) + let password_str = String::from_utf8_lossy(self.0.expose_secret()); + Some(SecretString::from_str(&password_str).unwrap()) } } + let mount_point = create_mount_point( Path::new(&mount_path), Path::new(&data_dir_path), - Box::new(PasswordProviderImpl(password)), + Box::new(PasswordProviderImpl(new_pass)), // use the pass one time Cipher::ChaCha20Poly1305, false, false, false, ); + drop(new_pass); // drop pass after use let handle = match RT.block_on(async { match mount_point.mount().await { diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 31b1448b..8185b4b8 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -657,6 +657,17 @@ impl EncryptedFs { self.read_only } + fn validate_filename(&self, secret_filename: &SecretBox) -> FsResult<()> { + let filename = secret_filename.expose_secret().to_string(); + if filename.contains('/') { + Err(FsError::InvalidInput("'/' not allowed in the filename")) + } else if filename.contains('\\') { + Err(FsError::InvalidInput("'\\' not allowed in the filename")) + } else { + Ok(()) + } + } + /// Create a new node in the filesystem #[allow(clippy::missing_panics_doc)] #[allow(clippy::missing_errors_doc)] @@ -681,6 +692,7 @@ impl EncryptedFs { if self.exists_by_name(parent, name)? { return Err(FsError::AlreadyExists); } + self.validate_filename(&name)?; // spawn on a dedicated runtime to not interfere with other higher priority tasks let self_clone = self @@ -966,6 +978,7 @@ impl EncryptedFs { if !matches!(attr.kind, FileType::RegularFile) { return Err(FsError::InvalidInodeType); } + // todo move to method let self_clone = self .self_weak .lock() @@ -1150,6 +1163,9 @@ impl EncryptedFs { } } }; + + self.validate_filename(&name)?; + let file_path = entry.path().to_str().unwrap().to_owned(); // try from cache let lock = self.dir_entries_meta_cache.get().await?; @@ -1998,6 +2014,7 @@ impl EncryptedFs { if !self.exists_by_name(parent, name)? { return Err(FsError::NotFound("name not found")); } + self.validate_filename(&new_name)?; if parent == new_parent && name.expose_secret() == new_name.expose_secret() { // no-op From 8ae419078d54bfcd86488e30ada922ecb233c977 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 22:28:44 +0200 Subject: [PATCH 054/150] Update Alternatives.md --- docs/Alternatives.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Alternatives.md b/docs/Alternatives.md index c08bee0d..dd2d48c9 100644 --- a/docs/Alternatives.md +++ b/docs/Alternatives.md @@ -16,7 +16,7 @@ - [ZenCrypt](https://play.google.com/store/apps/details?id=com.zestas.cryptmyfiles&hl=en) - [Hat.sh](https://hat.sh/) -- What separates us +## What separates us - [Asked](https://chatgpt.com/share/66e7a5a5-d254-8003-9359-9b1556b75fe9) ChatGPT if there are other solutions out there which offer all the key functionalities we do, seems like there are none :) -You can see the [key features](README.md#key-features) that separate us. \ No newline at end of file +[Asked](https://chatgpt.com/share/66e7a5a5-d254-8003-9359-9b1556b75fe9) ChatGPT if there are other solutions out there which offer all the key functionalities we do, seems like there are none :) +You can see the [key features](README.md#key-features) that separate us. From 1a51640c299b57bf53fc202fa703b040b7608957 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 22:38:02 +0200 Subject: [PATCH 055/150] Update README.md --- README.md | 121 +++++++++++++++++------------------------------------- 1 file changed, 37 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 54733840..b470571e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ [![codecov](https://codecov.io/gh/radumarias/rencfs/graph/badge.svg?token=NUQI6XGF2Y)](https://codecov.io/gh/radumarias/rencfs) [![Open Source Helpers](https://www.codetriage.com/radumarias/rencfs/badges/users.svg)](https://www.codetriage.com/radumarias/rencfs) - > [!WARNING] > **This crate hasn't been audited; it's using `ring` crate, which is a well-known audited library, so in principle, at @@ -20,135 +19,89 @@ stable release. An encrypted file system written in Rust that is mounted with FUSE on Linux. It can be used to create encrypted directories. You can then safely back up the encrypted directory to an untrusted server without worrying about the data being exposed. -You can also store it in a cloud storage service like Google Drive, Dropbox, etc., and have it synced across multiple devices. +You can also store it in a cloud storage service like Google Drive, Dropbox, etc., and sync it across multiple devices. You can use it as CLI or as a library to build your custom FUSE implementation or other apps that work with encrypted data. - - - - - - - - # Introduction - Motivation - Create a `simple,` `performant,` `modular` and `ergonomic` yet `very secure` `encrypted filesystem` to protect your `privacy`, which is also `open source` and is correctly and safely using `well-known audited` crates as `cryptographic primitives.` - - A short story - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust_2.pdf) - - - Talks - - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) [@meetup.com/star-tech-rd-reloaded](https://www.meetup.com/star-tech-rd-reloaded/) and [@OmniOpenCon](https://omniopencon.org/) - - [Basics of cryptography, Authenticated Encryption, Rust in cryptography and how to build an encrypted filesystem](https://www.youtube.com/live/HwmVxOl3pQg) @ITDays and [slides](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323). - - - Crate of the week in [This Week in Rust](https://this-week-in-rust.org/blog/2024/08/07/this-week-in-rust-559/#cfp-projects) - - It was [crate of the week](https://this-week-in-rust.org/blog/2024/08/14/this-week-in-rust-560/#crate-of-the-week) in Aug 2024. - - - -# Features - -- Key features - - Some of these are still being worked on and marked with `[WIP]`. - - `Security` using well-known audited `AEAD` cryptography primitives; - - [WIP] [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48) - - [WIP] [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53) - - `Safely` manage `credentials` in memory with `mlock(2)`, `mprotect`, `zeroize`, and `expiry` to mitigate cold boot attacks; - - `Memory safety`, `performance`, and `optimized` for `concurrency` with Rust; - - Simplicity; - - Encryption key generated from password; - - Password saved in OS's `keyring`; - - `Change password` without re-encrypting all data; - - [WIP] [Generate unique nonce in offline mode](https://github.com/radumarias/rencfs/issues/47) - - [WIP] [Add file inode and chunk index to AAD](https://github.com/radumarias/rencfs/issues/49) This prevents blocks from being copied between or within files by an attacker. - - `Fast seek` on both reads and writes; - - `Writes in parallel`; - - Exposed with `FUSE`; - - Fully `concurrent` for all operations; - - [WIP] [Handle long file names](https://github.com/radumarias/rencfs/issues/47) - - [WIP] [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/radumarias/rencfs/issues/97) - - [WIP] [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/radumarias/rencfs/issues/111) - -- [Alternatives](docs/Alternatives.md) - - +# Key features + +Some of these are still being worked on and marked with `[WIP]`. + - `Security` using well-known audited `AEAD` cryptography primitives; + - `[WIP]` [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48) + - `[WIP]` [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53) + - `Safely` manage `credentials` in memory with `mlock(2)`, `mprotect`, `zeroize`, and `expiry` to mitigate cold boot attacks; + - `Memory safety`, `performance`, and `optimized` for `concurrency` with Rust; + - Simplicity; + - Encryption key generated from password; +- Password saved in OS's `keyring`; +- `Change password` without re-encrypting all data; +- `[WIP]` [Generate unique nonce in offline mode](https://github.com/radumarias/rencfs/issues/47) + - `[WIP]` [Add file inode and chunk index to AAD](https://github.com/radumarias/rencfs/issues/49) This prevents blocks from being copied between or within files by an attacker. +- `Fast seek` on both reads and writes; +- `Writes in parallel`; +- Exposed with `FUSE`; +- Fully `concurrent` for all operations; +- `[WIP]` [Handle long file names](https://github.com/radumarias/rencfs/issues/47) +- `[WIP]` [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/radumarias/rencfs/issues/97) +- `[WIP]` [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/radumarias/rencfs/issues/111) + +# [Alternatives](docs/Alternatives.md) # Implementation - [Functionality](docs/Functionality.md) - - [Stack](docs/Stack.md) - - # Documentation - [Docs](docs/) [![rencfs](website/resources/layers.png)](website/resources/layers.png) -For detailed description of the various sequence flows please look into [Flows](docs/flows.md). - - - - - - +Please look into [Flows](docs/flows.md) for a detailed description of the various sequence flows. # Usage and Development - [Usage](docs/Usage.md) - - [Build from Source](docs/Build_from_Source.md) - -- Minimum Supported Rust Version (MSRV).The minimum supported version is `1.75`. - - - - +- Minimum Supported Rust Version (MSRV). The minimum supported version is `1.75`. # Next steps - - - The plan is to implement it also on macOS and Windows - - **Systemd service** is being worked on [rencfs-daemon](https://github.com/radumarias/rencfs-daemon) - - **GUI** is being worked on [rencfs-desktop](https://github.com/radumarias/rencfs-desktop) and [rencfs-kotlin](https://github.com/radumarias/rencfs-kotlin) - - **Mobile apps** for **Android** and **iOS** are being worked on [rencfs-kotlin](https://github.com/radumarias/rencfs-kotlin) + - The plan is to implement it also on macOS and Windows + - **Systemd service** is being worked on [rencfs-daemon](https://github.com/radumarias/rencfs-daemon) + - **GUI** is being worked on [rencfs-desktop](https://github.com/radumarias/rencfs-desktop) and [ciphershell-kotlin](https://github.com/radumarias/ciphershell-kotlin) + - **Mobile apps** for **Android** and **iOS** are being worked on [ciphershell-kotlin](https://github.com/radumarias/ciphershell-kotlin) # Considerations - - Performance - - `Aes256Gcm` is slightly faster than `ChaCha20Poly1305` by a factor of **1.28** on average. This is because of the hardware acceleration of AES -on most CPUs via AES-NI. However, where hardware acceleration is not available, `ChaCha20Poly1305` is faster. Also `ChaChaPoly1305` is better at `SIMD`. +- Performance + `Aes256Gcm` is slightly faster than `ChaCha20Poly1305` by an average factor of **1.28**. This is because of the hardware acceleration of AES +on most CPUs via AES-NI. However, where hardware acceleration is unavailable, `ChaCha20Poly1305` is faster. Also `ChaChaPoly1305` is better at `SIMD`. - [⚠️ Security ](docs/Security.md) - [Cipher comparison](docs/Cipher_comparison.md) - [Others](docs/Considerations.md) - - # Contribute -- Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing -pull requests are always appreciated. +Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing pull requests are always appreciated. - How to contribute - - Please see [CONTRIBUTING.md](CONTRIBUTING.md). + Please see [CONTRIBUTING.md](CONTRIBUTING.md). # Follow us -- Blog and tutorial - There will be a [series](https://medium.com/@xorio42/list/828492b94c23) of articles about the evolution of this project, trying to keep it like a tutorial. This is the [first one](https://systemweakness.com/the-hitchhikers-guide-to-building-an-encrypted-filesystem-in-rust-4d678c57d65c). \ No newline at end of file +- Blog and tutorial + There will be a [series](https://medium.com/@xorio42/list/828492b94c23) of articles about the evolution of this project, trying to keep it like a tutorial. This is the [first one](https://systemweakness.com/the-hitchhikers-guide-to-building-an-encrypted-filesystem-in-rust-4d678c57d65c). From 30e42bfce8eaf2b21b1aac72d8c26986c8372fbb Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 23:01:34 +0200 Subject: [PATCH 056/150] fix tests --- src/encryptedfs/test.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/encryptedfs/test.rs b/src/encryptedfs/test.rs index 6816029b..81e33d05 100644 --- a/src/encryptedfs/test.rs +++ b/src/encryptedfs/test.rs @@ -638,7 +638,7 @@ async fn test_read_dir() { .await .unwrap(); - let test_file_3 = SecretString::from_str("test/file/3").unwrap(); + let test_file_3 = SecretString::from_str("test_file_3").unwrap(); let (_fh, file_attr_2) = fs .create( parent, @@ -650,7 +650,7 @@ async fn test_read_dir() { .await .unwrap(); - let test_dir_2 = SecretString::from_str("test\\dir//2").unwrap(); + let test_dir_2 = SecretString::from_str("test_dir_2").unwrap(); let (_fh, dir_attr) = fs .create( parent, @@ -981,7 +981,7 @@ async fn test_exists_by_name() { async { let fs = get_fs().await; - for file in ["test-file", "test//\\file"] { + for file in ["test-file", "test--file"] { let test_file = SecretString::from_str(file).unwrap(); let _ = fs .create( @@ -1016,7 +1016,7 @@ async fn test_remove_dir() { }, async { let fs = get_fs().await; - for dir in ["test-dir", "test-dir\\", "test-dir/"] { + for dir in ["test-dir", "test-dir_", "test-dir-"] { let test_dir = SecretString::from_str(dir).unwrap(); let _ = fs .create( @@ -1061,7 +1061,7 @@ async fn test_remove_file() { async { let fs = get_fs().await; - for dir in ["test-dir", "test-dir\\", "test-dir/"] { + for dir in ["test-dir", "test-dir_", "test-dir-clear"] { let test_file = SecretString::from_str(dir).unwrap(); let _ = fs .create( @@ -1121,7 +1121,7 @@ async fn test_find_by_name_exists_by_name100files() { .unwrap(); } - let special_test_file = SecretString::from_str("test//\\file").unwrap(); + let special_test_file = SecretString::from_str("test-_file").unwrap(); let _ = fs .create( ROOT_INODE, @@ -1591,7 +1591,7 @@ async fn test_rename() { ) .await .unwrap(); - let dir_2 = SecretString::from_str("dir-\\2").unwrap(); + let dir_2 = SecretString::from_str("dir-_2").unwrap(); fs.rename(ROOT_INODE, &dir_1, new_parent, &dir_2) .await .unwrap(); @@ -1663,7 +1663,7 @@ async fn test_rename() { // file to existing file in same directory let file_1 = SecretString::from_str("file-1").unwrap(); - let file_2 = SecretString::from_str("file-/2").unwrap(); + let file_2 = SecretString::from_str("file--2").unwrap(); let new_parent = ROOT_INODE; let (_, attr) = fs .create( @@ -2374,8 +2374,7 @@ async fn test_read_only_write() { let cipher = Cipher::ChaCha20Poly1305; let file1 = SecretString::from_str("file1").unwrap(); let file_dest = SecretString::from_str("file_dest").unwrap(); - let dir1 = data_dir.clone().join("dir1"); - let dir1 = SecretString::from_str(dir1.to_str().unwrap()).unwrap(); + let dir1 = SecretString::from_str("dir1").unwrap(); let data = "Hello, world!"; let (fh, attr) = fs_rw From 8b372b3d31d0727cb3831c5d112be4b68b955741 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 23:03:04 +0200 Subject: [PATCH 057/150] move docs --- CONTRIBUTING.md | 5 +- Dockerfile_from_scratch | 53 ---------------- README.md | 85 ++++++++++++++++---------- Ramp-up.md | 14 ----- docs/flows.md | 22 ------- docs/{ => readme}/Alternatives.md | 0 docs/{ => readme}/Build_from_Source.md | 0 docs/{ => readme}/Cipher_comparison.md | 0 docs/{ => readme}/Considerations.md | 0 docs/{ => readme}/Functionality.md | 0 docs/{ => readme}/Key_features.md | 0 docs/readme/Ramp-up.md | 30 +++++++++ docs/{ => readme}/Security.md | 0 docs/{ => readme}/Stack.md | 0 docs/{ => readme}/Usage.md | 0 docs/readme/flows.md | 22 +++++++ docs/uml/lib_encryptedfs_usage.md | 56 ++++++++--------- 17 files changed, 135 insertions(+), 152 deletions(-) delete mode 100644 Dockerfile_from_scratch delete mode 100644 Ramp-up.md delete mode 100644 docs/flows.md rename docs/{ => readme}/Alternatives.md (100%) rename docs/{ => readme}/Build_from_Source.md (100%) rename docs/{ => readme}/Cipher_comparison.md (100%) rename docs/{ => readme}/Considerations.md (100%) rename docs/{ => readme}/Functionality.md (100%) rename docs/{ => readme}/Key_features.md (100%) create mode 100644 docs/readme/Ramp-up.md rename docs/{ => readme}/Security.md (100%) rename docs/{ => readme}/Stack.md (100%) rename docs/{ => readme}/Usage.md (100%) create mode 100644 docs/readme/flows.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7baaca43..c01d18e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ Unless you explicitly state otherwise, any Contribution intentionally submitted defined in the Apache License shall be dual-licensed as above, without any additional terms or conditions. 1. Join [slack](https://bit.ly/3UU1oXi) and join `#dev-beginners` channel -2. Become familiar with docs and code by reading the [ramp-up](Ramp-up.md) guide +2. Become familiar with docs and code by reading the [ramp-up](docs/readme/Ramp-up.md) guide 3. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and be able to create your own branches and not need to fork the repo 4. Pick an open issue or a task in the corresponding [project](https://github.com/users/radumarias/projects/1) for the @@ -24,7 +24,8 @@ defined in the Apache License shall be dual-licensed as above, without any addit and [VSCode](https://code.visualstudio.com/docs/languages/rust#_formatting) 10. `cargo clippy --all --release` and fix any errors 11. **DON'T INCREASE THE VERSION NUMBER IN `Cargo.toml`, WE WILL DO THAN WHEN RELEASING** -12. Create a `git` `push hook` file in `.git/hooks/pre-push` with [pre-push](scripts/git-hooks/linux-macos/pre-push) content +12. Create a `git` `push hook` file in `.git/hooks/pre-push` with [pre-push](scripts/git-hooks/linux-macos/pre-push) + content on `Linux` and `macOS`, and [pre-push](scripts/git-hooks/windows/pre-push) on `Windows`. Make it executable in Linux and macOS with `chmod +x .git/hooks/pre-push` .This will run when you do `git push` and will make the push quite diff --git a/Dockerfile_from_scratch b/Dockerfile_from_scratch deleted file mode 100644 index 018a3de3..00000000 --- a/Dockerfile_from_scratch +++ /dev/null @@ -1,53 +0,0 @@ -################ -##### Builder -FROM alpine:3.19.1 AS builder - -RUN apk update && apk upgrade && apk add binutils build-base ca-certificates curl file g++ gcc make patch fuse3 - -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - -RUN . ~/.cargo/env && rustup target add x86_64-unknown-linux-musl - -RUN . ~/.cargo/env && rustup default nightly && rustup update - -# Cache downloaded+built dependencies -#COPY Cargo.toml Cargo.lock /usr/src/rencfs/ -#RUN mkdir /usr/src/rencfs/src && \ -# echo 'fn main() {}' > /usr/src/rencfs/src/main.rs -# -#RUN . ~/.cargo/env && cd /usr/src/rencfs/ && cargo build --release && \ -# rm -Rvf /usr/src/rencfs/src - -# Build our actual code -#COPY Cargo.toml Cargo.lock /usr/src/rencfs/ -#COPY src /usr/src/rencfs -COPY . /usr/src/rencfs -#COPY examples /usr/src/rencfs/examples -RUN . ~/.cargo/env && \ - cd /usr/src/rencfs/ && \ - cargo build --target x86_64-unknown-linux-musl --release - -#Copy the fusermount3 binary and libraries into a directory -RUN mkdir /fusermount3dep && \ - cp $(which fusermount3) /fusermount3dep/ && \ - ldd $(which fusermount3) | awk '{ print $3 }' | xargs -I {} cp {} /fusermount3dep/ - - -################ -##### Runtime -FROM scratch AS runtime - -# Copy fusermount3 -COPY --from=builder /fusermount3dep/fusermount3 /usr/bin/ - -# Copy busybox -COPY --from=builder /bin/ /bin/ - -# Copy ld-musl (fusermount3 & busybox dep) -COPY --from=builder /fusermount3dep/ld* /lib/ - -# Copy application binary from builder image -COPY --from=builder /usr/src/rencfs/target/x86_64-unknown-linux-musl/release/rencfs /usr/bin/ - -# Run the application -CMD ["rencfs", "--help"] diff --git a/README.md b/README.md index b470571e..64f011f9 100644 --- a/README.md +++ b/README.md @@ -16,39 +16,51 @@ least the primitives should offer a similar level of security. stable release. > It's mostly ideal for experimental and learning projects.** -An encrypted file system written in Rust that is mounted with FUSE on Linux. It can be used to create encrypted directories. +An encrypted file system written in Rust that is mounted with FUSE on Linux. It can be used to create encrypted +directories. -You can then safely back up the encrypted directory to an untrusted server without worrying about the data being exposed. +You can then safely back up the encrypted directory to an untrusted server without worrying about the data being +exposed. You can also store it in a cloud storage service like Google Drive, Dropbox, etc., and sync it across multiple devices. -You can use it as CLI or as a library to build your custom FUSE implementation or other apps that work with encrypted data. +You can use it as CLI or as a library to build your custom FUSE implementation or other apps that work with encrypted +data. # Introduction - Motivation - Create a `simple,` `performant,` `modular` and `ergonomic` yet `very secure` `encrypted filesystem` to protect your `privacy`, which is also `open source` and is correctly and safely using `well-known audited` crates as `cryptographic primitives.` + Create a `simple,` `performant,` `modular` and `ergonomic` yet `very secure` `encrypted filesystem` to protect + your `privacy`, which is also `open source` and is correctly and safely using `well-known audited` crates + as `cryptographic primitives.` - A short story [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust_2.pdf) - Talks - - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) [@meetup.com/star-tech-rd-reloaded](https://www.meetup.com/star-tech-rd-reloaded/) and [@OmniOpenCon](https://omniopencon.org/) - - [Basics of cryptography, Authenticated Encryption, Rust in cryptography and how to build an encrypted filesystem](https://www.youtube.com/live/HwmVxOl3pQg) @ITDays and [slides](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323). - - Crate of the week in [This Week in Rust](https://this-week-in-rust.org/blog/2024/08/07/this-week-in-rust-559/#cfp-projects) -- It was [crate of the week](https://this-week-in-rust.org/blog/2024/08/14/this-week-in-rust-560/#crate-of-the-week) in Aug 2024. + - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) [@meetup.com/star-tech-rd-reloaded](https://www.meetup.com/star-tech-rd-reloaded/) + and [@OmniOpenCon](https://omniopencon.org/) + - [Basics of cryptography, Authenticated Encryption, Rust in cryptography and how to build an encrypted filesystem](https://www.youtube.com/live/HwmVxOl3pQg) + @ITDays and [slides](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323). + - Crate of the week + in [This Week in Rust](https://this-week-in-rust.org/blog/2024/08/07/this-week-in-rust-559/#cfp-projects) +- It was [crate of the week](https://this-week-in-rust.org/blog/2024/08/14/this-week-in-rust-560/#crate-of-the-week) in + Aug 2024. # Key features Some of these are still being worked on and marked with `[WIP]`. - - `Security` using well-known audited `AEAD` cryptography primitives; - - `[WIP]` [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48) - - `[WIP]` [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53) - - `Safely` manage `credentials` in memory with `mlock(2)`, `mprotect`, `zeroize`, and `expiry` to mitigate cold boot attacks; - - `Memory safety`, `performance`, and `optimized` for `concurrency` with Rust; - - Simplicity; - - Encryption key generated from password; + +- `Security` using well-known audited `AEAD` cryptography primitives; +- `[WIP]` [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48) +- `[WIP]` [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53) +- `Safely` manage `credentials` in memory with `mlock(2)`, `mprotect`, `zeroize`, and `expiry` to mitigate cold boot + attacks; +- `Memory safety`, `performance`, and `optimized` for `concurrency` with Rust; +- Simplicity; +- Encryption key generated from password; - Password saved in OS's `keyring`; - `Change password` without re-encrypting all data; - `[WIP]` [Generate unique nonce in offline mode](https://github.com/radumarias/rencfs/issues/47) - - `[WIP]` [Add file inode and chunk index to AAD](https://github.com/radumarias/rencfs/issues/49) This prevents blocks from being copied between or within files by an attacker. +- `[WIP]` [Add file inode and chunk index to AAD](https://github.com/radumarias/rencfs/issues/49) This prevents blocks + from being copied between or within files by an attacker. - `Fast seek` on both reads and writes; - `Writes in parallel`; - Exposed with `FUSE`; @@ -57,12 +69,12 @@ Some of these are still being worked on and marked with `[WIP]`. - `[WIP]` [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/radumarias/rencfs/issues/97) - `[WIP]` [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/radumarias/rencfs/issues/111) -# [Alternatives](docs/Alternatives.md) +# [Alternatives](docs/readme/Alternatives.md) # Implementation -- [Functionality](docs/Functionality.md) -- [Stack](docs/Stack.md) +- [Functionality](docs/readme/Functionality.md) +- [Stack](docs/readme/Stack.md) # Documentation @@ -70,33 +82,38 @@ Some of these are still being worked on and marked with `[WIP]`. [![rencfs](website/resources/layers.png)](website/resources/layers.png) -Please look into [Flows](docs/flows.md) for a detailed description of the various sequence flows. +Please look into [Flows](docs/readme/flows.md) for a detailed description of the various sequence flows. # Usage and Development -- [Usage](docs/Usage.md) -- [Build from Source](docs/Build_from_Source.md) +- [Usage](docs/readme/Usage.md) +- [Build from Source](docs/readme/Build_from_Source.md) - Minimum Supported Rust Version (MSRV). The minimum supported version is `1.75`. # Next steps - - The plan is to implement it also on macOS and Windows - - **Systemd service** is being worked on [rencfs-daemon](https://github.com/radumarias/rencfs-daemon) - - **GUI** is being worked on [rencfs-desktop](https://github.com/radumarias/rencfs-desktop) and [ciphershell-kotlin](https://github.com/radumarias/ciphershell-kotlin) - - **Mobile apps** for **Android** and **iOS** are being worked on [ciphershell-kotlin](https://github.com/radumarias/ciphershell-kotlin) +- The plan is to implement it also on macOS and Windows +- **Systemd service** is being worked on [rencfs-daemon](https://github.com/radumarias/rencfs-daemon) +- **GUI** is being worked on [rencfs-desktop](https://github.com/radumarias/rencfs-desktop) + and [ciphershell-kotlin](https://github.com/radumarias/ciphershell-kotlin) +- **Mobile apps** for **Android** and **iOS** are being worked + on [ciphershell-kotlin](https://github.com/radumarias/ciphershell-kotlin) # Considerations - Performance - `Aes256Gcm` is slightly faster than `ChaCha20Poly1305` by an average factor of **1.28**. This is because of the hardware acceleration of AES -on most CPUs via AES-NI. However, where hardware acceleration is unavailable, `ChaCha20Poly1305` is faster. Also `ChaChaPoly1305` is better at `SIMD`. -- [⚠️ Security ](docs/Security.md) -- [Cipher comparison](docs/Cipher_comparison.md) -- [Others](docs/Considerations.md) + `Aes256Gcm` is slightly faster than `ChaCha20Poly1305` by an average factor of **1.28**. This is because of the + hardware acceleration of AES + on most CPUs via AES-NI. However, where hardware acceleration is unavailable, `ChaCha20Poly1305` is faster. + Also `ChaChaPoly1305` is better at `SIMD`. +- [⚠️ Security ](docs/readme/Security.md) +- [Cipher comparison](docs/readme/Cipher_comparison.md) +- [Others](docs/readme/Considerations.md) # Contribute -Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing pull requests are always appreciated. +Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing pull +requests are always appreciated. - How to contribute Please see [CONTRIBUTING.md](CONTRIBUTING.md). @@ -104,4 +121,6 @@ Feel free to fork it, change and use it however you want. If you build something # Follow us - Blog and tutorial - There will be a [series](https://medium.com/@xorio42/list/828492b94c23) of articles about the evolution of this project, trying to keep it like a tutorial. This is the [first one](https://systemweakness.com/the-hitchhikers-guide-to-building-an-encrypted-filesystem-in-rust-4d678c57d65c). + There will be a [series](https://medium.com/@xorio42/list/828492b94c23) of articles about the evolution of this + project, trying to keep it like a tutorial. This is + the [first one](https://systemweakness.com/the-hitchhikers-guide-to-building-an-encrypted-filesystem-in-rust-4d678c57d65c). diff --git a/Ramp-up.md b/Ramp-up.md deleted file mode 100644 index bab2a609..00000000 --- a/Ramp-up.md +++ /dev/null @@ -1,14 +0,0 @@ -# Ramp-up guide - -1. Read an [article](https://medium.com/system-weakness/hitchhikers-guide-to-building-a-distributed-filesystem-in-rust-the-very-beginning-2c02eb7313e7) and an [one pager](The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust-1.pdf) to get more details about the project -2. Read [Basics of cryptography for building a filesystem in Rust](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=962517464374) and [Building an Encrypted Filesystem in Rust](https://miro.com/app/board/uXjVLa8i1h0=/?share_link_id=745134849333) -3. Become familiar with the [concepts and features](https://github.com/radumarias/rencfs) and [lib docs](https://docs.rs/rencfs/latest/rencfs) -4. Understand the [layers](https://github.com/radumarias/rencfs/blob/main/website/resources/layers.png) -5. Detailed [sequence flows](docs/flows.md) -6. [Talks](https://startech-rd.io/hitchhikers-guide-to/) -7. Give it a [quick try](https://github.com/radumarias/rencfs#give-it-a-quick-try-with-docker) with Docker -8. Or run it as [CLI](https://github.com/radumarias/rencfs?tab=readme-ov-file#command-line-tool) app -9. Clone or fork the repo. After being added to it, you can work in your branches in the original repo. No need to fork it if you don't want to -10. [Build](https://github.com/radumarias/rencfs?tab=readme-ov-file#build-from-source) from source and start it. If you don't have Linux, you can [develop inside a container](https://github.com/radumarias/rencfs?tab=readme-ov-file#developing-inside-a-container). This will start a new Linux container and remotely connecting the local IDE to the container, you can also connect with IDE's terminal to it and run the code. On Windows, you can use [WSL](https://harsimranmaan.medium.com/install-and-setup-rust-development-environment-on-wsl2-dccb4bf63700). As a last resort, you can [develop in browser](https://github.com/radumarias/rencfs/blob/main/README.md#browser). -11. Run and understand [examples](examples). You can write some new ones to understand the flow and code better. If you do, please create a `PR` back to the parent repo targeting the `main` branch to include those for others too -12. Become familiar with [tests](https://github.com/radumarias/rencfs/blob/main/src/encryptedfs/test.rs) (and in other files) and benchmarks. You can write some new ones to understand the flow and code better. If you do, please create a `PR` back to the parent repo targeting the `main` branch to include those for others too diff --git a/docs/flows.md b/docs/flows.md deleted file mode 100644 index 8a44efb0..00000000 --- a/docs/flows.md +++ /dev/null @@ -1,22 +0,0 @@ - -# Sequence flow diagrams - -The following diagrams depict the main flows supported by the current implementation. They depict the high-level interactions between the various components of the filesystem which means some details have been omitted. - -> [!WARNING] -> The single source of truth for in-depth interactions is the source code itself. - -- [Mount](uml/mount.md) -- [Change Password](uml/change_pass.md) -- [Open File](uml/open_file.md) -- [Close File](uml/close_file.md) -- [Read](uml/read.md) -- [Write](uml/write.md) -- [Create File](uml/create_file.md) -- [Search File](uml/search_file.md) - -Usage flows: - -- [Cli usage](uml/cli_usage.md) -- [Rencfs as a lib](uml/lib_rencfs_usage.md) -- [Encryptedfs as a lib](uml/lib_encryptedfs_usage.md) \ No newline at end of file diff --git a/docs/Alternatives.md b/docs/readme/Alternatives.md similarity index 100% rename from docs/Alternatives.md rename to docs/readme/Alternatives.md diff --git a/docs/Build_from_Source.md b/docs/readme/Build_from_Source.md similarity index 100% rename from docs/Build_from_Source.md rename to docs/readme/Build_from_Source.md diff --git a/docs/Cipher_comparison.md b/docs/readme/Cipher_comparison.md similarity index 100% rename from docs/Cipher_comparison.md rename to docs/readme/Cipher_comparison.md diff --git a/docs/Considerations.md b/docs/readme/Considerations.md similarity index 100% rename from docs/Considerations.md rename to docs/readme/Considerations.md diff --git a/docs/Functionality.md b/docs/readme/Functionality.md similarity index 100% rename from docs/Functionality.md rename to docs/readme/Functionality.md diff --git a/docs/Key_features.md b/docs/readme/Key_features.md similarity index 100% rename from docs/Key_features.md rename to docs/readme/Key_features.md diff --git a/docs/readme/Ramp-up.md b/docs/readme/Ramp-up.md new file mode 100644 index 00000000..85502637 --- /dev/null +++ b/docs/readme/Ramp-up.md @@ -0,0 +1,30 @@ +# Ramp-up guide + +1. Read + an [article](https://medium.com/system-weakness/hitchhikers-guide-to-building-a-distributed-filesystem-in-rust-the-very-beginning-2c02eb7313e7) + and an [one pager](The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust-1.pdf) to get more details + about the project +2. +Read [Basics of cryptography for building a filesystem in Rust](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=962517464374) +and [Building an Encrypted Filesystem in Rust](https://miro.com/app/board/uXjVLa8i1h0=/?share_link_id=745134849333) +3. Become familiar with the [concepts and features](https://github.com/radumarias/rencfs) + and [lib docs](https://docs.rs/rencfs/latest/rencfs) +4. Understand the [layers](https://github.com/radumarias/rencfs/blob/main/website/resources/layers.png) +5. Detailed [sequence flows](flows.md) +6. [Talks](https://startech-rd.io/hitchhikers-guide-to/) +7. Give it a [quick try](https://github.com/radumarias/rencfs#give-it-a-quick-try-with-docker) with Docker +8. Or run it as [CLI](https://github.com/radumarias/rencfs?tab=readme-ov-file#command-line-tool) app +9. Clone or fork the repo. After being added to it, you can work in your branches in the original repo. No need to fork + it if you don't want to +10. [Build](https://github.com/radumarias/rencfs?tab=readme-ov-file#build-from-source) from source and start it. If you + don't have Linux, you + can [develop inside a container](https://github.com/radumarias/rencfs?tab=readme-ov-file#developing-inside-a-container). + This will start a new Linux container and remotely connecting the local IDE to the container, you can also connect + with IDE's terminal to it and run the code. On Windows, you can + use [WSL](https://harsimranmaan.medium.com/install-and-setup-rust-development-environment-on-wsl2-dccb4bf63700). As + a last resort, you can [develop in browser](https://github.com/radumarias/rencfs/blob/main/README.md#browser). +11. Run and understand [examples](../../examples). You can write some new ones to understand the flow and code better. + If you do, please create a `PR` back to the parent repo targeting the `main` branch to include those for others too +12. Become familiar with [tests](https://github.com/radumarias/rencfs/blob/main/src/encryptedfs/test.rs) (and in other + files) and benchmarks. You can write some new ones to understand the flow and code better. If you do, please create + a `PR` back to the parent repo targeting the `main` branch to include those for others too diff --git a/docs/Security.md b/docs/readme/Security.md similarity index 100% rename from docs/Security.md rename to docs/readme/Security.md diff --git a/docs/Stack.md b/docs/readme/Stack.md similarity index 100% rename from docs/Stack.md rename to docs/readme/Stack.md diff --git a/docs/Usage.md b/docs/readme/Usage.md similarity index 100% rename from docs/Usage.md rename to docs/readme/Usage.md diff --git a/docs/readme/flows.md b/docs/readme/flows.md new file mode 100644 index 00000000..71d7f9f8 --- /dev/null +++ b/docs/readme/flows.md @@ -0,0 +1,22 @@ +# Sequence flow diagrams + +The following diagrams depict the main flows supported by the current implementation. They depict the high-level +interactions between the various components of the filesystem which means some details have been omitted. + +> [!WARNING] +> The single source of truth for in-depth interactions is the source code itself. + +- [Mount](../uml/mount.md) +- [Change Password](../uml/change_pass.md) +- [Open File](../uml/open_file.md) +- [Close File](../uml/close_file.md) +- [Read](../uml/read.md) +- [Write](../uml/write.md) +- [Create File](../uml/create_file.md) +- [Search File](../uml/search_file.md) + +Usage flows: + +- [Cli usage](../uml/cli_usage.md) +- [Rencfs as a lib](../uml/lib_rencfs_usage.md) +- [Encryptedfs as a lib](../uml/lib_encryptedfs_usage.md) \ No newline at end of file diff --git a/docs/uml/lib_encryptedfs_usage.md b/docs/uml/lib_encryptedfs_usage.md index 1e1089d8..189cfcb9 100644 --- a/docs/uml/lib_encryptedfs_usage.md +++ b/docs/uml/lib_encryptedfs_usage.md @@ -2,34 +2,34 @@ sequenceDiagram participant application participant enc_new as EncryptedFs::new - - application -->> enc_new : data_dir,password_provider,cipher,read_only + application -->> enc_new: data_dir,password_provider,cipher,read_only create participant EncryptedFs - enc_new -->> EncryptedFs : - enc_new -->> application : EncryptedFs - Note left of application : create file under root_inode
and open for read and/or write - application -->> EncryptedFs : create(root_inode,file_name,file_attributes,read_flag,write_flag) - Note left of application : extract file_inode from file_attributes - EncryptedFs -->> application : (file_handle, file_attributes) - Note left of application : write data buffer into file at offset - application -->> EncryptedFs : write(file_inode,offset,data_buffer,file_handle) - EncryptedFs -->> application : bytes_written - Note left of application : flush file contents on storage - application -->> EncryptedFs : flush(file_handle) - EncryptedFs -->> application : - Note left of application : close the file - application -->> EncryptedFs : release(file_handle) - EncryptedFs -->> application : - Note left of application : open the file with file_inode
for read and/or write - application -->> EncryptedFs : open(file_inode,read,write) - EncryptedFs -->> application : file_handle - Note left of application : read from file with file_inode
at offset into data buffer - application -->> EncryptedFs : read(file_inode,offset,data_buffer,file_handle) - EncryptedFs -->> application : read_bytes - Note left of application : close the file - application -->> EncryptedFs : release(file_handle) - EncryptedFs -->> application : - application --x application : exit +enc_new -->> EncryptedFs: + enc_new -->> application: EncryptedFs +Note left of application: create file under root_inode
and open for read and/or write +application -->> EncryptedFs: create(root_inode,file_name,file_attributes,read_flag,write_flag) +Note left of application: extract file_inode from file_attributes +EncryptedFs -->> application : (file_handle, file_attributes) +Note left of application: write data buffer into file at offset +application -->> EncryptedFs: write(file_inode,offset,data_buffer,file_handle) +EncryptedFs -->> application: bytes_written +Note left of application: flush file contents on storage +application -->> EncryptedFs: flush(file_handle) +EncryptedFs -->> application: +Note left of application: close the file +application -->> EncryptedFs : release(file_handle) +EncryptedFs -->> application: +Note left of application: open the file with file_inode
for read and/or write +application -->> EncryptedFs: open(file_inode,read,write) +EncryptedFs -->> application: file_handle +Note left of application: read from file with file_inode
at offset into data buffer +application -->> EncryptedFs: read(file_inode,offset,data_buffer,file_handle) +EncryptedFs -->> application : read_bytes +Note left of application: close the file +application -->> EncryptedFs: release(file_handle) +EncryptedFs -->> application: +application --x application: exit ``` -Further details about the internals of create, open, close, read and write flows can be found in [flows](../flows.md). +Further details about the internals of create, open, close, read and write flows can be found +in [flows](../readme/flows.md). From 8c743f9cc0442b0277893558995e5291bb93850d Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Wed, 11 Dec 2024 23:56:40 +0200 Subject: [PATCH 058/150] libs upgrade --- Cargo.lock | 156 ++++++++++++++++-------------- Cargo.toml | 8 +- java-bridge/Cargo.lock | 213 +++++++++++++++++++++++++---------------- src/keyring.rs | 30 +++++- 4 files changed, 247 insertions(+), 160 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e556801b..612ab960 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,7 +165,7 @@ dependencies = [ "async-task", "concurrent-queue", "fastrand 2.1.1", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "slab", ] @@ -203,17 +203,17 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock 3.4.0", "cfg-if 1.0.0", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "parking", - "polling 3.7.3", + "polling 3.7.4", "rustix 0.38.37", "slab", "tracing", @@ -276,7 +276,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -285,7 +285,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "atomic-waker", "cfg-if 1.0.0", @@ -322,7 +322,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -333,11 +333,11 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atomic-write-file" -version = "0.1.4" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf54d4588732bdfc5ebc3eb9f74f20e027112fc31de412fc7ff0cd1c6896dae" +checksum = "23e32862ecc63d580f4a5e1436a685f51e0629caeb7a7933e4f017d5e2099e13" dependencies = [ - "nix 0.28.0", + "nix 0.29.0", "rand", ] @@ -444,15 +444,15 @@ dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "piper", ] [[package]] name = "bon" -version = "2.3.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97493a391b4b18ee918675fb8663e53646fd09321c58b46afa04e8ce2499c869" +checksum = "f265cdb2e8501f1c952749e78babe8f1937be92c98120e5f78fc72d634682bad" dependencies = [ "bon-macros", "rustversion", @@ -460,15 +460,17 @@ dependencies = [ [[package]] name = "bon-macros" -version = "2.3.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2af3eac944c12cdf4423eab70d310da0a8e5851a18ffb192c0a5e3f7ae1663" +checksum = "38aa5c627cd7706490e5b003d685f8b9d69bc343b1a00b9fdd01e75fdf6827cf" dependencies = [ "darling", "ident_case", + "prettyplease", "proc-macro2", "quote", - "syn 2.0.79", + "rustversion", + "syn 2.0.90", ] [[package]] @@ -525,12 +527,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cfg_aliases" version = "0.2.1" @@ -605,7 +601,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -786,7 +782,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -797,7 +793,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -855,7 +851,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -915,9 +911,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener 5.3.1", "pin-project-lite", @@ -964,9 +960,9 @@ checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" [[package]] name = "fuse3" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3175b0f85e74bbabc3bbb1135f9c37a429a26a3fb16169ee6d9608138b0697d8" +checksum = "335dd07e2826edad49c2599ed8151c394d39d2f8efd62c07183816904414ac5c" dependencies = [ "async-notify", "bincode", @@ -1022,9 +1018,9 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ "fastrand 2.1.1", "futures-core", @@ -1041,7 +1037,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -1182,9 +1178,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -1409,18 +1405,6 @@ dependencies = [ "memoffset 0.7.1", ] -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags 2.6.0", - "cfg-if 1.0.0", - "cfg_aliases 0.1.1", - "libc", -] - [[package]] name = "nix" version = "0.29.0" @@ -1429,7 +1413,7 @@ checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.6.0", "cfg-if 1.0.0", - "cfg_aliases 0.2.1", + "cfg_aliases", "libc", "memoffset 0.9.1", ] @@ -1690,9 +1674,9 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if 1.0.0", "concurrent-queue", @@ -1718,6 +1702,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.90", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1730,9 +1724,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1885,7 +1879,7 @@ dependencies = [ "strum_macros", "subtle", "tempfile", - "thiserror", + "thiserror 2.0.6", "thread_local", "tokio", "tokio-stream", @@ -2046,9 +2040,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -2077,7 +2071,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -2100,7 +2094,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -2233,7 +2227,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -2255,9 +2249,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -2283,7 +2277,16 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.64", +] + +[[package]] +name = "thiserror" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +dependencies = [ + "thiserror-impl 2.0.6", ] [[package]] @@ -2294,7 +2297,18 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -2374,7 +2388,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -2423,7 +2437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "thiserror", + "thiserror 1.0.64", "time", "tracing-subscriber", ] @@ -2436,7 +2450,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -2496,7 +2510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -2507,7 +2521,7 @@ checksum = "96cbd06a7b648f1603e60d75d9ed295d096b340d30e9f9324f4b512b5d40cd92" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -2601,7 +2615,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -2623,7 +2637,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2944,7 +2958,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 945555e2..f00350de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ clap = { version = "4.5.4", features = ["derive", "cargo"] } libc = "0.2.153" serde = { version = "1.0.197", features = ["derive"] } bincode = "1.3.3" -thiserror = "1.0.58" +thiserror = "2.0.6" rand = "0.8.5" rand_core = "0.6.4" base64 = "0.22.1" @@ -49,18 +49,18 @@ hex = "0.4.3" rand_chacha = "0.3.1" lru = "0.12.3" okaywal = "0.3.1" -atomic-write-file = "0.1.4" +atomic-write-file = "0.2.2" tempfile = "3.10.1" async-trait = "0.1.80" blake3 = "=0.1.3" thread_local = "1.1.8" subtle = "2.6.1" -bon = "2.2.0" +bon = "3.3.0" shush-rs = "0.1.10" criterion = { version = "0.5.1", features = ["html_reports"] } [target.'cfg(target_os = "linux")'.dependencies] -fuse3 = { version = "0.7.2", features = ["tokio-runtime", "unprivileged"] } +fuse3 = { version = "0.8.1", features = ["tokio-runtime", "unprivileged"] } [[bench]] name = "crypto_read" diff --git a/java-bridge/Cargo.lock b/java-bridge/Cargo.lock index f180fc06..82b9c1e7 100644 --- a/java-bridge/Cargo.lock +++ b/java-bridge/Cargo.lock @@ -170,14 +170,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", "fastrand 2.1.0", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "slab", ] @@ -215,21 +215,21 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock 3.4.0", "cfg-if 1.0.0", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "parking", - "polling 3.7.2", + "polling 3.7.4", "rustix 0.38.34", "slab", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -288,16 +288,16 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] name = "async-signal" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.3", + "async-io 2.4.0", "async-lock 3.4.0", "atomic-waker", "cfg-if 1.0.0", @@ -306,7 +306,7 @@ dependencies = [ "rustix 0.38.34", "signal-hook-registry", "slab", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -334,7 +334,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -345,11 +345,11 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atomic-write-file" -version = "0.1.4" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf54d4588732bdfc5ebc3eb9f74f20e027112fc31de412fc7ff0cd1c6896dae" +checksum = "23e32862ecc63d580f4a5e1436a685f51e0629caeb7a7933e4f017d5e2099e13" dependencies = [ - "nix 0.28.0", + "nix 0.29.0", "rand", ] @@ -456,15 +456,15 @@ dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "piper", ] [[package]] name = "bon" -version = "2.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "811d7882589e047896e5974d039dd8823a67973a63d559e6ad1e87ff5c42ed4f" +checksum = "f265cdb2e8501f1c952749e78babe8f1937be92c98120e5f78fc72d634682bad" dependencies = [ "bon-macros", "rustversion", @@ -472,15 +472,17 @@ dependencies = [ [[package]] name = "bon-macros" -version = "2.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e745a763e579a5ce70130e66f9dd35abf77cfeb9f418f305aeab8d1ae54c43" +checksum = "38aa5c627cd7706490e5b003d685f8b9d69bc343b1a00b9fdd01e75fdf6827cf" dependencies = [ "darling", "ident_case", + "prettyplease", "proc-macro2", "quote", - "syn 2.0.72", + "rustversion", + "syn 2.0.90", ] [[package]] @@ -546,6 +548,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "ciborium" version = "0.2.2" @@ -614,7 +622,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -666,9 +674,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" @@ -774,16 +782,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cstr" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636" -dependencies = [ - "proc-macro2", - "quote", -] - [[package]] name = "ctrlc" version = "3.4.4" @@ -815,7 +813,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -826,7 +824,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -884,7 +882,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -944,9 +942,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener 5.3.1", "pin-project-lite", @@ -987,18 +985,17 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fuse3" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02ca1b211677ee014a10b94ab6aea31e622ad1ea35f48b1670ac492a4f88b1af" +checksum = "335dd07e2826edad49c2599ed8151c394d39d2f8efd62c07183816904414ac5c" dependencies = [ "async-notify", "bincode", "bytes", - "cstr", "futures-channel", "futures-util", "libc", - "nix 0.28.0", + "nix 0.29.0", "serde", "slab", "tokio", @@ -1025,9 +1022,9 @@ checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1046,9 +1043,9 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ "fastrand 2.1.0", "futures-core", @@ -1065,7 +1062,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1146,6 +1143,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "heck" version = "0.5.0" @@ -1205,12 +1208,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.3.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -1300,7 +1303,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.63", "walkdir", "windows-sys 0.45.0", ] @@ -1390,7 +1393,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -1476,7 +1479,19 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.6.0", "cfg-if 1.0.0", - "cfg_aliases", + "cfg_aliases 0.1.1", + "libc", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if 1.0.0", + "cfg_aliases 0.2.1", "libc", "memoffset 0.9.1", ] @@ -1682,9 +1697,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", "fastrand 2.1.0", @@ -1737,9 +1752,9 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.2" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if 1.0.0", "concurrent-queue", @@ -1747,7 +1762,7 @@ dependencies = [ "pin-project-lite", "rustix 0.38.34", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1765,6 +1780,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.90", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1777,9 +1802,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1932,7 +1957,7 @@ dependencies = [ "strum_macros", "subtle", "tempfile", - "thiserror", + "thiserror 2.0.6", "thread_local", "tokio", "tokio-stream", @@ -2093,9 +2118,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -2124,7 +2149,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -2147,7 +2172,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -2274,7 +2299,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -2296,9 +2321,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -2324,7 +2349,16 @@ version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.63", +] + +[[package]] +name = "thiserror" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +dependencies = [ + "thiserror-impl 2.0.6", ] [[package]] @@ -2335,7 +2369,18 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -2415,7 +2460,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -2464,7 +2509,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "thiserror", + "thiserror 1.0.63", "time", "tracing-subscriber", ] @@ -2477,7 +2522,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -2537,7 +2582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -2548,7 +2593,7 @@ checksum = "96cbd06a7b648f1603e60d75d9ed295d096b340d30e9f9324f4b512b5d40cd92" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -2641,7 +2686,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -2663,7 +2708,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2958,12 +3003,12 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "xdg-home" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3050,7 +3095,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] diff --git a/src/keyring.rs b/src/keyring.rs index b2fb96e2..46b9e0a8 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -6,7 +6,7 @@ use shush_rs::{ExposeSecret, SecretString}; #[allow(dead_code)] const KEYRING_SERVICE: &str = "rencfs"; #[allow(dead_code)] -const KEYRING_USER: &str = "encrypted_fs"; +const KEYRING_USER: &str = "rencfs"; #[allow(dead_code)] pub(crate) fn save(password: &SecretString, suffix: &str) -> Result<(), keyring::Error> { @@ -25,3 +25,31 @@ pub(crate) fn get(suffix: &str) -> Result { let entry = Entry::new(KEYRING_SERVICE, &format!("{KEYRING_USER}.{suffix}"))?; Ok(SecretString::from_str(&entry.get_password()?).unwrap()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_save() { + let password = SecretString::from_str("password").unwrap(); + assert!(save(&password, "test").is_ok()); + } + + #[test] + fn test_get() { + let password = SecretString::from_str("password").unwrap(); + save(&password, "test").unwrap(); + assert_eq!( + get("test").unwrap().expose_secret(), + password.expose_secret() + ); + } + + #[test] + fn test_remove() { + let password = SecretString::from_str("password").unwrap(); + save(&password, "test").unwrap(); + assert!(remove("test").is_ok()); + } +} From 9ebf1b81e420d9af27e31ec7c1236ea595145027 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 12 Dec 2024 00:51:37 +0200 Subject: [PATCH 059/150] fix tests --- src/encryptedfs.rs | 4 ++-- src/keyring.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 8185b4b8..7c2da5a4 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -692,7 +692,7 @@ impl EncryptedFs { if self.exists_by_name(parent, name)? { return Err(FsError::AlreadyExists); } - self.validate_filename(&name)?; + self.validate_filename(name)?; // spawn on a dedicated runtime to not interfere with other higher priority tasks let self_clone = self @@ -2014,7 +2014,7 @@ impl EncryptedFs { if !self.exists_by_name(parent, name)? { return Err(FsError::NotFound("name not found")); } - self.validate_filename(&new_name)?; + self.validate_filename(new_name)?; if parent == new_parent && name.expose_secret() == new_name.expose_secret() { // no-op diff --git a/src/keyring.rs b/src/keyring.rs index 46b9e0a8..2b560a77 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -33,15 +33,15 @@ mod tests { #[test] fn test_save() { let password = SecretString::from_str("password").unwrap(); - assert!(save(&password, "test").is_ok()); + assert!(save(&password, "test1").is_ok()); } #[test] fn test_get() { let password = SecretString::from_str("password").unwrap(); - save(&password, "test").unwrap(); + save(&password, "test2").unwrap(); assert_eq!( - get("test").unwrap().expose_secret(), + get("test2").unwrap().expose_secret(), password.expose_secret() ); } @@ -49,7 +49,7 @@ mod tests { #[test] fn test_remove() { let password = SecretString::from_str("password").unwrap(); - save(&password, "test").unwrap(); - assert!(remove("test").is_ok()); + save(&password, "test3").unwrap(); + assert!(remove("test3").is_ok()); } } From 488bcd6639afbada61b181fc2c52aa52d64f0bdf Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 12 Dec 2024 01:21:15 +0200 Subject: [PATCH 060/150] Update build_and_tests_reusable.yaml --- .github/workflows/build_and_tests_reusable.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index 578a524b..46204929 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -82,6 +82,7 @@ jobs: - name: java-bridge clippy run: | + cd java-bridge cargo clippy --all-targets --release -- \ -A clippy::similar_names \ -A clippy::too_many_arguments \ @@ -92,7 +93,9 @@ jobs: shell: bash - name: java-bridge doc - run: cargo doc --workspace --all-features --no-deps + run: | + cd java-bridge + cargo doc --workspace --all-features --no-deps - name: java-bridge tests if: matrix.os != 'windows-latest' From 7d6d6e37f75500468ea095431f4e29ecf27a1eaa Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 12 Dec 2024 03:39:30 +0200 Subject: [PATCH 061/150] clippy --- java-bridge/src/lib.rs | 11 +++-------- src/lib.rs | 30 ++++++++++++++---------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/java-bridge/src/lib.rs b/java-bridge/src/lib.rs index 7090fd76..235058e9 100644 --- a/java-bridge/src/lib.rs +++ b/java-bridge/src/lib.rs @@ -13,12 +13,10 @@ use rencfs::crypto::Cipher; use rencfs::encryptedfs::PasswordProvider; use rencfs::log::log_init; use rencfs::mount::{create_mount_point, umount, MountHandle}; -use secrets::SecretVec; use shush_rs::SecretString; use std::collections::BTreeMap; use std::ops::Add; use std::path::Path; -use std::str::FromStr; use std::sync::LazyLock; use std::{io, process}; use tokio::runtime::Runtime; @@ -120,8 +118,7 @@ pub extern "system" fn Java_RustLibrary_mount( let mount_path: String = env.get_string(&mnt).unwrap().into(); let data_dir_path: String = env.get_string(&data_dir).unwrap().into(); let password: String = env.get_string(&password).unwrap().into(); - let new_pass = SecretVec::::new(password.into_bytes()); // create new pass using secretvec - // drop(password); // drop password after + let new_pass = SecretString::new(Box::new(password)); info!("mount_path: {}", mount_path); info!("data_dir_path: {}", data_dir_path); @@ -174,11 +171,10 @@ pub extern "system" fn Java_RustLibrary_mount( }); } - struct PasswordProviderImpl(SecretVec); // use secretvec instead of string + struct PasswordProviderImpl(SecretString); // use secretvec instead of string impl PasswordProvider for PasswordProviderImpl { fn get_password(&self) -> Option { - let password_str = String::from_utf8_lossy(self.0.expose_secret()); - Some(SecretString::from_str(&password_str).unwrap()) + Some(self.0.clone()) } } @@ -191,7 +187,6 @@ pub extern "system" fn Java_RustLibrary_mount( false, false, ); - drop(new_pass); // drop pass after use let handle = match RT.block_on(async { match mount_point.mount().await { diff --git a/src/lib.rs b/src/lib.rs index 79715b4f..4dfc2c88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,6 @@ //! ```no_run //! use std::env::args; //! use std::path::Path; -//! use std::str::FromStr; //! use std::io; //! use tracing::info; //! @@ -43,13 +42,13 @@ //! args.next(); // skip program name //! let mount_path = args.next().expect("mount_path expected"); //! let data_path = args.next().expect("data_path expected"); -//! use rencfs::crypto::Cipher; +//! use rencfs::crypto::Cipher; //! //! struct PasswordProviderImpl {} //! impl PasswordProvider for PasswordProviderImpl { //! fn get_password(&self) -> Option { -//! // dummy password, use some secure way to get the password like with [keyring](https://crates.io/crates/keyring) crate -//! Some(SecretString::from_str("pass42").unwrap()) +//! // placeholder password, use some secure way to get the password like with [keyring](https://crates.io/crates/keyring) crate +//! Some(SecretString::new(Box::new(String::from("pass42")))) //! } //! } //! let mount_point = create_mount_point( @@ -77,15 +76,15 @@ //! You need to specify several parameters to create an encrypted file system: //! - `data_dir`: The directory where the file system will be mounted. //! - `password`: The password to encrypt/decrypt the data. -//! - `cipher`: The encryption algorithm to use. +//! - `Cipher`: The encryption algorithm to use. //! //! Currently, it supports these ciphers [Cipher](crypto::Cipher). //! //! ### Example //! //! ``` +//! #![allow(unused_imports)] //! use std::fs; -//! use std::str::FromStr; //! use shush_rs::SecretString; //! use rencfs::encryptedfs::{EncryptedFs, FileType, PasswordProvider, CreateFileAttr}; //! use rencfs::crypto::Cipher; @@ -98,8 +97,8 @@ //! struct PasswordProviderImpl {} //! impl PasswordProvider for PasswordProviderImpl { //! fn get_password(&self) -> Option { -//! // dummy password, use some secure way to get the password like with [keyring](https://crates.io/crates/keyring) crate -//! Some(SecretString::from_str("pass42").unwrap()) +//! // placeholder password, use some secure way to get the password like with [keyring](https://crates.io/crates/keyring) crate +//! Some(SecretString::new(Box::new(String::from("pass42")))) //! } //! } //! @@ -112,7 +111,7 @@ //! let cipher = Cipher::ChaCha20Poly1305; //! let mut fs = EncryptedFs::new(data_dir.clone(), Box::new(PasswordProviderImpl{}), cipher, false).await?; //! -//! let file1 = SecretString::from_str("file1").unwrap(); +//! let file1 = SecretString::new(Box::new(String::from("file-1"))); //! let (fh, attr) = fs.create(ROOT_INODE, &file1, file_attr(), false, true).await?; //! let data = "Hello, world!"; //! write_all_string_to_fs( &fs, attr.ino, 0,data, fh).await?; @@ -142,13 +141,13 @@ //! ## Change password from code //! //! ### Example +//! //! ```no_run //! use rencfs::crypto::Cipher; //! use rencfs::encryptedfs::{EncryptedFs, FsError}; //! use shush_rs::SecretString; //! use std::env::args; //! use std::path::Path; -//! use std::str::FromStr; //! //! #[tokio::main] //! async fn main() { @@ -160,8 +159,8 @@ //! //! match EncryptedFs::passwd( //! Path::new(&data_dir), -//! SecretString::from_str("old-pass").unwrap(), -//! SecretString::from_str("new-pass").unwrap(), +//! SecretString::new(Box::new(String::from("old-pass"))), +//! SecretString::new(Box::new(String::from("new-pass"))), //! Cipher::ChaCha20Poly1305, //! ) //! .await @@ -181,7 +180,6 @@ //! use std::env::args; //! use std::io; //! use std::io::Write; -//! use std::str::FromStr; //! //! use rpassword::read_password; //! use shush_rs::{ExposeSecret, SecretString}; @@ -201,13 +199,13 @@ //! use rencfs::crypto::Cipher; //! print!("Enter old password: "); //! io::stdout().flush().unwrap(); -//! let old_password = SecretString::from_str(&read_password().unwrap()).unwrap(); +//! let old_password = SecretString::new(Box::new(read_password().unwrap())); //! print!("Enter new password: "); //! io::stdout().flush().unwrap(); -//! let new_password = SecretString::from_str(&read_password().unwrap()).unwrap(); +//! let new_password = SecretString::new(Box::new(read_password().unwrap())); //! print!("Confirm new password: "); //! io::stdout().flush().unwrap(); -//! let new_password2 = SecretString::from_str(&read_password().unwrap()).unwrap(); +//! let new_password2 = SecretString::new(Box::new(read_password().unwrap())); //! if new_password.expose_secret() != new_password2.expose_secret() { //! error!("Passwords do not match"); //! return; From d607cafc5219766032bab3d8c7738d1f0f288943 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 12 Dec 2024 03:49:27 +0200 Subject: [PATCH 062/150] fix tests --- src/keyring.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/keyring.rs b/src/keyring.rs index 2b560a77..9970dc76 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -34,6 +34,7 @@ mod tests { fn test_save() { let password = SecretString::from_str("password").unwrap(); assert!(save(&password, "test1").is_ok()); + remove("test1").unwrap(); } #[test] @@ -44,6 +45,7 @@ mod tests { get("test2").unwrap().expose_secret(), password.expose_secret() ); + remove("test2").unwrap(); } #[test] From 90f2495dc9f383c4855ddb4d8b08bce8ccd5675a Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 12 Dec 2024 03:54:30 +0200 Subject: [PATCH 063/150] Update package_reusable.yaml --- .github/workflows/package_reusable.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/package_reusable.yaml b/.github/workflows/package_reusable.yaml index 1673ce4d..7b901d67 100644 --- a/.github/workflows/package_reusable.yaml +++ b/.github/workflows/package_reusable.yaml @@ -83,6 +83,7 @@ jobs: - name: build push = ${{ inputs.upload_artifacts }} uses: docker/build-push-action@v6 with: + file: docker/Dockerfile push: ${{ inputs.upload_artifacts }} tags: ${{ env.REGISTRY_USER }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} sbom: true From 6d35c3d00979e105a3f37ff2bc9ea16a2233105e Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 12 Dec 2024 03:55:24 +0200 Subject: [PATCH 064/150] Update package_reusable.yaml --- .github/workflows/package_reusable.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/package_reusable.yaml b/.github/workflows/package_reusable.yaml index 7b901d67..c4b09861 100644 --- a/.github/workflows/package_reusable.yaml +++ b/.github/workflows/package_reusable.yaml @@ -65,8 +65,6 @@ jobs: IMAGE_NAME: ${{ github.event.repository.name }} IMAGE_TAG: latest SHA: ${{ github.event.pull_request.head.sha || github.event.after }} - with: - file: docker/Dockerfile steps: - uses: actions/checkout@v4 From 46302684180c763328b99190b9c13a8c1d20c0f0 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Thu, 12 Dec 2024 04:21:22 +0200 Subject: [PATCH 065/150] Update build_and_tests_reusable.yaml --- .github/workflows/build_and_tests_reusable.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index 46204929..f3f7bd02 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -56,7 +56,7 @@ jobs: - name: tests if: matrix.os != 'windows-latest' - run: cargo test --release --all --all-features + run: cargo test --release --all --all-features -- --skip keyring - name: test package if: matrix.os == 'ubuntu-latest' From 13050f867d71477a89948cbb67beda6da89d7518 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 03:55:03 +0200 Subject: [PATCH 066/150] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 64f011f9..d4239a24 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ data. Some of these are still being worked on and marked with `[WIP]`. - `Security` using well-known audited `AEAD` cryptography primitives; -- `[WIP]` [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48) -- `[WIP]` [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53) +- `[WIP]` [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48); +- `[WIP]` [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53); - `Safely` manage `credentials` in memory with `mlock(2)`, `mprotect`, `zeroize`, and `expiry` to mitigate cold boot attacks; - `Memory safety`, `performance`, and `optimized` for `concurrency` with Rust; @@ -58,16 +58,16 @@ Some of these are still being worked on and marked with `[WIP]`. - Encryption key generated from password; - Password saved in OS's `keyring`; - `Change password` without re-encrypting all data; -- `[WIP]` [Generate unique nonce in offline mode](https://github.com/radumarias/rencfs/issues/47) +- `[WIP]` [Generate unique nonce in offline mode](https://github.com/radumarias/rencfs/issues/47); - `[WIP]` [Add file inode and chunk index to AAD](https://github.com/radumarias/rencfs/issues/49) This prevents blocks - from being copied between or within files by an attacker. + from being copied between or within files by an attacker; - `Fast seek` on both reads and writes; - `Writes in parallel`; - Exposed with `FUSE`; - Fully `concurrent` for all operations; -- `[WIP]` [Handle long file names](https://github.com/radumarias/rencfs/issues/47) -- `[WIP]` [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/radumarias/rencfs/issues/97) -- `[WIP]` [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/radumarias/rencfs/issues/111) +- `[WIP]` [Handle long file names](https://github.com/radumarias/rencfs/issues/47); +- `[WIP]` [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/radumarias/rencfs/issues/97); +- `[WIP]` [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/radumarias/rencfs/issues/111); # [Alternatives](docs/readme/Alternatives.md) From fb87465bcbc944cb45a276a11230ab09ddfaf66e Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 03:56:23 +0200 Subject: [PATCH 067/150] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d4239a24..8d4f98be 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ least the primitives should offer a similar level of security. stable release. > It's mostly ideal for experimental and learning projects.** -An encrypted file system written in Rust that is mounted with FUSE on Linux. It can be used to create encrypted +An encrypted file system written in Rust mounted with FUSE on Linux. It can be used to create encrypted directories. You can then safely back up the encrypted directory to an untrusted server without worrying about the data being @@ -49,8 +49,8 @@ data. Some of these are still being worked on and marked with `[WIP]`. - `Security` using well-known audited `AEAD` cryptography primitives; -- `[WIP]` [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48); -- `[WIP]` [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53); +- `[WIP]` [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48) +- `[WIP]` [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53) - `Safely` manage `credentials` in memory with `mlock(2)`, `mprotect`, `zeroize`, and `expiry` to mitigate cold boot attacks; - `Memory safety`, `performance`, and `optimized` for `concurrency` with Rust; @@ -58,16 +58,16 @@ Some of these are still being worked on and marked with `[WIP]`. - Encryption key generated from password; - Password saved in OS's `keyring`; - `Change password` without re-encrypting all data; -- `[WIP]` [Generate unique nonce in offline mode](https://github.com/radumarias/rencfs/issues/47); +- `[WIP]` [Generate unique nonce in offline mode](https://github.com/radumarias/rencfs/issues/47) - `[WIP]` [Add file inode and chunk index to AAD](https://github.com/radumarias/rencfs/issues/49) This prevents blocks from being copied between or within files by an attacker; - `Fast seek` on both reads and writes; - `Writes in parallel`; - Exposed with `FUSE`; - Fully `concurrent` for all operations; -- `[WIP]` [Handle long file names](https://github.com/radumarias/rencfs/issues/47); -- `[WIP]` [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/radumarias/rencfs/issues/97); -- `[WIP]` [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/radumarias/rencfs/issues/111); +- `[WIP]` [Handle long file names](https://github.com/radumarias/rencfs/issues/47) +- `[WIP]` [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/radumarias/rencfs/issues/97) +- `[WIP]` [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/radumarias/rencfs/issues/111) # [Alternatives](docs/readme/Alternatives.md) From b6d85866f6fa680f4ada7be04f0efc9c47a3d1d3 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 05:27:48 +0200 Subject: [PATCH 068/150] Update Testing.md --- docs/readme/Testing.md | 73 ++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index b49da369..75583bea 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -4,77 +4,68 @@ We'd appreciate it if you could help test the app. For now, the filesystem mount Here are some ways you can do it. -## Testing in the browser or in local VSCode +## Testing in the browser or local VSCode + +You'll need a GitHub account for this. This will create a Codespace instance on GitHub, which is a Linux container, so we will be able to test it. -The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month free for Codespace, which means 60 hours for that instance. We will connect to it from the local VSCode browser. +The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month free for Codespace, which means 60 hours for that instance. We will connect to it from the browser and the local VSCode. ### First setup 1. Open the [repo](https://github.com/radumarias/rencfs) 2. Press `Code` button ![image](https://github.com/user-attachments/assets/7c0e8872-fe1f-44b9-a833-2586ade4f618) -3. Create codespace on main +3. Create codespace on main ![image](https://github.com/user-attachments/assets/5fee55f6-ef54-427c-b790-c135312d3355) 4. This will create the container on GitHub. If it asks you to setup config, select the minimum possible CPU and RAM -5. Start it and leave it to finish -6. Go back to the repo root. You can close the current tab -7. Press `Code` button - ![image](https://github.com/user-attachments/assets/0baec7da-cbbd-4186-a82b-887e18c0c85d) -8. Press ```...``` right to the instance in the list - ![image](https://github.com/user-attachments/assets/c621c258-009d-46bf-adb7-f81a3d7131f6) -9. Press `Open in Browser` -10. Allow it to finish -11. Open a terminal in VSCode from the menu `Terminal -> New Terminal` - On the browser, you can find the menu in the top left 3 lines icon +5. Start it and leave it to finish. This could take a bit longer +6. Goto terminal in the browser version of the VSCode editor you're presented with. It should be at the bottom, or open it from the menu `Terminal -> New Terminal` +7. You can find the menu in the top left, with 3 lines icon ![image](https://github.com/user-attachments/assets/48681023-e450-49b3-8526-ec0323be0d40) -12. Install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: +8. Install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: ```bash apt-get update && apt-get install fuse3 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - mkdir mnt && mkdir data + mkdir tmp && mkdir mnt && mkdir data ``` Press enter on Rust installation, accepting all defaults - + ### Each resume and after the first setup -Do steps 1, 7, and 8 from above. +1. Open the [repo](https://github.com/radumarias/rencfs) +2. Press `Code` button + ![image](https://github.com/user-attachments/assets/7c0e8872-fe1f-44b9-a833-2586ade4f618) +3. Press ```...``` right to the instance in the list + ![image](https://github.com/user-attachments/assets/c621c258-009d-46bf-adb7-f81a3d7131f6) -#### In Browser +#### VSCode in Browser -Do step 9 from above. This will open VSCode in the browser. -Instead of taking step 8 and the above, you can directly click on the container name. +4. Press `Open in Browser`, or directly click on the container name #### In local VSCode -Make sure you have VSCode installed locally, based on your OS +Make sure you have VSCode installed locally, based on your OS. -After step 8, press `Open in Visual Studio Code`. +4. Press `Open in Visual Studio Code` #### Continue Do step 11 from above. -1. Type this in terminal, which will fetch the changes from the repo (if there are conflicts, accept Theirs): +5. Type this in the VSCode terminal, which will fetch the changes from the repo (if there are conflicts, accept Theirs): ```bash git pull + git checkout --theirs . cargo run --release -- mount -m mnt -d data ``` -2. Input a password and confirm it the first time -3. Copy test files from your machine to `tmp` folder in `VSCode`, by `Ctrl + C / Ctrl + V` or by Drag and Drop -4. Copy files and folders from `tmp` to `mnt` and do all kinds of operations on `nnt` folder -5. Make sure files were copied successfully by right-clicking a file and then `Download...` and save it to local machine -6. Make sure files open correctly -7. Do all kinds of operations in `mnt` folder and make sure they are ok - -## Testing on Linux - -TODO - -## Testing on macOS - -TODO - -## Testing on Windows - -TODO +6. Input a password and confirm it the first time +9. Copy test files from your machine to `tmp` folder in `VSCode`, by `Ctrl + C / Ctrl + V` or by Drag and Drop +10. Copy files and folders from `tmp` to `mnt` and do all kinds of operations on `nnt` folder +11. Make sure files were copied successfully by right-clicking a file and then `Download...` and save it to local machine +12. Make sure files open correctly +13. Do all kinds of operations in `mnt` folder and make sure they are ok + +- [ ] Testing on Linux +- [ ] Testing on macOS +- [ ] Testing on Windows From de64d4543e64d2ba847ff65562bec6c8af30dcd2 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 05:32:45 +0200 Subject: [PATCH 069/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c01d18e6..c2cdcb0c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,15 +7,18 @@ Unless you explicitly state otherwise, any Contribution intentionally submitted defined in the Apache License shall be dual-licensed as above, without any additional terms or conditions. 1. Join [slack](https://bit.ly/3UU1oXi) and join `#dev-beginners` channel -2. Become familiar with docs and code by reading the [ramp-up](docs/readme/Ramp-up.md) guide -3. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and +2. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and be able to create your own branches and not need to fork the repo + +# Devs & QA automation (which steps apply) + +3. Become familiar with docs and code by reading the [ramp-up](docs/readme/Ramp-up.md) guide 4. Pick an open issue or a task in the corresponding [project](https://github.com/users/radumarias/projects/1) for the - repo that you'll be working on. You can + repo you'll work on. You can see [good for first issues](https://github.com/radumarias/rencfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) that you can pick from -5. **Assign the issues you are working on to you and move them to the corresponding status column as you are progressing - on them. If the task is not an issue yet, convert it to issue first** +5. **Assign the issues you are working on to you and move them to the corresponding status column as you progress + . If the task is not an issue yet, convert it to an issue first** 6. Make the changes in your branch 7. Add docs as they apply 8. Add tests, benchmarks, and examples for your changes, if applicable @@ -38,3 +41,7 @@ defined in the Apache License shall be dual-licensed as above, without any addit 16. Respond to any comments 17. **DON'T MERGE THE PR YOURSELF. LEAVE THAT TO REPOSITORY OWNERS** 18. In the end, ideally, it will be merged into `main` + +# QA manual + +Please follow these [steps](docs/readme/Testing.md). From 5e6a160ed6b903176383231845b91670ea5c4d21 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 06:34:39 +0200 Subject: [PATCH 070/150] Update Testing.md --- docs/readme/Testing.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 75583bea..8904e872 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -60,12 +60,31 @@ Do step 11 from above. cargo run --release -- mount -m mnt -d data ``` 6. Input a password and confirm it the first time -9. Copy test files from your machine to `tmp` folder in `VSCode`, by `Ctrl + C / Ctrl + V` or by Drag and Drop -10. Copy files and folders from `tmp` to `mnt` and do all kinds of operations on `nnt` folder -11. Make sure files were copied successfully by right-clicking a file and then `Download...` and save it to local machine -12. Make sure files open correctly -13. Do all kinds of operations in `mnt` folder and make sure they are ok + +You can now perform two types of tests; see below. In both cases, follow these steps. + +7. Copy files and folders from your local machine to `tmp` folder in VSCode +8. Copy files and folders from `tmp` to `mnt` and then do your operations the data in `nnt` folder +9. Make sure files were copied successfully by right-clicking a file and then `Download...` and save it to local machine and making sure it opens correctly + +#### Exploratory testing + +That is, testing anything that comes to mind. + +Repeat steps 7-9 in various ways. + +#### Test specific issues + +Test specific issues from the [project](https://github.com/users/radumarias/projects/1). You can take the ones from `Ready for QA` column: +1. Assign the issue to you and move it to `In QA` +2. Test it +3. When you finished, move it to `Tested` - [ ] Testing on Linux - [ ] Testing on macOS - [ ] Testing on Windows + +## Open a bug + +## Open a feature + From 5ef61b5542971bdd3e24aa8e3caafb0723acc50f Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 06:44:21 +0200 Subject: [PATCH 071/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2cdcb0c..5becebd3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,10 +37,11 @@ defined in the Apache License shall be dual-licensed as above, without any addit 13. Commit and push your changes, and if there are any errors, fix them before you push them 14. Create a `PR` back to the `parent` repo targeting the `main` branch and request review from owners of the repository by adding them to the `Reviewers` field -15. Monitor the checks (GitHub actions runs) and fix the code if they are failing -16. Respond to any comments -17. **DON'T MERGE THE PR YOURSELF. LEAVE THAT TO REPOSITORY OWNERS** -18. In the end, ideally, it will be merged into `main` +15. In the project, move the item to `In Code Review` +16. Monitor the checks (GitHub actions runs) and fix the code if they are failing +17. Respond to any comments +18. **DON'T MERGE THE PR YOURSELF. LEAVE THAT TO REPOSITORY OWNERS** +19. In the end, ideally, it will be merged into `main` # QA manual From 904d29531d5e8995f9218eb92c2ccd1f24a243b8 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 07:20:41 +0200 Subject: [PATCH 072/150] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea78..597dfe6c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -17,6 +17,8 @@ Steps to reproduce the behavior: 3. Scroll down to '....' 4. See error +**Actual behavior** + **Expected behavior** A clear and concise description of what you expected to happen. @@ -25,13 +27,12 @@ If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - Version [e.g. 22] + - Last 100 lines from logs **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** From 69c296e313d900afb05978b3f43706076e57bff1 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 07:26:14 +0200 Subject: [PATCH 073/150] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8d4f98be..38e32f4f 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,12 @@ Please look into [Flows](docs/readme/flows.md) for a detailed description of the # Contribute +If you find any issues or you desire a feature, please follow these steps: + +- [Open a bug](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=): Create a report to help us improve. +- [Report a security vulnerability](https://github.com/radumarias/rencfs/security/advisories/new): Report a security vulnerability. +- [Feature request](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=): Suggest an idea for this project. + Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing pull requests are always appreciated. From 9049f3926aabc70b1e2f5c4f1231c3294cb52d5b Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 07:28:17 +0200 Subject: [PATCH 074/150] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 38e32f4f..1eaaf70f 100644 --- a/README.md +++ b/README.md @@ -112,14 +112,13 @@ Please look into [Flows](docs/readme/flows.md) for a detailed description of the # Contribute -If you find any issues or you desire a feature, please follow these steps: +If you find any issues, vulnerabilities or you'd like a feature, please follow these steps: - [Open a bug](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=): Create a report to help us improve. - [Report a security vulnerability](https://github.com/radumarias/rencfs/security/advisories/new): Report a security vulnerability. - [Feature request](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=): Suggest an idea for this project. -Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing pull -requests are always appreciated. +Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing pull requests, it is always appreciated. - How to contribute Please see [CONTRIBUTING.md](CONTRIBUTING.md). @@ -127,6 +126,6 @@ requests are always appreciated. # Follow us - Blog and tutorial - There will be a [series](https://medium.com/@xorio42/list/828492b94c23) of articles about the evolution of this + There is a [series](https://medium.com/@xorio42/list/828492b94c23) of articles about the evolution of this project, trying to keep it like a tutorial. This is the [first one](https://systemweakness.com/the-hitchhikers-guide-to-building-an-encrypted-filesystem-in-rust-4d678c57d65c). From f57b181e69fe44d69be136d9a60477fca85f4ed0 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 07:28:43 +0200 Subject: [PATCH 075/150] Update Testing.md --- docs/readme/Testing.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 8904e872..00c41d51 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -84,7 +84,19 @@ Test specific issues from the [project](https://github.com/users/radumarias/proj - [ ] Testing on macOS - [ ] Testing on Windows +## Test cases + +I created some [files](https://drive.google.com/drive/folders/1N-2KhGNo7f23tQ9Si4yWa9dlFtxUnsoM?usp=sharing) to keep our tests until we migrate to browserstack or similar. + +- `test cases`: generic test cases +- `smoke tests`: short, small tests used to test a build quickly +- `acceptance`: tests that must be passed to consider a build stable. These we will run for prod builds +- `bug` sample sample file to create a new bug. Copy it and create a new file in `bugs` folder. After you have this, please create a bug on GitHub, too and name the file as the bug in there, including #id too + ## Open a bug -## Open a feature +Please use [this](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=) and follow the steps in there. + +## Creating a test case +## Creating a smoke test From ac057c7df6a210c80e3fc6f575064237cf1aa825 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 07:30:30 +0200 Subject: [PATCH 076/150] Update Testing.md --- docs/readme/Testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 00c41d51..6210618a 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -91,7 +91,7 @@ I created some [files](https://drive.google.com/drive/folders/1N-2KhGNo7f23tQ9Si - `test cases`: generic test cases - `smoke tests`: short, small tests used to test a build quickly - `acceptance`: tests that must be passed to consider a build stable. These we will run for prod builds -- `bug` sample sample file to create a new bug. Copy it and create a new file in `bugs` folder. After you have this, please create a bug on GitHub, too and name the file as the bug in there, including #id too +- `bug` sample sample file to create a new bug. Copy it and create a new file in `bugs` folder. After you have this, please create a bug on GitHub, too, and name the file as the bug in there, including #id too ## Open a bug @@ -99,4 +99,4 @@ Please use [this](https://github.com/radumarias/rencfs/issues/new?assignees=&lab ## Creating a test case -## Creating a smoke test +Please add a new row in the `test cases` file and follow the template of the first row, for example. The same applies to smoke tests and acceptance tests. From 29b407a8e67a56778ee8be1a6cb6071a3172153a Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 14:12:22 +0200 Subject: [PATCH 077/150] Update Testing.md --- docs/readme/Testing.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 6210618a..10b03e6c 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -16,12 +16,12 @@ The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month fre 1. Open the [repo](https://github.com/radumarias/rencfs) 2. Press `Code` button ![image](https://github.com/user-attachments/assets/7c0e8872-fe1f-44b9-a833-2586ade4f618) -3. Create codespace on main +3. Create codespace on main ![image](https://github.com/user-attachments/assets/5fee55f6-ef54-427c-b790-c135312d3355) 4. This will create the container on GitHub. If it asks you to setup config, select the minimum possible CPU and RAM 5. Start it and leave it to finish. This could take a bit longer 6. Goto terminal in the browser version of the VSCode editor you're presented with. It should be at the bottom, or open it from the menu `Terminal -> New Terminal` -7. You can find the menu in the top left, with 3 lines icon +7. You can find the menu in the top left, with 3 lines icon ![image](https://github.com/user-attachments/assets/48681023-e450-49b3-8526-ec0323be0d40) 8. Install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: ```bash @@ -36,7 +36,7 @@ The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month fre 1. Open the [repo](https://github.com/radumarias/rencfs) 2. Press `Code` button ![image](https://github.com/user-attachments/assets/7c0e8872-fe1f-44b9-a833-2586ade4f618) -3. Press ```...``` right to the instance in the list +3. Press ```...``` right to the instance in the list ![image](https://github.com/user-attachments/assets/c621c258-009d-46bf-adb7-f81a3d7131f6) #### VSCode in Browser From f1d2bda9dfc3b2a5b27da8af87adb1fff6fe8ee6 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 14:32:35 +0200 Subject: [PATCH 078/150] Update Testing.md --- docs/readme/Testing.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 10b03e6c..e0862f93 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -29,7 +29,14 @@ The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month fre curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh mkdir tmp && mkdir mnt && mkdir data ``` - Press enter on Rust installation, accepting all defaults + Press 1 and Enter on Rust installation, accepting all defaults. + + If installation aborts, then run these + ```bash + apt update + apt install rustc + rustc + ``` ### Each resume and after the first setup From 30514267cdf701b96452a3243c58fd8eac8d5efa Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 14:44:28 +0200 Subject: [PATCH 079/150] Update Testing.md --- docs/readme/Testing.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index e0862f93..056d31a7 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -27,7 +27,6 @@ The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month fre ```bash apt-get update && apt-get install fuse3 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - mkdir tmp && mkdir mnt && mkdir data ``` Press 1 and Enter on Rust installation, accepting all defaults. @@ -37,6 +36,10 @@ The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month fre apt install rustc rustc ``` +9. Run this in terminal: + ```bash + mkdir tmp && mkdir mnt && mkdir data + ``` ### Each resume and after the first setup From a90bc83805a98a56013609d0c6a86b99aaa8ed69 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 13 Dec 2024 17:49:33 +0200 Subject: [PATCH 080/150] fix typo in docs --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5becebd3..e156b93d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ defined in the Apache License shall be dual-licensed as above, without any addit save, [RustRover](https://www.jetbrains.com/help/rust/rustfmt.html) and [VSCode](https://code.visualstudio.com/docs/languages/rust#_formatting) 10. `cargo clippy --all --release` and fix any errors -11. **DON'T INCREASE THE VERSION NUMBER IN `Cargo.toml`, WE WILL DO THAN WHEN RELEASING** +11. **DON'T INCREASE THE VERSION NUMBER IN `Cargo.toml`, WE WILL DO THAT WHEN RELEASING** 12. Create a `git` `push hook` file in `.git/hooks/pre-push` with [pre-push](scripts/git-hooks/linux-macos/pre-push) content on `Linux` and `macOS`, and [pre-push](scripts/git-hooks/windows/pre-push) on `Windows`. From 155ed0a971aff7881e7700769e7d4ca04bfa19cf Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Sun, 15 Dec 2024 19:59:05 +0200 Subject: [PATCH 081/150] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 597dfe6c..c2f71968 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,8 +22,8 @@ Steps to reproduce the behavior: **Expected behavior** A clear and concise description of what you expected to happen. -**Screenshots** -If applicable, add screenshots to help explain your problem. +**Screenshots or screen recordings** +If applicable, add screenshots or screen recordings to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] From c01cf63dbe5626a01464b89a095a8a54f1d2ef31 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Sun, 15 Dec 2024 20:32:23 +0200 Subject: [PATCH 082/150] Update Testing.md --- docs/readme/Testing.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 056d31a7..76276842 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -101,7 +101,6 @@ I created some [files](https://drive.google.com/drive/folders/1N-2KhGNo7f23tQ9Si - `test cases`: generic test cases - `smoke tests`: short, small tests used to test a build quickly - `acceptance`: tests that must be passed to consider a build stable. These we will run for prod builds -- `bug` sample sample file to create a new bug. Copy it and create a new file in `bugs` folder. After you have this, please create a bug on GitHub, too, and name the file as the bug in there, including #id too ## Open a bug From 51a0784bbfac9ede7b96911a0db1a9bc41bedd70 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Sun, 15 Dec 2024 20:32:50 +0200 Subject: [PATCH 083/150] Update Testing.md --- docs/readme/Testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 76276842..2d6b1bf4 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -94,7 +94,7 @@ Test specific issues from the [project](https://github.com/users/radumarias/proj - [ ] Testing on macOS - [ ] Testing on Windows -## Test cases +## Tests I created some [files](https://drive.google.com/drive/folders/1N-2KhGNo7f23tQ9Si4yWa9dlFtxUnsoM?usp=sharing) to keep our tests until we migrate to browserstack or similar. From e81243c82f9fb4fe8045721c0ca9a4c39b21c9d5 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Sun, 15 Dec 2024 20:49:27 +0200 Subject: [PATCH 084/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e156b93d..33ab7c93 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,13 +35,14 @@ defined in the Apache License shall be dual-licensed as above, without any addit slow, but please give it time to complete as this helps to fix any issues locally and not rely just on running `ci` on GitHub when you create the PR 13. Commit and push your changes, and if there are any errors, fix them before you push them -14. Create a `PR` back to the `parent` repo targeting the `main` branch and request review from +14. Create a `PR` back to the `parent` repo targeting the `main` branch with the title as the GitHub issue title, including `#ID`. Also, include the link to GitHub issue in the description saying like `Fix for ` for bugs or `Implementation for ` for features and others +15. Request review from owners of the repository by adding them to the `Reviewers` field -15. In the project, move the item to `In Code Review` -16. Monitor the checks (GitHub actions runs) and fix the code if they are failing -17. Respond to any comments -18. **DON'T MERGE THE PR YOURSELF. LEAVE THAT TO REPOSITORY OWNERS** -19. In the end, ideally, it will be merged into `main` +16. In the project, move the item to `In Code Review` +17. Monitor the checks (GitHub actions runs) and fix the code if they are failing +18. Respond to any comments +19. **DON'T MERGE THE PR YOURSELF. LEAVE THAT TO REPOSITORY OWNERS** +20. In the end, ideally, it will be merged into `main` # QA manual From 0f353a8f2b2f6798d0929df601b254e5cb19fe42 Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Sun, 15 Dec 2024 20:55:19 +0200 Subject: [PATCH 085/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33ab7c93..64967e2c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,15 +34,16 @@ defined in the Apache License shall be dual-licensed as above, without any addit with `chmod +x .git/hooks/pre-push` .This will run when you do `git push` and will make the push quite slow, but please give it time to complete as this helps to fix any issues locally and not rely just on running `ci` on GitHub when you create the PR -13. Commit and push your changes, and if there are any errors, fix them before you push them -14. Create a `PR` back to the `parent` repo targeting the `main` branch with the title as the GitHub issue title, including `#ID`. Also, include the link to GitHub issue in the description saying like `Fix for ` for bugs or `Implementation for ` for features and others -15. Request review from +13. Commit the changes and make sure to include the ` <#ID>` of the GitHub issue in the message. This will make the commits visible in the context of the issue so we can clearly see a clean history of all changes +14. Push your changes, and if there are any errors, fix them before you push them +15. Create a `PR` back to the `parent` repo targeting the `main` branch with the title as the GitHub issue title, including `#ID`. Also, include the link to GitHub issue in the description, saying like `Fix for <link>` for bugs or `Implementation for <link>` for features and others +16. Request review from owners of the repository by adding them to the `Reviewers` field -16. In the project, move the item to `In Code Review` -17. Monitor the checks (GitHub actions runs) and fix the code if they are failing -18. Respond to any comments -19. **DON'T MERGE THE PR YOURSELF. LEAVE THAT TO REPOSITORY OWNERS** -20. In the end, ideally, it will be merged into `main` +17. In the project, move the item to `In Code Review` +18. Monitor the checks (GitHub actions runs) and fix the code if they are failing +19. Respond to any comments +20. **DON'T MERGE THE PR YOURSELF. LEAVE THAT TO REPOSITORY OWNERS** +21. In the end, ideally, it will be merged into `main` # QA manual From b21a204cfd6dd62766bc89646a781f165d5acfc6 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sun, 15 Dec 2024 21:09:30 +0200 Subject: [PATCH 086/150] Update Testing.md --- docs/readme/Testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 2d6b1bf4..cbc4cab5 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -23,7 +23,7 @@ The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month fre 6. Goto terminal in the browser version of the VSCode editor you're presented with. It should be at the bottom, or open it from the menu `Terminal -> New Terminal` 7. You can find the menu in the top left, with 3 lines icon ![image](https://github.com/user-attachments/assets/48681023-e450-49b3-8526-ec0323be0d40) -8. Install Rust and create a `tmp` folder, which we will use to copy files from our machine, by typing these in terminal: +8. Install Rust by pasting these in the terminal: ```bash apt-get update && apt-get install fuse3 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh @@ -36,7 +36,7 @@ The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month fre apt install rustc rustc ``` -9. Run this in terminal: +9. Create a `tmp` folder, which we will use to copy files from our machine, by pasting this in the terminal: ```bash mkdir tmp && mkdir mnt && mkdir data ``` From 10beb818473d00031ff54a1b3becad13a1b74901 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sun, 15 Dec 2024 21:13:21 +0200 Subject: [PATCH 087/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 64967e2c..8e8e38c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,16 +34,17 @@ defined in the Apache License shall be dual-licensed as above, without any addit with `chmod +x .git/hooks/pre-push` .This will run when you do `git push` and will make the push quite slow, but please give it time to complete as this helps to fix any issues locally and not rely just on running `ci` on GitHub when you create the PR -13. Commit the changes and make sure to include the `<title> <#ID>` of the GitHub issue in the message. This will make the commits visible in the context of the issue so we can clearly see a clean history of all changes +13. Commit the changes and include the `<title> <#ID>` of the GitHub issue in the message. This will make the commits visible in the context of the issue so we can clearly see a clean history of all changes 14. Push your changes, and if there are any errors, fix them before you push them 15. Create a `PR` back to the `parent` repo targeting the `main` branch with the title as the GitHub issue title, including `#ID`. Also, include the link to GitHub issue in the description, saying like `Fix for <link>` for bugs or `Implementation for <link>` for features and others -16. Request review from - owners of the repository by adding them to the `Reviewers` field -17. In the project, move the item to `In Code Review` -18. Monitor the checks (GitHub actions runs) and fix the code if they are failing -19. Respond to any comments -20. **DON'T MERGE THE PR YOURSELF. LEAVE THAT TO REPOSITORY OWNERS** -21. In the end, ideally, it will be merged into `main` +16. Request review from owners of the repository by adding them to the `Reviewers` field +17. ![image](https://github.com/user-attachments/assets/5ac0313d-4175-44d1-8d1e-d18da773ab32) + After you've created the PR, go to the GitHub issue, and in the mid-right of the page, press the gear icon from above, select `radumarias/rencfs`, then write the PR number and select it. This will link the two, and when the PR is merged, it will close the issue too +18. In the project, move the item to `In Code Review` +19. Monitor the checks (GitHub actions runs) and fix the code if they are failing +20. Respond to any comments +21. **DON'T MERGE THE PR YOURSELF. LEAVE THAT TO REPOSITORY OWNERS** +22. In the end, ideally, it will be merged into `main` # QA manual From 75439a82f933f74a9804d1e32de7687ad8debcbe Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sun, 15 Dec 2024 21:14:23 +0200 Subject: [PATCH 088/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e8e38c0..f08ed5ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,8 +38,8 @@ defined in the Apache License shall be dual-licensed as above, without any addit 14. Push your changes, and if there are any errors, fix them before you push them 15. Create a `PR` back to the `parent` repo targeting the `main` branch with the title as the GitHub issue title, including `#ID`. Also, include the link to GitHub issue in the description, saying like `Fix for <link>` for bugs or `Implementation for <link>` for features and others 16. Request review from owners of the repository by adding them to the `Reviewers` field -17. ![image](https://github.com/user-attachments/assets/5ac0313d-4175-44d1-8d1e-d18da773ab32) - After you've created the PR, go to the GitHub issue, and in the mid-right of the page, press the gear icon from above, select `radumarias/rencfs`, then write the PR number and select it. This will link the two, and when the PR is merged, it will close the issue too +17. After you've created the PR, go to the GitHub issue, and in the mid-right of the page, press the gear icon from the below image, select `radumarias/rencfs`, then write the PR number and select it. This will link the two, and when the PR is merged, it will close the issue too + ![image](https://github.com/user-attachments/assets/5ac0313d-4175-44d1-8d1e-d18da773ab32) 18. In the project, move the item to `In Code Review` 19. Monitor the checks (GitHub actions runs) and fix the code if they are failing 20. Respond to any comments From 7406975d31ec138608a5530bf2e1d8e7b82e29f7 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sun, 15 Dec 2024 21:38:50 +0200 Subject: [PATCH 089/150] Update Testing.md --- docs/readme/Testing.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index cbc4cab5..5674fa35 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -38,7 +38,7 @@ The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month fre ``` 9. Create a `tmp` folder, which we will use to copy files from our machine, by pasting this in the terminal: ```bash - mkdir tmp && mkdir mnt && mkdir data + mkdir tmp && mkdir final && mkdir data ``` ### Each resume and after the first setup @@ -67,14 +67,14 @@ Do step 11 from above. ```bash git pull git checkout --theirs . - cargo run --release -- mount -m mnt -d data + cargo run --release -- mount -m final -d data ``` 6. Input a password and confirm it the first time You can now perform two types of tests; see below. In both cases, follow these steps. 7. Copy files and folders from your local machine to `tmp` folder in VSCode -8. Copy files and folders from `tmp` to `mnt` and then do your operations the data in `nnt` folder +8. Copy files and folders from `tmp` to `final` and then do your operations the data in `nnt` folder 9. Make sure files were copied successfully by right-clicking a file and then `Download...` and save it to local machine and making sure it opens correctly #### Exploratory testing From 4a6859006c453d2d05defe7b0c378e7435f8e522 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sun, 15 Dec 2024 21:46:45 +0200 Subject: [PATCH 090/150] Update Testing.md --- docs/readme/Testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index 5674fa35..db83c5d5 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -67,7 +67,7 @@ Do step 11 from above. ```bash git pull git checkout --theirs . - cargo run --release -- mount -m final -d data + rm -rf final; cargo run --release -- mount -m final -d data ``` 6. Input a password and confirm it the first time From 76be08359cbb3dcc78baf2fbd1484c78cc6a2c65 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sun, 15 Dec 2024 21:53:42 +0200 Subject: [PATCH 091/150] Update Testing.md From 8285fc99001d498c7c9435f0a3ea23af2535092e Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sun, 15 Dec 2024 21:57:26 +0200 Subject: [PATCH 092/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f08ed5ed..2a8fc94c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,7 +36,7 @@ defined in the Apache License shall be dual-licensed as above, without any addit running `ci` on GitHub when you create the PR 13. Commit the changes and include the `<title> <#ID>` of the GitHub issue in the message. This will make the commits visible in the context of the issue so we can clearly see a clean history of all changes 14. Push your changes, and if there are any errors, fix them before you push them -15. Create a `PR` back to the `parent` repo targeting the `main` branch with the title as the GitHub issue title, including `#ID`. Also, include the link to GitHub issue in the description, saying like `Fix for <link>` for bugs or `Implementation for <link>` for features and others +15. Create a `PR` back to the `parent` repo targeting the `main` branch with the title as the GitHub issue title, including `#ID`. Also, include the link to the GitHub issue in the description, saying like `Fix for <link>` for bugs or `Implementation for <link>` for features and others 16. Request review from owners of the repository by adding them to the `Reviewers` field 17. After you've created the PR, go to the GitHub issue, and in the mid-right of the page, press the gear icon from the below image, select `radumarias/rencfs`, then write the PR number and select it. This will link the two, and when the PR is merged, it will close the issue too ![image](https://github.com/user-attachments/assets/5ac0313d-4175-44d1-8d1e-d18da773ab32) From 262f56dfa30649fbe83b4ccc56e9218505ba5bfb Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sun, 15 Dec 2024 22:24:36 +0200 Subject: [PATCH 093/150] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c2f71968..c0bc109f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -18,6 +18,7 @@ Steps to reproduce the behavior: 4. See error **Actual behavior** +A clear and concise description of what you exaclty is happening. **Expected behavior** A clear and concise description of what you expected to happen. @@ -25,6 +26,9 @@ A clear and concise description of what you expected to happen. **Screenshots or screen recordings** If applicable, add screenshots or screen recordings to help explain your problem. +**Logs** +Collectt last 100 lines from terminal and attach those to the issue. + **Desktop (please complete the following information):** - OS: [e.g. iOS] - Version [e.g. 22] From 19e03909bfc82ee64bc698b59fe49c294f6414bf Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sun, 15 Dec 2024 23:29:01 +0200 Subject: [PATCH 094/150] Add files via upload --- website/resources/download.png | Bin 0 -> 4444 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 website/resources/download.png diff --git a/website/resources/download.png b/website/resources/download.png new file mode 100644 index 0000000000000000000000000000000000000000..b23b3227687817c97abbf96e41411f30fa2759ad GIT binary patch literal 4444 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 literal 0 HcmV?d00001 From 0cfeeefb799db3368a1359593daf66fa4882a82d Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 01:02:33 +0200 Subject: [PATCH 095/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a8fc94c..c434ed66 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ defined in the Apache License shall be dual-licensed as above, without any addit with `chmod +x .git/hooks/pre-push` .This will run when you do `git push` and will make the push quite slow, but please give it time to complete as this helps to fix any issues locally and not rely just on running `ci` on GitHub when you create the PR -13. Commit the changes and include the `<title> <#ID>` of the GitHub issue in the message. This will make the commits visible in the context of the issue so we can clearly see a clean history of all changes +13. Commit the changes with the commit message following the [Commit Message Guidelines](https://gist.github.com/radumarias/5b5374f3ed022c99d617eb849aafd069) 14. Push your changes, and if there are any errors, fix them before you push them 15. Create a `PR` back to the `parent` repo targeting the `main` branch with the title as the GitHub issue title, including `#ID`. Also, include the link to the GitHub issue in the description, saying like `Fix for <link>` for bugs or `Implementation for <link>` for features and others 16. Request review from owners of the repository by adding them to the `Reviewers` field From 5b4ead0b4c1c22bf855f912fc9dfae3de75c2816 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 05:49:17 +0200 Subject: [PATCH 096/150] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c434ed66..af4bf77f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,7 @@ defined in the Apache License shall be dual-licensed as above, without any addit 14. Push your changes, and if there are any errors, fix them before you push them 15. Create a `PR` back to the `parent` repo targeting the `main` branch with the title as the GitHub issue title, including `#ID`. Also, include the link to the GitHub issue in the description, saying like `Fix for <link>` for bugs or `Implementation for <link>` for features and others 16. Request review from owners of the repository by adding them to the `Reviewers` field -17. After you've created the PR, go to the GitHub issue, and in the mid-right of the page, press the gear icon from the below image, select `radumarias/rencfs`, then write the PR number and select it. This will link the two, and when the PR is merged, it will close the issue too +17. After you create the PR, link it to the GH issue from the mid-right of the page, press the gear icon from the below image, select `radumarias/rencfs`, then write the issue number and select it. This will link the two, and when the PR is merged, it will close the issue too ![image](https://github.com/user-attachments/assets/5ac0313d-4175-44d1-8d1e-d18da773ab32) 18. In the project, move the item to `In Code Review` 19. Monitor the checks (GitHub actions runs) and fix the code if they are failing From ae65de4911657c45d9e77aea0db5f2e62f5de79a Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 06:03:53 +0200 Subject: [PATCH 097/150] Update build_and_tests_reusable.yaml --- .github/workflows/build_and_tests_reusable.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index f3f7bd02..9677f1a1 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -58,6 +58,10 @@ jobs: if: matrix.os != 'windows-latest' run: cargo test --release --all --all-features -- --skip keyring + - name: bench + if: matrix.os != 'windows-latest' + run: cargo bench --workspace --all-targets --all-features -- --skip keyring + - name: test package if: matrix.os == 'ubuntu-latest' run: | @@ -102,3 +106,9 @@ jobs: run: | cd java-bridge cargo test --release --all --all-features + + - name: java-bridge bench + if: matrix.os != 'windows-latest' + run: | + cd java-bridge + cargo bench --workspace --all-targets --all-features From e3ccedbab409cea586cc94409a227bff187e66e5 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 06:11:38 +0200 Subject: [PATCH 098/150] Update build_and_tests_reusable.yaml --- .github/workflows/build_and_tests_reusable.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index 9677f1a1..ae206ce7 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -51,9 +51,6 @@ jobs: -A clippy::type_complexity shell: bash - - name: doc - run: cargo doc --workspace --all-features --no-deps - - name: tests if: matrix.os != 'windows-latest' run: cargo test --release --all --all-features -- --skip keyring @@ -62,6 +59,9 @@ jobs: if: matrix.os != 'windows-latest' run: cargo bench --workspace --all-targets --all-features -- --skip keyring + - name: doc + run: cargo doc --workspace --all-features --no-deps + - name: test package if: matrix.os == 'ubuntu-latest' run: | @@ -96,11 +96,6 @@ jobs: -A clippy::type_complexity shell: bash - - name: java-bridge doc - run: | - cd java-bridge - cargo doc --workspace --all-features --no-deps - - name: java-bridge tests if: matrix.os != 'windows-latest' run: | @@ -112,3 +107,8 @@ jobs: run: | cd java-bridge cargo bench --workspace --all-targets --all-features + + - name: java-bridge doc + run: | + cd java-bridge + cargo doc --workspace --all-features --no-deps From d0a044a30fcb021f4c34e4f07c9651dc1d7956a9 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 06:50:01 +0200 Subject: [PATCH 099/150] add run bench to check-before-push but keep it commented out --- scripts/check-before-push.bat | 6 ++++++ scripts/check-before-push.sh | 2 ++ 2 files changed, 8 insertions(+) diff --git a/scripts/check-before-push.bat b/scripts/check-before-push.bat index f4ff8206..d63d24ca 100755 --- a/scripts/check-before-push.bat +++ b/scripts/check-before-push.bat @@ -28,6 +28,9 @@ REM if %errorlevel% neq 0 exit /b %errorlevel% cargo test --release --all --all-features if %errorlevel% neq 0 exit /b %errorlevel% +REM cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu +if %errorlevel% neq 0 exit /b %errorlevel% + cargo doc --workspace --all-features --no-deps if %errorlevel% neq 0 exit /b %errorlevel% @@ -68,4 +71,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% cargo doc --workspace --all-features --no-deps if %errorlevel% neq 0 exit /b %errorlevel% +REM cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu +if %errorlevel% neq 0 exit /b %errorlevel% + cd .. diff --git a/scripts/check-before-push.sh b/scripts/check-before-push.sh index c5f54083..aad62c74 100755 --- a/scripts/check-before-push.sh +++ b/scripts/check-before-push.sh @@ -21,6 +21,7 @@ cargo clippy --all-targets --release --target x86_64-unknown-linux-gnu -- \ -A clippy::missing_errors_doc \ -A clippy::type_complexity cargo test --release --all --all-features --target x86_64-unknown-linux-gnu +# cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu cargo doc --workspace --all-features --no-deps --target x86_64-unknown-linux-gnu # cargo publish --dry-run --allow-dirty --target x86_64-unknown-linux-gnu @@ -43,5 +44,6 @@ cargo clippy --all-targets --release --target x86_64-unknown-linux-gnu -- \ -A clippy::missing_errors_doc \ -A clippy::type_complexity cargo test --release --all --all-features --target x86_64-unknown-linux-gnu +# cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu cargo doc --workspace --all-features --no-deps --target x86_64-unknown-linux-gnu cd .. From 43c108cc46f1e0dd803f9cde933fc25f1c2ba6a3 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 18:23:54 +0200 Subject: [PATCH 100/150] Delete website/resources/slack3.png --- website/resources/slack3.png | Bin 5596 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 website/resources/slack3.png diff --git a/website/resources/slack3.png b/website/resources/slack3.png deleted file mode 100644 index 044c657d0727750b7673c64b218d8c454a40f30e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5596 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 DcXuYL From d9ca8e8331b15ced785e9ab909c69429f5ada30b Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 18:24:40 +0200 Subject: [PATCH 101/150] Add files via upload --- .../resources/slack-icon-256x255-6c91kojx.png | Bin 0 -> 15599 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 website/resources/slack-icon-256x255-6c91kojx.png diff --git a/website/resources/slack-icon-256x255-6c91kojx.png b/website/resources/slack-icon-256x255-6c91kojx.png new file mode 100644 index 0000000000000000000000000000000000000000..4e6448938684e25ad6983b9d81b52c57c956b95b GIT binary patch literal 15599 zcmZ{LRa_lEwDla^p}4yicZY*RDee?4?ohPoDem4<T#LIGin~K`cPQ>I=i`6Br~7c{ zA(_eS$>f);$s~L26``suhmJyw0ssKIg1odk0D#_30Voo}TT0%AME;h+SxG2K06<L~ z>a!{Q+cl+`yt)zqc+&#_Bp3i5--;mr0KlCC01iz6K<Fm`5IASFsENGYKy*`3l0n=< z#lR-v_1Uy<c`HKrtSBe_mcJbjQmp|1Abh1DEurbPbmE7cL1OU`;EfzknZp?Q1*va4 zY(?%j{5yDD8MOp;EG#VCU3J{zFSx}y;SiYjcT_o~0oJ*!A&tUG$BwOUiN~2oT<TE6 z;>z8_bN0THB|ocZ;;Mbx$F(Z4>|cxaTvy{yB4@7R^f+>cd3}Xr*cD4`mU^oi$+Lda zdW$N_Gr#c{U5c&iC};+khVCEdFYflY&f4Rk?eg`dDVuiU1m-v1&tIb$Z323;<%iGC z_tE9&f70E2xixxDe_k&&E}G!lwo0>j$_*?SwO9{XkI1eoS}gQ)Q!4Vq51=L`od(P+ z1@n}aQBzL<^Om0T{h`5sRy3|`odHntgKYUT>Iyj&%H4RAG$ip)$oTDcL{E-o-poeR zbc}@K{p$&nf~4@%pgoK~du#)8_c%KnjId>)p_ZE^-Z>^(?XhS#b<fBZVxiYUiSRbC z>cyI}auOi?bOcs158c)^kb|MJ^_fTD$`Q<`Y!dsI{Ns*gpNwPAYjSV)mzduyU7eLS zW}C#ua$Vqg-!SkUWr>wO?Fl%l^o!EBpnSp#(WQ@3N-xojc2-n9E8*9)e|_W)<;4n) zwNsZ%yIR-~brTX=`D42vTS2CBSeiOW$N8qtF)}7I1n_a<C}!UX?*m=V!?v){7$|WS zBn8rf<yrb((~a`ir+ogkUGrZzT@!hz{Pd&uyG3}$cw~E2espXJbwEOB@eo({+|0^i zS=r{78G2o$9}-MD17bsAm;U)|lz0(m#dgTtJg}OFV-zI7)cJ;QeiA;3-t-RZHyI=c z5V;EYvBI#poO!YWy{OTLG!fJfSj=k$2dQ_z2o~f1tNX`&P&a)~c5Qf_dA&+2?d!$r zrsU?}7UNdoHnxm~M01=)cjGYg#jy+en)a)whGCtEG&D$0-Ev!YT`4))Z>*M&g8#`* zUb%o!{ezX4kV3lJ34MQpcV@NP%{N4q4QVdX@tH+~tQzLQWjtJLcnLLH7VIIk=m)cE zwJUD!pwpPs8hN`x{5i;6cgS@pr5yokv-bUZ>nB=wzI6%Gn}KUShr-2Skt;cB!)TZd z8(d&qY1R`@KYY>0U{a7{Skq3u=|GeB{lB#puQ<0Vw+Xj1T3l<^$a&eps#$JJy*9!C za*H1`ek_UoWAL%WR=#DI$eIT#(k;aNr_=JH@<X2s85ouy(aAmD3k`ICj8U<wowUyJ zDfB7g&1uNt&23oxNLYYkcaMfLmAyTI!`j^MTjVl><}7te%T=i3*q_Z#sUSbKg;Vjn zCziw)7uPCYaN)DHxRsq++`7bnHC${hO%hM(tI>YuhDwxIH?rsPxz|yaFyyK(M4W+R z$@WK+W~WsRUsN`eQE-LN8KW(aJcG7S0>zU}l(}nT1RH74oMWsjV45u>nc>J@^6gpq zQ3Vke(-aTO7`-~y=JtN}c+w2;2w#y7o-eUP?JP)*TFtZeWs?_ttig%PFsyf>u|7*o z8&P>Iu|cR0t^a`=TF|gM8X<2d9$JsB6o$&eH^4B;=ic~BYM10kexhx=pOyK2kW0Sd zx0zDzHuAjuB!4(9oyZbb|Liq+*b2^6WU`k}XuKXdl=pbH@W!&!_B!pZf`xEhrG6jd zXSaKOPygutSsXN-ZJ2@`uR)56a@8cL-(zzMrlXsxTlk1I_%<kHA-5%gT8lwv=CPOd zJ_;{LfBEAdC_2aLF_<cyD&3nGALV>m6l1yl(UAh(@I_t;QCS<+EVl~Z1+)2v#^q2# z5wP!YoRggoN0v4RpP~I@(^1Kc+BZzoF&P>qJXX*8uoe;sG{}T5aH;WYEXm;$>R$^K z8YwzG1qKRl{ZeePD~8dg4hsmyzW3eY@r^Z?szg&n6;UQ_BKTJXabYnP$os>*cYKcx z7vcL@^M%Y7)+f#3FnW$Wh8x^d_M~k=F;!|5oZh2N+MVy~F<|{OlZEsq-G1j*Hnpev z>$Ga#ti9I$wk!0wG&h$h1C{t<CuL?;ZB`tQzlXUcMn%s$IZNmmiDNmjM<5W4Nuy6M z8J?W`KwtJTc~<31tTk5sLcO4+f+z~bqo{mPU#Zyl>Bk=AjmUORiSJQC*5eMA#0v@J zGD7(ZjaQSoYTrt;6sk+IyEUNnz6)a^GwL4HsseGPbImR5giFAU$Ul->d@I7kcR?~r z+E49eEwX|=gcEa3*-Y|>X_BJe%6G@DC@#KPozq}s85Y}kQ+J}7nF4tdq-{`5KMRwb zYb+<s^m4sk;P=@dP$j2)84rKv(kBuvaVuYp!W*^ToZ{AZWEe)@@JN|_^vgYrLJi_L zMh@BXz6&kY?tN!^={+MN9$9|KxDe#K!n}_2ikglZ;(SK<dyrRf+$~s|CGyPol$BTh z+%l;XC#s5al+ZG9;v4JmfS8fR5e0{r3SS@QSUq@HBxp6;GvuU&A^i#JqP~6ozHN<) zeFS@ZgRxH>Jf`M@Fc6uZbT6ae*Tqt@!mm2b*YUBUe)hXJ3On!joEg<arGYLrjIAM_ zqd)_5?1B<DILwq07VVq-jAh&Y)~Yd%k=r&^NsU&UkhCxVkVSEqwv_|~=uwx1XMTla zhodZkD<?}|Ky~GQ2mzNY-DIT8*-V2hCHr4jr{tKjcm73PX=LY1$`^1=iXX53QCb|~ zfsb(AjQw)$Ug9cU=FKM-%=eP38cOi%!jfxcYC!F)1K2g}r=_0e7Z%FQnYq0UoVd~m z{Pad{yQa*l6|-2eLozw{O`^(6*N-V!gS$V2;?sWMH_+J)wB6IFse1+eJ*2%`5Pv^7 zJR2hDruSjLpnlv}m~H%-befcov7YXT==z@|<)@$X_lkAp$K!%-EriDE3CV`=3coQ3 zmd9!zRbezL{L{*wNws9TGEF0-WHKX5n6IfN=58gYlez78rF@qd)R0(YLGg2SkAw8v z8w&}9^-uhs8113-U7!pzW+VyqyPlAjBQN82HinUfEh@2CGW1KqDcZfU$jBckwcd>T zE5o#R&&H<ZJc_5ei?=hoA;M1rwhBm-Su7u@=vmzb(_k2MHNO+!C_V0^%=Vy8yDb*8 zmJAGGbKeh5sJXs_SxK6)f+U7U3SszB>NX_lxW{~<Qnd`6K<g^>#e?sBdK^+A^N~!T zy}Ndu@eR(YSk_yyE?Bg$qnPH}*wqW7V|W-`N|0<viJCq8^kje)Q}lqbU5u)#^Z8SD ztAL*uxZY;Kd7Mq@L|i)ga?uZWyQpT0R!dOe5P8ZgvLP+qTDVZx#VH`c^`4K+|4;`d zQ=fZJvP$f&k8fDuipv+c5;*GNF!{;J7ErDz!mrVIj|$)@KAikwV<jcMK@;i}LX>rP zg|wixK_oX!#xG<#`yO!FMuW<kXqXROocH!Q(7GAnmKaEb-W7zYAszP!W-I%bwIKdo zZE4EpdX~(15JY2uORR33R^uuB*s79z;=}80im7$8T3U3c&i9?2aJx06C;mKSD;=F~ z>Q!E3GN(^;Sh*P~<;d)J_=C-wu*&5o$NSol=q`60Ny;v-oT(2H7K`GDPQDVkAq1r_ z7}c#ke-aSGHYIQh$+9>U?U|84Ti@V&abj?f#4(*((j{qTx}(zAFB6SoWkDzI#Iou? zbw6%)Id+w1sj!|#kKJp#hccE7Y@tVmkc6R^#u41UEi&jKK3Rntc=f<m2!6rWKrL>| zVduKzg0$C(8UbrlTz=lOoegVqEKJ-nihFhx$V&nm;ZX{2{Zm7T_npy)>&;9bL1MCA z*kndcO%XSO#EWN;m&Up@u~~%Js^%QY{?&s3F?lbnrmR6S;|}-8p<sXMX;M8bDjx=q zr*%$q;L7R#2ID1Saiyw@T}?wV=L9d@Rn-acs#g3=K$3U<@(a(8^q?R!7^a<wHRJ&B zhxqSB5uS(3N9-lS5w%}F<(j=upCs9Ke<3CPbc=}lh=f}YfiRXdQO{Z>#`l$u_ov@u zsK;e(pUHd*d#mP%G5|aU{wszHtaB@V@DqgZGnzn~D)oEkWRgmLnV(=tN=mvs5YDYj zN^B6Z*Qpa6oA5Y<iDiCcCLf1OOjBEk^gr!I?`7`gmdWiJh!@iq=L}GIA~$m8SRa~) z?~)G`*TRV90*lb7)DvM+8LCnc1)SW9!YL3nJ(YCFd0u7`Ti+z=L7eFkzqw&NmymiT z_?A3(NXbXoW0dMCNd$3ir{cnCaGo>dZz#zUKE%V^>kDK;H2Gkkhg2?8_lv)9aGhdO zX4yK<-xk~K8Bg0}(|lOUe2C9RAi<?zzf8qW0!35rHz9JLC#ON|=Hxb>8?iDLiGy)i zKmrwXNc)=|>}N<lK~gTd+e%%|EU2kAJ3^lyyUrQQoh!qhT7GoHcjr6rA!2sAjGPAM zuf5P@Jg>E#pUh%*@g8LbSs}<+-(mFVQbz|uq5{82%+_eKs}f44Vbi)N{4#uzXC-(V zW&ZeKtnAga@=*m}ZD)(#m>B<7X4B35dVBQS$=?V@+)A+Nd82SdV16x{99p@o-}!Sq zJR6rdh8%k<EQXKOrpH13_b5}-fg_U(doXKg7-WPWKCurc3_(Uu@RxSo%WXHRZ(tg! zD)Y`ex%iR@(%qhzqSy6vX{f&sQY`zo3x0;bSf~HuzL9>^8D*hFc<pQ1-wei!`tbRA z?4VT%v!{cfuZu7ZiF7v=MpMYZnq67>rU)+G?~M5RnVDdtujCPloj7|`O$;Gl?_Ns> zPT7Vn`+|QSfvq8A^Q8v&isB=_`2f_@oHb+pda!`7<EOHL3+9TW4;fz`a+g{^x4czU z1JKG?B3?<<?{s*pnmZv8;{VD64=>Wr7+{;!*{>T~a(*?cVdkGH?;~Jkh^6f_opgyB zk8ro;%$;%Zn!Rz(#MPl}E@ZI&ij5{G^876Y6eo^g`PKLD<g(YsINjRv1w%lXnU&g{ zBg;o=iyjd#<BrSC59XVNSxBRGnxPL-5u_oo370&zZfvN`hO48wp1L7FJAR&?=)=6? ziZc$s>u-}<iwMorKH-;?L+8Q8yTb8py#Zx)aX1}_5{0dce;2qq)Jc1AZ^;!#N#L3R zF0Y~GHzhWv{&{98tH@PMNB2GK(^Solc_}8@jLdSgU{B+{`;nrjCD=bTR*P%ykXsXC zf5wo)n1={uTa3>1%D=&`)ajuN2--E(Yi`Iuo>SE;vlq5$JMss(3;bU*Dh0^QDkmm; z^UCkdnZ8x5j}<+9gh}poT@TgmJAQR|fMqlWM>!dTose2LNAJ$EwfE)^C|)Sel~fVJ z2g2u3^ufCEGa8<nc4vlgdU`*cE=>`~$hL?Ak|2?24;2kvHWvPw?Gfuzf-uXlbmy8u z4-x*9B9W5u`%%f|oYZkPr_L@{@!ypa>ss7l@U=3(9S(e-X%82CMG$4t>{1h3bN*G@ z{Vcs>^2dh~<zS^TQz`kM1}o^i;d@kST|&MaR+YO7F6u)<WiHCfVaaRe;hNWDhtf&( zC!rIL;U52_{tCaTQe=So0|R-4gQ0FKj>ONV=hISTh*`=yGUg52K1uFJ)#5f)2C!+D z=cx9{^z6sS2P*#q`S#W-lE2T_**uJ=#!8^-PGW>l)<;xb${h#;tQ}>xFdN~XP@tpk zHN`2IOM;h=3}-c%33S%|{`+HTb|<@!LEMwD=t`6@-gAzqrWOdiZguM*%3w@*Vnx+r z`(JxF3#n;hFE@yz{=rZNU9;B}_|_7;K&KNPZ@w6l-=-6fmn6cf7P-!cn1omzjo!xQ z!^W1>0{41H(;}n-$degec|<{)!FM+Y<fk>l{zGlv!&O@-7eyR0&(f8Z$j-Mdlq}1k zfbQoeZ9~9d?Gm?nOQM~K9yCk#7aq9Sc`42f@YVm>F}p_%a+Vf@WfC73&f3|{)PTo( zbq009cbX@F$?ek#`Nbe^ny#&8{cahrbqW6Y`_!0fa}*N~*<7z&gxfF>RLD>|XfTM! z(C3dVSlbDQuHw=#Smtfls5abmUpT!^u{A{Uy_47}4HaMm3WLD{YUS|u^$VogX|a>* zFn$Gho#Al|ubRW5QC51}m~22Z@RKE+01?+%?N^TXSBu$zGD#^h{hLT}qcdYc)sVFw zsS5@7*G(9R6ygmk<=7uexFzW_zShJ;Xx#OM5zjJvU+aq|?vWHxvnq-!sFRb96*7iD ziVC4bfjoQ{d~v803@jH@j1bp^Y-~h`<00O_dg&{55>-gB0Rr^sAJE<xAQmN98DqDQ zQj~zV;$9T*za_36@c-Dz?KptYB!q}x%Wdvh{%0H}&84v-JQ=Cqsl&{<cVu;M>Zs28 z7~0m!>|cZ#RKxOI%gj~L1{ybtf-y-(1!w|w>jHX<_|b@PXm8S(I0K@To~(0cJ?B2p z{>=Yo=1oCq1Eb?9Tc`s#qjy%HBW!u2x<oFBE>f4ZD!pE(Eab#B`mS{Z-91$0scgK) zaK24m;SG0t^nlz@7}BM++B%ro_MAbbcDc&u$4?#TV*CVpX?B$(o<!U8Jp5nhc`(}E zrygh75aQ4;ph=*x0Dlr53<uxYklZI6!D+m-27i+sg8%G0I*&1pAS6lT)t3PA;p^Ra zJVkn*!M7ZenmK(EcIc}8hyDC1jcO1c;v$jxn}Im2R3$30Wx|{b8Y_J;RptZzdK@tD zgjoEo7<)p}Q=q3l$THwTnk;;vk&{0j74nF<3pdynVht(4pd>T&*u;FjfN48G#CttA zrHBEs0f!WN+LSzw2n9^My45AF!^kgwqz3`z+f_<teF&rmfHO!{oHEOS{EW2xPei>O zQAx+4ge29C=S!$s_|*=@fAA@0R;|Jyo|Y3~kw+Oj6=;q=7XR@y@#@t}v~ws<02lx> zxkO;IKGX(~30n~#I5}DFUmW9IcGL6Te_(!u(>iI~v4aJ89|uNko);AYq_cWWf|sHH z^X@n#nZ&^!M^WPH_If06(WRi@8if*tLIE-JvmC73cCpy+Bt0xY8(prn;1p71Fa4Mv z4PI$Y(fddm<PlM(_#mS5Uti)okFrV|wBukX!=Q>5+*o6@x$HZE-^bbRvuiQ3$Z$9N zHZ46=e$=2h%c{wn|816ynHr^!__NH-DZx0iaw?FMyhkD4&p*JFV4`{@m9GmO6t{QY z3&>Qs|7r!N+z3~?RBBf$eFbkbJn3Ur;?VyPLl1->Sga$7%(MoM5v|NgZzrv{5;!Ir zh5qs&5!n6~D(_H)<xj=LNTuHprRV~PN!Lh~G8aC6&JSwrB>V!!_QbA~>=&;YCxl9w zbzVH4{fm?QM2TLbPz-xR3n0*td(}hz1bk+LEc_^x#9G>4=yVSb1I_0a-mx|DcOQD8 z^hmw7EA1IC^R{re+wk5HI-Pm&1;2>nj@05VJ86Ff57c9TWFB*I0|}0i&kMhV;s6H* zp-LQi5t14YYuw^qK-S6v+**MPW{WCM$sJP8Z;)i!Si@0cMnG6($n|>cs~TUx=-|IN z^Ucdx4q@Jy?&m%?d=%c&_p||_YH&admXD!B*cq=Vnw-7VD*Jb3gBqmMV_%$^;`yIq ztZ7sW;$l+O6D-Sd3)=T*)V@hc_6W8tCjYRImxgpaL(zxs``kXbVr3CVa1U6vp#rsw zI;}pb!vXig0mTb<(=Ti5FBotd{l}g)61ntL<#Lcu@iU5zgU>(M+(Rju1L&<lzo0FD z`n9404@g(m2r7JhI!L|elS=JC;H@%dld!9F^3)%80{#z%EVd@SRKFaZaMOf;0`ifG zk67f_+~U5Ep2a?!Bx{pl{<7Qs8FP_;CPZFjYabM;-jfwNO5MB%!a1E9UY+!-wliii zYr`eSO={Q=xAm;?KV1J{w98%!e5oP}>98M4feN9MP!ok)7B=i^SNugTK6(Difw1g~ zI8zD~&n;Xo+gA~>ae>Q4jL2Ea?BMl;Efw(~R!2eNK4CEjSh-_R3c19^zW>nVKS}<1 zOy*DxFj#za)2g&syKFV}19PveosnE9N54y%ac#l{HUG?j^9IGg<4havVr`oWHTTb( z{uFo~{i9eIG)dvnGq257xpec~2KDPX@$2Ii1J4A(Z2VP{3%Il9R)uZWa{{;y`7J@J zPPDLA4G1LK09FD}FfRE9xvVwD>YYqm!->0e@^8^ye?tbIISm0-vCgyf7Ev6UUe=>o zL+P>>U3L(LTvDi+|I%eynMgzve#9mDBXw#EHqbO!)t!JB{*dyaz=08A1U0-@y{|`# z0&7m#lhqNpWP0w?cqEAycu48qaB4|JX9xVL!}QXeMUf$P^P-4hIv3u`U(bxJ@6bt@ zA45b82jMBb=j^p^nLGgUXm++KDF_iPACUXL!)of)!3}i2+K6Q<*)WTDkAe%FRv<y7 z`C?$UhDwblBA8FV?T+8{FF^;VWu}AH@ho=ZKO=4=;F9MUFFme?9s|-a8-l_FKr&{v zt^E<q2CR;!R<}c++w786XZA|%dPMCNo6W1vclb_fulXq(7Iea|Ev_aabh_PA(0`xN zH+X#2a*b~>eb7^3!CVdz!n5Zv8w1ub<*T8{CO6Dl?#=JiEY{`{onVB)fa;?2(SmHy z36vC#QOponNKj_~2oUEYb>5wa;Jze2hVP;=0{>$5pAT%v{^()4>B$j#H*Ufe2t914 ztQ+!L=0Cm&3$3jj-W(%=oR^0C%4Zf-pD2jH?qT$#-)g-j6pMU973$x+o>S-?bT~id zg*w30Ql))!s<K<**tn4AzmwLx%c&5G`qC_02G!Sce|n%&R6+(y`NN6I=^<Io0|d<2 zz*0lr*u%S0U<J>(SrHnNU<G(3>fIZ{GQeamkp>UQZ7mxX^%*Lb_1jzdom>4w7P9*Q zu$&s%4!%%C(+;%4<pa$+@eOgg2stMu6F|Xxa3ldSUNXvao+$DaOUw~K%WdsrO}?xu zifD4gU7l^siVSI3laM+M*gE-fVM=wF6sWZt#RE~<5;rO!n6GS-n(2grOyZ4KZ>Gic zKVPBfL4I8$P{lpZ+GS^Y$mTDI&E7eo&f)_)m43w3HfFY6nwy_+QGEMYY_bcmDCt0L z3he469Qi*$1Yxk*O$-}$tTDT&Ij7*|VPIv&?BlHG4tln-B@I=bsHO&xQT_Sd)$2am zA$(Nd^x*ck);Gl|(t93W&?+LdMlu@~%MQY6Vk?5$apI5>N?F8%xIxi#Q9C!765W~I zvTho~RT#9`UbmN<SbI`R2Na{6vx|-_+!5jRF3V^HiGv@}c7fw4+G^SyG0&b+kij}- zA%Oh572z|Md`dtmx~!~T^y@zhPtD}T_J%|D^F~Y7lGAo#7zh_d0CUIZ=4a@b#1)M2 z<7x{6xJ~?)4t?Y5p5o-1+5&|xF@_sS6Qxiu=8sw`)Li5NI!OT!&zm*G*ieAtiFo2X z*0TRzJ6g&J3Po!IH%klqw7>(%O~wsvNH0_=rdk1H5$2)jPpn7)3HkF=sw(;dMM=6* zflc0Zp)5$$(Kg;C6~y~ZBjMp}84K2m8dZboggOAgJ2aTT?M_ZTA)ycf&V+;(IO5bf zS7A2z|BjSnP|X80&erOxcm14xpU;86AX)%JnmCuhC$xVdof!Fgp+sj*N{aK|=u>!R z+D*nkSA9h+w%SmWA#0Q^JeI?PEMMqSb?D;h4gq5WGrPrOX3M46z5BAFy}izFy~0-% zRG`Ii*sY*(YXU7Y-Z7rjv<1j<G?qE&G^WJaFqxwZJ_Ka{gtEZ1;mxq0Z{`XZEE0V? z%e4U~1nN%^QIcefJQEC?Hn$7IOxPjM-FSHiTVTxm#Dwp%y?<D;+H4(^W~gZ1m)Zl( zV#k+?uV2Ek)VGaOqZ}OXVEp)i1hj}nTKiTVywlcyo{<H~;^c-dulFv)(rn)V{sq73 zCDbt`BL7B_Xi}7sIJz|_30hzNS~MraM)Jhy-MI6cN~2DL^<po*kBjNuMAu17<(m3< zGo$-?)P0T0%XF}N@A9SQN~wAFmUx7kR>|~tev;s9p?7!U?{j(Rhh{zh-}a9&&nRWw zIY%@dK|3Uz_GeIaaF9P=slOpmAg%B~y$kY1W>RGT%b}XD0{ea$K^BF|?5n!oj}HdS z(pP9^yy}fgX&}z`i$~ubf61IG2D~3uKK?S4ge1k2X^-^Ojysu5NsKRKglVGDVOuMI zn}1CZN+u!22fMkkF~|>}8`>cQFLCt|KmCRF1^$ta03anrn6*r3($aBJsP<?Xdh>V= z@d6mJ{nG%8_7n07VrSPBSPZgBO#zXa!!T0=TtrYd_SFCkjG#anP<;d=gtE+d=j&TF z=rBJJRsW%ek5b^$TCgEQ*eRJFKX%HFz^%ajCfzxIo$hf&603oet@?_TA^r#Tc>SK$ zn!y-zNuh%+{ZErPI#E-=`%sb{YL{?7%AmW(qE7dV&(6O49+S##?~bijVJcAxx)x&M zB<^>$;$6I`QnF6>Q+l99R3utdoR`GGbE|NmtKAJ_cd>izEJIi6c|5EDl-ve>u<yCd zU@#&`_nmqr2p){^c>NKq;7(0eFb{J*8I{bi5)T6qi63D~+5dv8%wB=*K^zU+d|@j^ ziZQz4@Be9Gto|Oo1SEWa766~zQ^j`BDvQyMyOQ!kl=v4gr@0tr*LV-d)P&Ua!>+6K zT9?r0sQNL#DK0SQp(x(1BGiCuQgu1QtJTW+BR`0bnhY!klp-YmlPSfuE%=-hXbLwJ zE8(HeEN5WI*`FN=3?fit`CIU2U8AZI8XDYsmJS%8e(Pw%D3P#gC$py`mCpr*mv>P7 z8Bs1%>0gijbC!W*x||u0am_QT&9HX5v@rz_X0UyIN<gk=f9?6m?r|Mgo3<${d3|E( zoSP=@yU~Yd^*V$EsrN(k^%TV%&ec&q@R4ixB8oP8proC<RUTsjyJu%Uj*0WbZ96GT z@L(p6YsHVy6N7<*fv_N?+7^6F=!BX|=fno;*;Le=kxmZ}oatys{rAO#>8|^vcK}fe zrpUZF0*xzs#h_;#X+4m!&l)WK$=IK3*nCEI;KO-UJD{~sgT@5>Yk#K5Kty(6LJ>Bz z>*!r>j0mMHyEuokqyfW4YDj-%*guG}&GDC09p|3DbIx!dspkq1grOu*gq&p&EDjk? z*$J4439vUuJqQdp#y%S!2lKF8#Z7Nw>0X)1Kb42BUCm&BJaktYMNFRYv*zM(hxbo4 zGMKk`F3hnvW6s}b`DaA97T~Xh1gK9*O|&5nZ|W#bxE}|+^K$gfkP-NBE7hBU<@khd zhGE7H4aP!hc!sG}8*3(Jzx4Rjq(J~cr9jpE7BGJeAOZZ{29RfkbU0{GRo7q&Teim@ zy8^KDWfb|~`IMsYMo4z=H8})Zar+gwHw=nurWospwU?c9-@lJ;hhoo1ut6lDeanTc zePUJ&zmi{s_$bxy5lXv+*y(b6R@VLe3kit4c%txfU&@Hb(eLzi9_zs5%3sFxQ;S~j zIDA4Mc$R+szE7x)np0vdQwM?u6L&h&D;Dt~!gtO-vKyGa8zkb9qfU{RIL}rz-9bgQ zl?4iB)qc!q#MtGYo9vwIouCdS%o8U*3^YKxQlgvg5{g#W{sjZowjEdwv2j23ed>ww z$|v16%-g9sOa>1+TT=Y4OFOh<9u(gJHFzgUkvA%A@12po&b%C3k8o{YETm8&IS(kX zC=k+Ufxz^Sh>nN1qb4-n7Zk=)>Uu)wlY~cbcA66%-ZQ+sbU?r3gN|0!K#V0|L-uKv zQ9;~mU9=<@9^&+HV2uz>XnTgELcj~>p7R(Gs({!I76r}K7(m4M?MO}JpE@c!N=LOL z0q2*0r=>=5+bheIROt4rw8+;G&jS~9R^ZQB7Fvl~T@4&?$%FpNgajzW&*n40L9~8F zcK*6nyu>E1WkUJ@9=@sK1%X3kXaA5t9q>!oD-CE@G3?rutcyS+e<5cB$TzHJsU3sU z6`9-bAQtjrxTpXz)?>0G1z{~EV|vIS$>*ClX91>@22ecknpZlXPYzZ50s5`QJye7J zN0Hb7U=bz%Op?t#bE{HV*LMr8d*iFE3;^A^veq0d-SQ49LS<rAV284$;MXCH$#LNT z)%K$IKZm8nfJS;CwmlTNyTTDy2WK8o1ym2J6R;xbqo{#H*s;u5!vT083Qm4lsa!`O z1g1?bXb;d_N8D(Af*%acYoGyQB|+zpbMWA$j;tqOP4FG|ttWcJot)n`RVV4+vw4r0 zaSlAG_}Qon0ycb<dXhS2q2K<5$yG*rqM^$HVsOw4{IZiO0#JO64<Rg1J)rA>QqWeL zdn*=lhYAB}2{5!>BE8>giho>)?(E`}LV;X|lVpfD*V&HBzQ9vEIS5}A&cOjx)sJ#- zgYkS8kj^X4iOu-O7z=>)TF9=W1A%ipExk2-nx^A(JM9G!UY}kycr9s6rG6vc&;xm* zac}QL8fhY7Lj)fZvBTKP2<=J1bpFjf4e9U-T9e)kl%L6o?fg}dqb#+bjcKv!XA1Hn z5bKGIJ5&M+{P+m%Hsry>tc1498h9pZNaspZg??L7FgXo!=}g1J*%#<8r6_K(GH5mk zs`9a)ucpVWmrD4-<t47I^BPewkePMoCsy<{1Yg0Ozot?rUsL$I-}@yr;S|4$TOX)f z{cME81;o4f1;?~)-<`tdQ$H#*TQevdGvp)$DUSTE(gMZL!w7ZWt??7Hff#u|h>^U3 zsCT4ydUS}r1oh*mx5yWaKK`4-7f_54UHAuQzq^;B%T-U5oyQAmdAqC@eBiem+-F#o zz`Ioq;?RfuRm|L59G)NBBwv4e{myy4i%5J4uRsCR)IEmoP&8b3kkDkKmq{RruKkX_ ze=dLDTCH{y`zA-~;{gxxIV9AXchs(<++JG~E~6J#y<z~>&gShO*!V5z=QoEznaw9K z_Xd@(Sy~2y><w(RqTDQO))9F{#ynIQmFl*=_it<rCGayRx)kd`7jBJifKUV2@C#WA zKujI1rn(cjxnmRnxUHeko_bLv?On+)aEW8!9?2Ln@sJN@He2K85*l{cl3IQdb88Ot zFEjaoNgB8tbt)?ioe7}Mj7lvu(?nZ@ZF$MYqI2l-fCtx&B7_7l6y$Vq$`ksjtFmQN zU>PpKj9)>KhIJ|#D@5mCuS|F1vjaVbZ?JriPoD3q6n2znf&i0|WF_!$199Mw>|Jj7 zb+KCIWeCO4@e8CHNQ}kvGFIquJQ<@0g{0B$e=Ag{{J_I24gY)I!dkrZM@A42WAOWX zQmgf0YWNx1D6Yrkp32W5JWGF$bdjr`{)$U%P&i5SbMqFdwIM-1@0<L604<H8Qcb$O zyVv$d^<{3yShFaq74o*FSJw!hmzkHFC$fG-4*s=FX=-fQ{IP>|8LCiy8+!-*<NA)G zrcFNU1FQN@q6g$~f1Q(bgGp(LEZTuBIl&S(7%&fZTNIM_z>r`wN=?dNgX^Rp!FA=R zpv-0cc7I-mMdAy3n@*~jA~D-wklm4K4eiiYFMSlxDbMif08zDC0IU2k#QR_)oZET< zL!trVjSrAE_=fI+UGQ&!<Yf3=BuEgP!p%Nns5h~#0pZ&!P!<@wKVA|LL*j|%X>tQm zmQfH`e~~xUMY_S+-++PS`-4T6S90;-AeZ!i$$y~&3h6Nc&5%pbKUl`(&apS|wZNg= z3wtS)f&8^ZPuntm_#0!1+y;fmk(89i0h+Pie+EE_+Ygy@6qp>H`0q4IyOPy!RDk#^ zqF*>sL^|lrg*by)Tx3P{5cU-+Q`YFY2xIaV=T}Hsma>V+VYMMUJE7AEf&=qeh6Vcx z($;&96c%iY&Z&r>oCVwaCX26-1xq*>ykJse8dJG7Tcy&&N~p(mVcO(kep)`@IPRrn z1L(nvF70?$dpJJBwClCgolVbZ&rePeXwX@07L3A&7EWD1Kh_{wNX4hKN%&5E3H4ZR zC4AP*)65I0SLF~m{4xZyS3+acAXMN(T8Y~xrV_ET&^o_Ci34jlNq7+5`ZG<?wbz^D zy4dDDmW73we|)BWWV<Q5Gu?kY!0rYGd_t@b6%KhpH_Yos(e`ziQd)pO2&+4kmB6y> z1?=ZYDFf@Jjzs`yFtwrcK}prKv?2D9;?F2FS)&}}ti@0VMgSx0ZESr+>&|<~^K+>* zNjA=%ePU4|C0g6?y^4zfHc+XojV;pvWl~drWpaxi<<o)*EXe#uaX4*-K&v6b@Ya%& zRM=d1{%SE;((j<Y*OI#q!GOmn83o~GO(vwoxBg}t(c>{`%jaZ<`wbLVV+p|I`KdUw z=1893n^jjbpS=*6TYP6+v`T%C+*O+SYXXB-4pxB_<Bh%$3SuWA+Dtczl&4{LRQ261 z>9BqZSk|-&uf0^G<r56wihjkKK-+=Ifnoh^A9YXvlbTVnsX{UPIlum0m9_zZ#OLtC zT8o{X4QtrE8UKxB!F7cb6Tqx>Ay`LBlD3@grpPNBs}(A{BnDRvnEz;yLaFQf$xhiX z6=(o_Oz<614k@^0dozWbeK~3rPmC^tri8@4o377UEIo<-yKDKJ$3jALx{+%5W<G@p zS;x~05PvgY6|amRf7TPpak6O?^59frQ0*oyV?a<d0;m0l%8wBkVXDhYBE%d-Hxys1 ziubc=KMadZyGd1-v~H$|c0NXL6D-Sp{j9o*4B@v`07wzuwgS3Mrf=lcb+G$h(Z3aA zne^J$5@L>#VLkhCLjT_l6JzNiA3=u~9p#mPz$rob&BGj+u1$*o7}Jqp8yXoK0foHU zzihL1@FUfFSC;5k4-Pv9mj97{2jYS?Ff;I0!H;!ytSAL0m0RC^E-U}Eu50FCuWiH& z4=%nDg0OfprUa=uC8utLly>O6eMzk)X`Sq%Veez*Vbt!_vdBdQn*$!fTJQk6h(i61 z!)hfOW-cJm9+~7F3#oQvUPrQkB%VhKy|#?jdSuzifs<##ZpOPxnS0aTIVU4RU#hgV zfad-6fjufmyhYmkAK6BILw~L)m+n;B-@TEhKMye^TnA_o{54l_tCz&X2sS4DKs=G* z`69inh8$le>9kD!AIZ<Utr&gG)8}!2I93^vcSdqk?K-@Xjnct+3jKEQd!8tK?g7)W za7Miq<u1j%*<^z~ffhop$7UP%b7y7SfXJSHIGfGlDQ^NzwLT0br2))u9(Fcnn<`2t zJf$c2snXZ3`uuMMU$E5cvd9MQ;D$91wiJ+_U!dRl61?8mLw%hMy)ez&mMJ0?wrgSS z*vcb;a7<{NQy{j)s{g$5^^=_ph{H1s6{ss8$ETa)W9cRhBoXlqm)FAvR940%oHLr7 zy=S4nQzWl5=l)hQrH8_C?YYGPcO3!b;xCGD7n%v%?4fQUGV1$3Z9!g~Pt>ASMM$k* zJ0M@&g^@&|Yu9w}xgMO}W9thu+ll-JqoCZz^^fUT=k2aAX~bt&R>suDh+$cmQP+u! zrM~SIW2U7<8#0+;JcHhd9>QOBs|}DTBEJP(jBs4;jxdirBN{}PLa7G@hR@oQZWk#j zjnrMJHvH~z!ySC_zoZHToYO(PO4>fL-&u%wWR=WOF4f0vhlobv7U6H%XHjCJ{s@U) z##~#@3Qb^VDUM!>%ijKP4Wi4R6P(&xH)uh%s}K9FFuU5zPv&R9VhPrXKT>T3=WC~; zAmJB5Z8sh38?qw4mq4YL<`VpG1ad*r2=eL)y<6M8^bBymo+1PGw@jSSt`x}ArMQ&( z8pPukx4y<8E#v@ZD-b35H4CBUTfkL`goWTMc3-AHKa%!|mqvwTa`Iwe`D;QX&S)|G zo7eS6-Kbh2)_Ex9^_geaYePDH-}5lk5Y#Ti4))H^XK4+10~0aC`RUBWKGCN86>~hS zpfOnm`TYoQveS*spJeuXwNv~SQrC|S(S>XlcUxaf_jQA<kd*f}lzvfFG*g}bOV7#l zhP^_s4$g($3t;>sSNb|gz;5R9T0=q}q?tY39reAk`d6fADIQYO%MT0G+BDD}g}}4a zL7S$kbgUTk5S3Ewp1Smz{24eUc>lE`!76ge7nZ#ID6K_S%&`>lr^8{5cP5x54JMhS zQ>Jq!SmLt-`~BGyrp8g`KCAhVRIIBL?0jETm*@T!)~7~{+Sfxo3$;qHx({OaoG_*J z@5o(rIBh5vOm`oaZ7Y%13j-C-v1v&kX_D4UCeFjF_Aa4D8}MsZ*=wrfF9Yh;=Uf!j zU|npkTTb(PVznYyJ?-%k@288>cBUY&Up~(EzTND9W8{U?50SmPvL(E|^CF6~?LefY zbS6Ll`k}J6CuDJtEVUPUOC{}OFzGgYee`m<MD%(Vkq8em`$UM^8@cTJ?rikaGOy6I zW*nCN?P6ZJL8T7ba2s2A@4c!#Ml358v5JHG0mFyG?_Q4+e6!rz=;*mvs%J&yfdzAJ zm|p?_#DO0t{&7YJDOl#aReb4ou|r7JZpGQ%>G~D?m_#lJ5327lVqv&aNf`%m#vbg& z^XQ{y+vA4hv1oU3S8;PB{`(<MU~X7@VWGyaigPY0lHpmL@FY!9>YmiWm)(-IWeO|` z)d;**#lvUaqOIn+o=jQRgre;py9o!@Xd0l^Z}2;(hxQ2}QB|yOZ0ZAYu-UH<#Lx>2 z@!H2^O1o^sX&ZhbY<vaY)QVw(b6Mj#xtKe2pRXjGu?Y1~b@pcj=(pgk$#zP41dCTC zZusx{=b<|4d9dFF9W`IdXE_n&Xdnm&_A}4k5$rcN#xmdQ3uOZtONzFYKItW@7G=sH zG-GXW2o($cxb+^9{&aSer}a`;^6s1DhC!PVB#rEEMi<%{!=R~V1I1lq4-T8-@qrxq zeWi?+6<+~>B_wN^YOzaTjjfs_WqsmSoglZuK4>ZuMp3PK@3NPY495j!Us@uqQ;I>W z>n%iIFAYOvBI=gg?J+=dCJcE<D5h=yZ6-1mcKaw<S8(jOWg@we6q2xQYKh_Y*Y}$M z=DRP1TRRMU?CMV?ey>4!v+ex*&q_3~JSIDIhB!IhRBUhphnWv0?DQwQc}Y+$S5~y^ zV%`n!)VDYhB!e7_nWv0I8b+x|R8Xw9<txTYPlz<d;eLmDwT;}g(f&d$)=$&MB}_c? z2B~a*#@y>`c(WCTPBk4q@tBL+n;35Ko9iPi`{B(p=Qvm8b%d4{lTho~*mradBm`_J zaTp6%eNw(oE;FFwefWkxcDI|M8tn(Zlb>2rQW+15ABePPi9UyUTX&J>m?D9DWHYjm z*lG5P^(LRioH&e*&lPJb#k5Zret_7mUN8M%hIL#((0oktU(phMcsuAa0=5{*eTOSN zLkvUez7>Z<Z`h>Y|2JfbIy_9(93OX(2Bg9U=>ymbZ+&13>qdz#4AVENGA5#vgY5jJ z$MQi-6G?aRdh<zIVW`?X7en|u?!KA6Qw?WS9O9!kaY8`5cVcYYXd8x(<{V~B7t)#4 zT<wYSbvE(=u7%G_SC|eXmJIaX>}-W&`1Z4!_ZeAngFm@fan)ZKFt=X$t>IX$2Rd7$ zVH}ofCE%{H`x}OI7UrbEnMEsOBWWn^M4PRqzD!tS9W~ZQvhH|=h~SB<pk49{ybqS9 zbr(sLv*yUOaqsiaK@*bpjj-gvp#6C6Y%y8&Z!zlOr8wtJhmbJfXioSiO{g%UV4>2k zij|(A(^RwLUrFPhBq;3$vX~YhvbSiRbh@mP44fg7DxK*YRe&V-0R9j=Or0g)(;436 zWvlYxEld)%)|+F$pDb95(fvX~wR>{g`==^DvaFw4mf)b;yL;8$iK3g>l&jEq{D@jV z<M9yTCDQ8m+$?-g15>okX}=8i$hcToxQidC5{8{6I@Q|qF|Y6a<9SP&JZ?8j0vfC} zJQ~~t1YqaX_b5@m;i$3tkWi?XH{S@%npu5KhU9C?QJj7UPE*J9J>;C7Sl>AumXtNO z8TYh<(r@*ARWUbVU61G`J~br?I(hwn^AjRJC?)!4Nyh>UbM_{wHfFBm(C=1x+qCk= zgIq~2<l+|rL8RYYoi%i-_5rY`340N-ZOLx8S#LpgwuhQx!-8d5zOae4++xj1m!@gH zDOAG?wYM#AJfE(U2L=X=*3XuHYL=2wuFZc}R4;w@v+bV~>IF0M54n6M1vUPu-r?(d z)7yz>P4|TGq)!gCqVqQ9V`lqtWSIEp<!OjB4-P}DwI*;XmIl0ts!VxcP6X<=wKrhN zr{O_r&~O53Yy9)CaOiGZ+jWJ7Waiju49C7pIOHNMSxyITCq07wUV^anE^U4)Jm{5u zh;6cGoShlLh8S~Jka=7}Xm62Kd5dJZnMy(udtg}5V7HHUOw%DBmo-)pW1TEb0Qhj0 zA2eojL`t|j1rtR5eTe^9H*TVtBgyGAPf9skZxYm5O})8doe@ijv{fC0U+j55vH|qr z=9q}s%*CV<jc{`YaZWES%SMfuJwYeAAt0wse8PU4{54dGoNHuh%^L9);P2HJHJ2Zv zIGSW3RQ-b$dr@uL#^ukLXin2yqFT<F%Mele`sR%19;@|J8HY#5NFTGP9bAgW|4(%` zL*f$TMzGZpPk`{yPm_$P?6=+=m(%Fri*oh|<6=3E$0@3&8lO!6{55OgWNqkgZVfCX z$ZXpBTe%+*d>2l$T1$K$P9WH#B-hai?(+qY3Tq{mIqJA5ffxzO0UoD6Th(D1enGm7 zSX5j-Ewr=qpK`OFa*vX$C)H;xHa5Y+=@5}2DkG0SJrcHJqNv{VV>siYpP$tV_*GXs zl;azI=Z%f0yM9SVoGLt_9)4r65-P#oWz9Pa>hko&wTKJ!JB1SNT7LSj`aZ+QE2Cv9 z$i@>}uB|`-`6R8N)kWqgC*7NtW3f3^EH3?U*(>kQhg<U+%AB8EUsH!fD8)eo*CCfH z?Tnx7^smMUv$6kVN#8)xB-F)M-xL81Vsg6>lF0s_6Fl4%U{Qnk?}!)s&JxUnsi+MI znr_lrR!$rULvEoKnJ%j)mqb3aszV?}yAMI8;RjK*cUupZ8Hb;7M~%C(g_|lp$2Z1B zV@rYAk%`)1YK3<f`{71z1$WfeM_+2)G@K|gro7UcIM@>ohK4@G@blaHH-^ffRlNm} z#E+Ku_gL=Rw2}&|bDxh{|2TQd`Rd%xSaL_OM<f(^gf7pW^P$ePUe9LGCRy%X<3^uK zT|;hvWxCex!A^mV@ZdG(h7dGI?~8BfT-KZ1NuLX6FFcWtU{>u5m{r#H{dHW>)<>yJ z-6YEmLi9SK%NT*p@YB@O%r`k$;GB8gN71%BavPlh$)qE>JT1}Sxu0h|u}iV!@+iE_ ztLX4uDbjkvI-}Y8ZGGwKpJ#X2DXs7`z1pOV@W=D&wP@z3Cq<{))YuVf_YblT)@;!E z6c<1SWnu=c;xs+mOH9W2v=;pO8trughZvSaoH(;{hAtXsLFtdn&0X1Ye^LBUlll$@ zb~dO(bEuMdzS#B>nSYdu<sg=qkf!3OCAS@d4C1Lf9V}fR71$Np>70|#ZMUfkv8|H0 zZYgp2HdZ6lbf^7++`j&PXi)dSYiVZ4?)fQc%D-~V@V39UTRDdIKpWN9e@o8kTXOUM z{6(WN#HCe>&M2nc^3h=;Xp^?gE{^VR`4cB6nIN&dgxXNE@S8Z0&kxa$;AW`k3_~{Y zSYPuGeG6ZUY>W0)n1feaS!I=TmDj|uomcHTrTz&+!;*oBVqE?On9ORFEVeCy=uPLk znKDSIf=e3FD9QK95;eL$Lgm{YrD_@%CuKtoy+(IzBX7fR>b-&*n>WECYSC25M?+`E zgwRJCY@F$UX}fD-Z_Kuo*U;A=Vm+tGbrS^>g=aZud2ktXHy#Uf4S1ESK{IO2MGUGN zhGA|kGGC70ki5+xf;bSy3wLs5eU}vpvx#k<wj56`@#tiVqAsou<|k{Ev;3+t9oTJa z?vpf`V@lBYs&>16BC34b+R#_(jht;<FpP*YPrp~C<%1ir7JKK+^ePf_8X>NS5&I?N z+Zb=FLgSg~kA|1x1Z1w62>EsF@+DvM@=ptzv<iNRB{)o~r2ZKQ-4ojXgy%FASTp}I z()x#qvs=yw&&$qRH@<(0&>kRX&2$uFNeVI(nw&XF4H<1h<$B3VevA~qlS;EoXy{?( zdt%Dd5+(@g4e`P@jzIHH(o;X}ywkYd5>GHPm6XzklRfTOw1Z00`%khk&q&`CcCT+) z74M_)J(XN97N?H(bu418H(|E77s=$=sd}~hWo|)XHws@mllK+X-c-PJZZf)V=B92I zLS|nq-ZFrTgNuumlarN`N0U=Ph>Kr{mz$Y`Lx_W8z?R_he?8#fWNu^m_5XgLk-tj$ QKcTULjIwm4q)Fia1MvThxc~qF literal 0 HcmV?d00001 From 275de41556645e4b49d77c84528855b57b8d742e Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 18:25:13 +0200 Subject: [PATCH 102/150] Rename slack-icon-256x255-6c91kojx.png to slack.png --- .../{slack-icon-256x255-6c91kojx.png => slack.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename website/resources/{slack-icon-256x255-6c91kojx.png => slack.png} (100%) diff --git a/website/resources/slack-icon-256x255-6c91kojx.png b/website/resources/slack.png similarity index 100% rename from website/resources/slack-icon-256x255-6c91kojx.png rename to website/resources/slack.png From 49fa20e67b3ef793c831c30167526dc60fcd0755 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 18:25:43 +0200 Subject: [PATCH 103/150] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1eaaf70f..9244c7c2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![build-and-tests](https://github.com/radumarias/rencfs/actions/workflows/build_and_tests.yaml/badge.svg)](https://github.com/radumarias/rencfs/actions/workflows/build_and_tests.yaml) [![release](https://github.com/radumarias/rencfs/actions/workflows/release.yaml/badge.svg)](https://github.com/radumarias/rencfs/actions/workflows/release.yaml) [![codecov](https://codecov.io/gh/radumarias/rencfs/graph/badge.svg?token=NUQI6XGF2Y)](https://codecov.io/gh/radumarias/rencfs) -<a href="https://bit.ly/3UU1oXi"><img src="website/resources/slack3.png" style = "width: 87px; height: 20px;"/></a> +<a href="https://bit.ly/3UU1oXi"><img src="website/resources/slack.png" style = "width: 87px; height: 20px;"/></a> [![Open Source Helpers](https://www.codetriage.com/radumarias/rencfs/badges/users.svg)](https://www.codetriage.com/radumarias/rencfs) > [!WARNING] From 99a6732a1a5de0dae32b74b8db5985e04b65d2d1 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 18:26:00 +0200 Subject: [PATCH 104/150] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9244c7c2..e992ed24 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![build-and-tests](https://github.com/radumarias/rencfs/actions/workflows/build_and_tests.yaml/badge.svg)](https://github.com/radumarias/rencfs/actions/workflows/build_and_tests.yaml) [![release](https://github.com/radumarias/rencfs/actions/workflows/release.yaml/badge.svg)](https://github.com/radumarias/rencfs/actions/workflows/release.yaml) [![codecov](https://codecov.io/gh/radumarias/rencfs/graph/badge.svg?token=NUQI6XGF2Y)](https://codecov.io/gh/radumarias/rencfs) -<a href="https://bit.ly/3UU1oXi"><img src="website/resources/slack.png" style = "width: 87px; height: 20px;"/></a> +<a href="https://bit.ly/3UU1oXi"><img src="website/resources/slack.png" style = "width: 20px; height: 20px;"/></a> [![Open Source Helpers](https://www.codetriage.com/radumarias/rencfs/badges/users.svg)](https://www.codetriage.com/radumarias/rencfs) > [!WARNING] From a3e7b91003986b133e64d6265cc28b243e8ebc58 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 16 Dec 2024 18:27:35 +0200 Subject: [PATCH 105/150] Delete website/resources/slack.png --- website/resources/slack.png | Bin 15599 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 website/resources/slack.png diff --git a/website/resources/slack.png b/website/resources/slack.png deleted file mode 100644 index 4e6448938684e25ad6983b9d81b52c57c956b95b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15599 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 QKcTULjIwm4q)Fia1MvThxc~qF From 6796450f9e00deed239bf4522fd9601658d64bbc Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:32:59 +0200 Subject: [PATCH 106/150] Create SECURITY.md --- SECURITY.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..2c1ea16c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 0.x.y | :white_check_mark: | + +## Reporting a Vulnerability + +[Report a security vulnerability](https://github.com/radumarias/rencfs/security/advisories/new): Report a security vulnerability. From f4c0d26e0fbc862cd9a46c492a2a1a0d6e0ebef0 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:33:47 +0200 Subject: [PATCH 107/150] Rename SECURITY.md to .github/SECURITY.md --- SECURITY.md => .github/SECURITY.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename SECURITY.md => .github/SECURITY.md (100%) diff --git a/SECURITY.md b/.github/SECURITY.md similarity index 100% rename from SECURITY.md rename to .github/SECURITY.md From 9a8cb808782b2dac8cf94bafc74a5f55f5155edb Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:34:24 +0200 Subject: [PATCH 108/150] Rename CONTRIBUTING.md to .github/CONTRIBUTING.md --- CONTRIBUTING.md => .github/CONTRIBUTING.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%) diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md From 0d4d08e0f27394fc23b92c343ded904da289d9a3 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:34:48 +0200 Subject: [PATCH 109/150] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e992ed24..3795d761 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ If you find any issues, vulnerabilities or you'd like a feature, please follow t Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing pull requests, it is always appreciated. - How to contribute - Please see [CONTRIBUTING.md](CONTRIBUTING.md). + Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md). # Follow us From 647a3192ed014b0962cc64e59bc52f1e35e4be15 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:35:55 +0200 Subject: [PATCH 110/150] Update and rename CODE_OF_CONDUCT.md to .github/CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md (99%) diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 99% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md index 41719f52..c1caa7c9 100644 --- a/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -60,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -radumarias@gmail.com. +hello@xorio.rs. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the From cedb61c4e0b82c040d85fcee16f1b05ce42f3be0 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:36:55 +0200 Subject: [PATCH 111/150] Rename LICENSE-Apache-2.0 to .github/LICENSE-Apache-2.0 --- LICENSE-Apache-2.0 => .github/LICENSE-Apache-2.0 | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE-Apache-2.0 => .github/LICENSE-Apache-2.0 (100%) diff --git a/LICENSE-Apache-2.0 b/.github/LICENSE-Apache-2.0 similarity index 100% rename from LICENSE-Apache-2.0 rename to .github/LICENSE-Apache-2.0 From eb5a060b2501748d71cae2154dda2cafe8ecc92e Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:37:21 +0200 Subject: [PATCH 112/150] Rename .github/LICENSE-Apache-2.0 to LICENSE-Apache-2.0 --- .github/LICENSE-Apache-2.0 => LICENSE-Apache-2.0 | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/LICENSE-Apache-2.0 => LICENSE-Apache-2.0 (100%) diff --git a/.github/LICENSE-Apache-2.0 b/LICENSE-Apache-2.0 similarity index 100% rename from .github/LICENSE-Apache-2.0 rename to LICENSE-Apache-2.0 From 44c24b863a78d57fad953d8f93f4a0a013ec1048 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:44:18 +0200 Subject: [PATCH 113/150] Create PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..f186b482 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,44 @@ +## Title + +If this is related to a GitHub issue, please have use the issue's title, inlcude `#ID` too. + +## Description + +Please include a summary of the changes and the related issue. Describe **why** this change is needed. + +Fixes # (issue) + +--- + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update + +--- + +## Checklist: + +- [ ] I have performed a self-review of my code +- [ ] I have tested my code on different platforms (if applicable) +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have added necessary documentation (if appropriate) +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes + +--- + +## Screenshots (if applicable) + +Add screenshots to illustrate changes if necessary. + +--- + +## Additional Context + +Add any additional context or information here. From 1b0c8be8d25b368214b73c6ad2e64e45547aa5e9 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:46:13 +0200 Subject: [PATCH 114/150] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3795d761..5294c918 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Some of these are still being worked on and marked with `[WIP]`. [![rencfs](website/resources/layers.png)](website/resources/layers.png) -Please look into [Flows](docs/readme/flows.md) for a detailed description of the various sequence flows. +Please look into [Flows](docs/readme/flows.md) for a detailed sequence flow description. # Usage and Development @@ -118,7 +118,7 @@ If you find any issues, vulnerabilities or you'd like a feature, please follow t - [Report a security vulnerability](https://github.com/radumarias/rencfs/security/advisories/new): Report a security vulnerability. - [Feature request](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=): Suggest an idea for this project. -Feel free to fork it, change and use it however you want. If you build something interesting and feel like sharing pull requests, it is always appreciated. +Feel free to fork, change, and use it however you want. It is always appreciated if you build something interesting and feel like sharing pull requests. - How to contribute Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md). From 39d57f1f4231f35f1f6e1bc14f21cd9071f1a513 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:52:42 +0200 Subject: [PATCH 115/150] Add files via upload --- .../Screenshot From 2024-12-16 18-27-11.png | Bin 0 -> 23620 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 website/resources/Screenshot From 2024-12-16 18-27-11.png diff --git a/website/resources/Screenshot From 2024-12-16 18-27-11.png b/website/resources/Screenshot From 2024-12-16 18-27-11.png new file mode 100644 index 0000000000000000000000000000000000000000..5b05833e45c59869e3bd5842ea5630d20e317670 GIT binary patch literal 23620 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 literal 0 HcmV?d00001 From e7a89a690fad6dbc84b8f862d8b644be5b152b09 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:53:02 +0200 Subject: [PATCH 116/150] Rename Screenshot From 2024-12-16 18-27-11.png to slack.png --- ...nshot From 2024-12-16 18-27-11.png => slack.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename website/resources/{Screenshot From 2024-12-16 18-27-11.png => slack.png} (100%) diff --git a/website/resources/Screenshot From 2024-12-16 18-27-11.png b/website/resources/slack.png similarity index 100% rename from website/resources/Screenshot From 2024-12-16 18-27-11.png rename to website/resources/slack.png From 4b47c562ef2e74dcef23ffba91cf2a07c34c15f4 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:54:54 +0200 Subject: [PATCH 117/150] Create SUPPORT.md --- .github/SUPPORT.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/SUPPORT.md diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 00000000..a52d3def --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,14 @@ +# Support + +Feel free to contact us at +- hello@xorio.rs or on [slack](https://bit.ly/3UU1oXi) +- in the `#support` channel +- or write us on [LinkedIn](https://www.linkedin.com/company/xorio) + +# Issues + +If you find any issues, vulnerabilities or you'd like a feature, please follow these steps: + +- [Open a bug](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=): Create a report to help us improve. +- [Report a security vulnerability](https://github.com/radumarias/rencfs/security/advisories/new): Report a security vulnerability. +- [Feature request](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=): Suggest an idea for this project. From c588b3a93007009ad5a13f85e37013e7bd0a5e8d Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:55:24 +0200 Subject: [PATCH 118/150] Update SUPPORT.md --- .github/SUPPORT.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md index a52d3def..82e62555 100644 --- a/.github/SUPPORT.md +++ b/.github/SUPPORT.md @@ -1,9 +1,9 @@ # Support -Feel free to contact us at -- hello@xorio.rs or on [slack](https://bit.ly/3UU1oXi) -- in the `#support` channel -- or write us on [LinkedIn](https://www.linkedin.com/company/xorio) +Feel free to contact us on +- hello@xorio.rs +- [slack](https://bit.ly/3UU1oXi) in the `#support` channel +- [LinkedIn](https://www.linkedin.com/company/xorio) # Issues From 79a12b77a9edd97cbb491259089148742c6b4c37 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:56:12 +0200 Subject: [PATCH 119/150] Update SUPPORT.md --- .github/SUPPORT.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md index 82e62555..babe9012 100644 --- a/.github/SUPPORT.md +++ b/.github/SUPPORT.md @@ -1,14 +1,13 @@ # Support -Feel free to contact us on +For any issues you might have, feel free to contact us on: - hello@xorio.rs - [slack](https://bit.ly/3UU1oXi) in the `#support` channel - [LinkedIn](https://www.linkedin.com/company/xorio) # Issues -If you find any issues, vulnerabilities or you'd like a feature, please follow these steps: - +If you find any issues or vulnerabilities or you'd like a feature, please follow these steps: - [Open a bug](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=): Create a report to help us improve. - [Report a security vulnerability](https://github.com/radumarias/rencfs/security/advisories/new): Report a security vulnerability. - [Feature request](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=): Suggest an idea for this project. From cb004ef075f34978d22f59c7119ec83b1c9f950a Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 04:56:49 +0200 Subject: [PATCH 120/150] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5294c918..9a7c26cd 100644 --- a/README.md +++ b/README.md @@ -105,20 +105,19 @@ Please look into [Flows](docs/readme/flows.md) for a detailed sequence flow desc `Aes256Gcm` is slightly faster than `ChaCha20Poly1305` by an average factor of **1.28**. This is because of the hardware acceleration of AES on most CPUs via AES-NI. However, where hardware acceleration is unavailable, `ChaCha20Poly1305` is faster. - Also `ChaChaPoly1305` is better at `SIMD`. + Also, `ChaChaPoly1305` is better at `SIMD.` - [⚠️ Security ](docs/readme/Security.md) - [Cipher comparison](docs/readme/Cipher_comparison.md) - [Others](docs/readme/Considerations.md) # Contribute -If you find any issues, vulnerabilities or you'd like a feature, please follow these steps: - +If you find any issues or vulnerabilities or you'd like a feature, please follow these steps: - [Open a bug](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=): Create a report to help us improve. - [Report a security vulnerability](https://github.com/radumarias/rencfs/security/advisories/new): Report a security vulnerability. - [Feature request](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=): Suggest an idea for this project. -Feel free to fork, change, and use it however you want. It is always appreciated if you build something interesting and feel like sharing pull requests. +Feel free to fork, change, and use it however you want. We always appreciate it if you build something interesting and feel like sharing pull requests. - How to contribute Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md). From c01ea19bf7ec6caca39bb7d6188e68bc91a91455 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 06:11:32 +0200 Subject: [PATCH 121/150] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 9a7c26cd..55213de0 100644 --- a/README.md +++ b/README.md @@ -128,3 +128,9 @@ Feel free to fork, change, and use it however you want. We always appreciate it There is a [series](https://medium.com/@xorio42/list/828492b94c23) of articles about the evolution of this project, trying to keep it like a tutorial. This is the [first one](https://systemweakness.com/the-hitchhikers-guide-to-building-an-encrypted-filesystem-in-rust-4d678c57d65c). + +# Get in touch + +- [Slack](https://bit.ly/3UU1oXi) +- [hello@xorio.rs](mailto:hello@xorio.rs) +- [LinkedIn](https://www.linkedin.com/company/xorio) From 1eb8adb429585e551067514519c21e998c354729 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 06:55:31 +0200 Subject: [PATCH 122/150] set num threads for build, test and bench --- scripts/check-before-push.bat | 6 ++++-- scripts/check-before-push.sh | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/check-before-push.bat b/scripts/check-before-push.bat index d63d24ca..39ea86dd 100755 --- a/scripts/check-before-push.bat +++ b/scripts/check-before-push.bat @@ -4,6 +4,8 @@ setlocal set CARGO_TERM_COLOR=always gsset RUSTFLAGS=-Dwarnings set RUSTDOCFLAGS=-Dwarnings +set RUST_TEST_THREADS=14 +set CARGO_BUILD_JOBS=14 if %errorlevel% neq 0 exit /b %errorlevel% @@ -28,7 +30,7 @@ REM if %errorlevel% neq 0 exit /b %errorlevel% cargo test --release --all --all-features if %errorlevel% neq 0 exit /b %errorlevel% -REM cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu +REM cargo bench --workspace --all-targets --all-features -j 14 if %errorlevel% neq 0 exit /b %errorlevel% cargo doc --workspace --all-features --no-deps @@ -71,7 +73,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% cargo doc --workspace --all-features --no-deps if %errorlevel% neq 0 exit /b %errorlevel% -REM cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu +REM cargo bench --workspace --all-targets --all-features -j 14 if %errorlevel% neq 0 exit /b %errorlevel% cd .. diff --git a/scripts/check-before-push.sh b/scripts/check-before-push.sh index aad62c74..028407c3 100755 --- a/scripts/check-before-push.sh +++ b/scripts/check-before-push.sh @@ -5,6 +5,8 @@ set -e export CARGO_TERM_COLOR=always export RUSTFLAGS="-Dwarnings" export RUSTDOCFLAGS="-Dwarnings" +export RUST_TEST_THREADS=14 +export CARGO_BUILD_JOBS=14 cargo fmt --all @@ -21,7 +23,7 @@ cargo clippy --all-targets --release --target x86_64-unknown-linux-gnu -- \ -A clippy::missing_errors_doc \ -A clippy::type_complexity cargo test --release --all --all-features --target x86_64-unknown-linux-gnu -# cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu +cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu -j 14 cargo doc --workspace --all-features --no-deps --target x86_64-unknown-linux-gnu # cargo publish --dry-run --allow-dirty --target x86_64-unknown-linux-gnu @@ -44,6 +46,6 @@ cargo clippy --all-targets --release --target x86_64-unknown-linux-gnu -- \ -A clippy::missing_errors_doc \ -A clippy::type_complexity cargo test --release --all --all-features --target x86_64-unknown-linux-gnu -# cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu +cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu -j 14 cargo doc --workspace --all-features --no-deps --target x86_64-unknown-linux-gnu cd .. From 0b54f4fe10f8d108c90a33e54a9bc705c8e57eb3 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 06:57:48 +0200 Subject: [PATCH 123/150] update bench run in CI --- .github/workflows/build_and_tests_reusable.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index ae206ce7..7acc11fa 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -57,7 +57,7 @@ jobs: - name: bench if: matrix.os != 'windows-latest' - run: cargo bench --workspace --all-targets --all-features -- --skip keyring + run: cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu -j 14 -- --skip keyring - name: doc run: cargo doc --workspace --all-features --no-deps @@ -106,7 +106,7 @@ jobs: if: matrix.os != 'windows-latest' run: | cd java-bridge - cargo bench --workspace --all-targets --all-features + cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu -j 14 -- --skip keyring - name: java-bridge doc run: | From 31a9688abf42edaa3f114a1cc039a48b7f4fc691 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 07:27:33 +0200 Subject: [PATCH 124/150] update bench run in CI --- .github/workflows/build_and_tests_reusable.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index 7acc11fa..fd2aed9d 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -57,7 +57,7 @@ jobs: - name: bench if: matrix.os != 'windows-latest' - run: cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu -j 14 -- --skip keyring + run: cargo bench --workspace --all-targets --all-features -j 14 -- --skip keyring - name: doc run: cargo doc --workspace --all-features --no-deps @@ -106,7 +106,7 @@ jobs: if: matrix.os != 'windows-latest' run: | cd java-bridge - cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu -j 14 -- --skip keyring + cargo bench --workspace --all-targets --all-features -j 14 -- --skip keyring - name: java-bridge doc run: | From 3959af1e6423ee9b9c2934f4b08e1d03ff324681 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 07:45:23 +0200 Subject: [PATCH 125/150] Update build_and_tests_reusable.yaml (#265) Signed-off-by: Radu Marias <radumarias@gmail.com> --- .github/workflows/build_and_tests_reusable.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index fd2aed9d..ae206ce7 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -57,7 +57,7 @@ jobs: - name: bench if: matrix.os != 'windows-latest' - run: cargo bench --workspace --all-targets --all-features -j 14 -- --skip keyring + run: cargo bench --workspace --all-targets --all-features -- --skip keyring - name: doc run: cargo doc --workspace --all-features --no-deps @@ -106,7 +106,7 @@ jobs: if: matrix.os != 'windows-latest' run: | cd java-bridge - cargo bench --workspace --all-targets --all-features -j 14 -- --skip keyring + cargo bench --workspace --all-targets --all-features - name: java-bridge doc run: | From 14ab69a28dc3e1a22f2b0bee846bf9f2bf9af50d Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 07:50:46 +0200 Subject: [PATCH 126/150] Update build_and_tests_reusable.yaml (#266) Signed-off-by: Radu Marias <radumarias@gmail.com> From 02ac56f844a845392c60a4ccf360961a67f4469e Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 07:55:36 +0200 Subject: [PATCH 127/150] Update build_and_tests_reusable.yaml Signed-off-by: Radu Marias <radumarias@gmail.com> --- .../workflows/build_and_tests_reusable.yaml | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index ae206ce7..f3f7bd02 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -51,17 +51,13 @@ jobs: -A clippy::type_complexity shell: bash + - name: doc + run: cargo doc --workspace --all-features --no-deps + - name: tests if: matrix.os != 'windows-latest' run: cargo test --release --all --all-features -- --skip keyring - - name: bench - if: matrix.os != 'windows-latest' - run: cargo bench --workspace --all-targets --all-features -- --skip keyring - - - name: doc - run: cargo doc --workspace --all-features --no-deps - - name: test package if: matrix.os == 'ubuntu-latest' run: | @@ -96,19 +92,13 @@ jobs: -A clippy::type_complexity shell: bash - - name: java-bridge tests - if: matrix.os != 'windows-latest' + - name: java-bridge doc run: | cd java-bridge - cargo test --release --all --all-features + cargo doc --workspace --all-features --no-deps - - name: java-bridge bench + - name: java-bridge tests if: matrix.os != 'windows-latest' run: | cd java-bridge - cargo bench --workspace --all-targets --all-features - - - name: java-bridge doc - run: | - cd java-bridge - cargo doc --workspace --all-features --no-deps + cargo test --release --all --all-features From eee96fbb18012f22aeedf824bba662a4db9d01c2 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 07:58:08 +0200 Subject: [PATCH 128/150] Update build_and_tests_reusable.yaml Signed-off-by: Radu Marias <radumarias@gmail.com> --- .../workflows/build_and_tests_reusable.yaml | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index f3f7bd02..562673c3 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -51,13 +51,17 @@ jobs: -A clippy::type_complexity shell: bash - - name: doc - run: cargo doc --workspace --all-features --no-deps - - name: tests if: matrix.os != 'windows-latest' run: cargo test --release --all --all-features -- --skip keyring + - name: bench + if: matrix.os != 'windows-latest' + run: cargo bench --workspace --all-targets --all-features -- --skip keyring + + - name: doc + run: cargo doc --workspace --all-features --no-deps + - name: test package if: matrix.os == 'ubuntu-latest' run: | @@ -92,13 +96,19 @@ jobs: -A clippy::type_complexity shell: bash - - name: java-bridge doc + - name: java-bridge tests + if: matrix.os != 'windows-latest' run: | cd java-bridge - cargo doc --workspace --all-features --no-deps + cargo test --release --all --all-features - - name: java-bridge tests + - name: bench if: matrix.os != 'windows-latest' run: | cd java-bridge - cargo test --release --all --all-features + cargo bench --workspace --all-targets --all-features + + - name: java-bridge doc + run: | + cd java-bridge + cargo doc --workspace --all-features --no-deps From 99fa3e4da944a106c6f0b12026b12f355534b2a1 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 17:02:58 +0200 Subject: [PATCH 129/150] Update CONTRIBUTING.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- .github/CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index af4bf77f..427bc8db 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -12,10 +12,10 @@ defined in the Apache License shall be dual-licensed as above, without any addit # Devs & QA automation (which steps apply) -3. Become familiar with docs and code by reading the [ramp-up](docs/readme/Ramp-up.md) guide +3. Become familiar with docs and code by reading the [ramp-up](../docs/readme/Ramp-up.md) guide 4. Pick an open issue or a task in the corresponding [project](https://github.com/users/radumarias/projects/1) for the repo you'll work on. You can - see [good for first issues](https://github.com/radumarias/rencfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) + see [good for first issues](https://github.com/xorio/rencfs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) that you can pick from 5. **Assign the issues you are working on to you and move them to the corresponding status column as you progress . If the task is not an issue yet, convert it to an issue first** @@ -38,7 +38,7 @@ defined in the Apache License shall be dual-licensed as above, without any addit 14. Push your changes, and if there are any errors, fix them before you push them 15. Create a `PR` back to the `parent` repo targeting the `main` branch with the title as the GitHub issue title, including `#ID`. Also, include the link to the GitHub issue in the description, saying like `Fix for <link>` for bugs or `Implementation for <link>` for features and others 16. Request review from owners of the repository by adding them to the `Reviewers` field -17. After you create the PR, link it to the GH issue from the mid-right of the page, press the gear icon from the below image, select `radumarias/rencfs`, then write the issue number and select it. This will link the two, and when the PR is merged, it will close the issue too +17. After you create the PR, link it to the GH issue from the mid-right of the page, press the gear icon from the below image, select `xoriors/rencfs`, then write the issue number and select it. This will link the two, and when the PR is merged, it will close the issue too ![image](https://github.com/user-attachments/assets/5ac0313d-4175-44d1-8d1e-d18da773ab32) 18. In the project, move the item to `In Code Review` 19. Monitor the checks (GitHub actions runs) and fix the code if they are failing @@ -48,4 +48,4 @@ defined in the Apache License shall be dual-licensed as above, without any addit # QA manual -Please follow these [steps](docs/readme/Testing.md). +Please follow these [steps](../docs/readme/Testing.md). From 8f30e34dc50e9ed19fc170d50272f4bb1f0e7c7e Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 17:05:29 +0200 Subject: [PATCH 130/150] Update README.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 55213de0..4c76a516 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# [![](favicon.png)](https://github.com/radumarias/rencfs) rencfs +# [![](favicon.png)](https://github.com/xoriors/rencfs) rencfs [![rencfs-bin](https://img.shields.io/aur/version/rencfs-bin?color=1793d1&label=rencfs-bin&logo=arch-linux)](https://aur.archlinux.org/packages/rencfs-bin/) [![crates.io](https://img.shields.io/crates/v/rencfs.svg)](https://crates.io/crates/rencfs) [![docs.rs](https://img.shields.io/docsrs/rencfs?label=docs.rs)](https://docs.rs/rencfs/) -[![build-and-tests](https://github.com/radumarias/rencfs/actions/workflows/build_and_tests.yaml/badge.svg)](https://github.com/radumarias/rencfs/actions/workflows/build_and_tests.yaml) -[![release](https://github.com/radumarias/rencfs/actions/workflows/release.yaml/badge.svg)](https://github.com/radumarias/rencfs/actions/workflows/release.yaml) -[![codecov](https://codecov.io/gh/radumarias/rencfs/graph/badge.svg?token=NUQI6XGF2Y)](https://codecov.io/gh/radumarias/rencfs) +[![build-and-tests](https://github.com/xoriors/rencfs/actions/workflows/build_and_tests.yaml/badge.svg)](https://github.com/xoriors/rencfs/actions/workflows/build_and_tests.yaml) +[![release](https://github.com/xoriors/rencfs/actions/workflows/release.yaml/badge.svg)](https://github.com/xoriors/rencfs/actions/workflows/release.yaml) +[![codecov](https://codecov.io/gh/xoriors/rencfs/graph/badge.svg?token=NUQI6XGF2Y)](https://codecov.io/gh/xoriors/rencfs) <a href="https://bit.ly/3UU1oXi"><img src="website/resources/slack.png" style = "width: 20px; height: 20px;"/></a> -[![Open Source Helpers](https://www.codetriage.com/radumarias/rencfs/badges/users.svg)](https://www.codetriage.com/radumarias/rencfs) +[![Open Source Helpers](https://www.codetriage.com/xoriors/rencfs/badges/users.svg)](https://www.codetriage.com/xoriors/rencfs) > [!WARNING] > **This crate hasn't been audited; it's using `ring` crate, which is a well-known audited library, so in principle, at @@ -49,8 +49,8 @@ data. Some of these are still being worked on and marked with `[WIP]`. - `Security` using well-known audited `AEAD` cryptography primitives; -- `[WIP]` [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/radumarias/rencfs/issues/48) -- `[WIP]` [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/radumarias/rencfs/issues/53) +- `[WIP]` [Data integrity, data is written with WAL to ensure integrity even on crash or power loss](https://github.com/xoriors/rencfs/issues/48) +- `[WIP]` [Hide all info for enhanced privacy; all metadata, content, file name, file size, *time fields, files count, and directory structure is encrypted](https://github.com/xoriors/rencfs/issues/53) - `Safely` manage `credentials` in memory with `mlock(2)`, `mprotect`, `zeroize`, and `expiry` to mitigate cold boot attacks; - `Memory safety`, `performance`, and `optimized` for `concurrency` with Rust; @@ -58,16 +58,16 @@ Some of these are still being worked on and marked with `[WIP]`. - Encryption key generated from password; - Password saved in OS's `keyring`; - `Change password` without re-encrypting all data; -- `[WIP]` [Generate unique nonce in offline mode](https://github.com/radumarias/rencfs/issues/47) -- `[WIP]` [Add file inode and chunk index to AAD](https://github.com/radumarias/rencfs/issues/49) This prevents blocks +- `[WIP]` [Generate unique nonce in offline mode](https://github.com/xoriors/rencfs/issues/47) +- `[WIP]` [Add file inode and chunk index to AAD](https://github.com/xoriors/rencfs/issues/49) This prevents blocks from being copied between or within files by an attacker; - `Fast seek` on both reads and writes; - `Writes in parallel`; - Exposed with `FUSE`; - Fully `concurrent` for all operations; -- `[WIP]` [Handle long file names](https://github.com/radumarias/rencfs/issues/47) -- `[WIP]` [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/radumarias/rencfs/issues/97) -- `[WIP]` [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/radumarias/rencfs/issues/111) +- `[WIP]` [Handle long file names](https://github.com/xoriors/rencfs/issues/47) +- `[WIP]` [Abstraction layer for Rust File and fs API to use it as lib to switch to using encrypted files by just changing the use statements](https://github.com/xoriors/rencfs/issues/97) +- `[WIP]` [Abstraction layer to access the storage with implementations for desktop, Wasm, Android, and iOS and the ability to write your own implementation](https://github.com/xoriors/rencfs/issues/111) # [Alternatives](docs/readme/Alternatives.md) @@ -113,9 +113,9 @@ Please look into [Flows](docs/readme/flows.md) for a detailed sequence flow desc # Contribute If you find any issues or vulnerabilities or you'd like a feature, please follow these steps: -- [Open a bug](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=): Create a report to help us improve. -- [Report a security vulnerability](https://github.com/radumarias/rencfs/security/advisories/new): Report a security vulnerability. -- [Feature request](https://github.com/radumarias/rencfs/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=): Suggest an idea for this project. +- [Open a bug](https://github.com/xoriors/rencfs/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=): Create a report to help us improve. +- [Report a security vulnerability](https://github.com/xoriors/rencfs/security/advisories/new): Report a security vulnerability. +- [Feature request](https://github.com/xoriors/rencfs/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=): Suggest an idea for this project. Feel free to fork, change, and use it however you want. We always appreciate it if you build something interesting and feel like sharing pull requests. From 6a2ebc4ad1c471851ac3c00b24cfd65b4b9e858b Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 17:08:16 +0200 Subject: [PATCH 131/150] docs --- README.md | 2 +- ...uilding_an_Encrypted_Filesystem_in_Rust.pdf | Bin ...lding_an_Encrypted_Filesystem_in_Rust_2.pdf | Bin 154116 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) rename The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust.pdf => docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust.pdf (100%) delete mode 100644 docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust_2.pdf diff --git a/README.md b/README.md index 4c76a516..9d486cc6 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ data. your `privacy`, which is also `open source` and is correctly and safely using `well-known audited` crates as `cryptographic primitives.` - A short story - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust_2.pdf) + [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust.pdf) - Talks - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) [@meetup.com/star-tech-rd-reloaded](https://www.meetup.com/star-tech-rd-reloaded/) and [@OmniOpenCon](https://omniopencon.org/) diff --git a/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust.pdf b/docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust.pdf similarity index 100% rename from The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust.pdf rename to docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust.pdf diff --git a/docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust_2.pdf b/docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust_2.pdf deleted file mode 100644 index 788144fdcc09578f953a999ae7daa5ab9cbf30de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154116 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 From 4609612424cbbe069d5738c3d9bde39e554a63c8 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 20:42:41 +0200 Subject: [PATCH 132/150] Update package_reusable.yaml Signed-off-by: Radu Marias <radumarias@gmail.com> --- .github/workflows/package_reusable.yaml | 135 +++++++++++++----------- 1 file changed, 74 insertions(+), 61 deletions(-) diff --git a/.github/workflows/package_reusable.yaml b/.github/workflows/package_reusable.yaml index c4b09861..578a524b 100644 --- a/.github/workflows/package_reusable.yaml +++ b/.github/workflows/package_reusable.yaml @@ -1,20 +1,19 @@ -name: '_package' +name: '_build-and-tests' on: workflow_call: - inputs: - upload_artifacts: - description: 'If we should upload artifacts' - type: boolean - required: true - version: - description: 'The current value in version file (type: string)' - type: string - required: true + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 # TODO: remove this when we cache the builds jobs: - aur_build: - name: build AUR - runs-on: ubuntu-latest + tests: + name: build and tests + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + fail-fast: true steps: - uses: actions/checkout@v4 @@ -30,59 +29,73 @@ jobs: cargo install cargo-aur cargo install cargo-generate-rpm - - name: run cargo aur and generate-rpm + - name: build + run: | + cargo build --all-targets --all-features + cargo build --release --all-targets --all-features + + - name: fmt check + run: cargo fmt --all -- --check + + - name: check + run: cargo check --all + + - name: clippy + run: | + cargo clippy --all-targets --release -- \ + -A clippy::similar_names \ + -A clippy::too_many_arguments \ + -A clippy::significant_drop_tightening \ + -A clippy::redundant_closure \ + -A clippy::missing_errors_doc \ + -A clippy::type_complexity + shell: bash + + - name: doc + run: cargo doc --workspace --all-features --no-deps + + - name: tests + if: matrix.os != 'windows-latest' + run: cargo test --release --all --all-features + + - name: test package + if: matrix.os == 'ubuntu-latest' run: | cargo aur cargo generate-rpm - - name: upload PKGBUILD artifact - if: inputs.upload_artifacts - uses: actions/upload-artifact@v4 - with: - name: PKGBUILD - path: ./target/cargo-aur/PKGBUILD + - name: java-bridge build + run: | + cd java-bridge + cargo build --all-targets --all-features + cargo build --release --all-targets --all-features - - name: upload ${{ github.event.repository.name }}-${{ inputs.version }}-x86_64.tar.gz artifact - if: inputs.upload_artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ github.event.repository.name }}-${{ inputs.version }}-x86_64.tar.gz - path: ./target/cargo-aur/${{ github.event.repository.name }}-${{ inputs.version }}-x86_64.tar.gz - - docker: - name: docker - runs-on: ubuntu-latest - permissions: - id-token: write - packages: write - contents: read - attestations: write - env: - # Hostname of your registry - REGISTRY: docker.io - REGISTRY_USER: xorio42 - # Image repository, without hostname and tag - IMAGE_NAME: ${{ github.event.repository.name }} - IMAGE_TAG: latest - SHA: ${{ github.event.pull_request.head.sha || github.event.after }} - steps: - - uses: actions/checkout@v4 + - name: java-bridge fmt check + run: | + cd java-bridge + cargo fmt --all -- --check - - name: log in to Docker Hub - if: inputs.upload_artifacts - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + - name: java-bridge check + run: | + cd java-bridge + cargo check --all - - name: set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: java-bridge clippy + run: | + cargo clippy --all-targets --release -- \ + -A clippy::similar_names \ + -A clippy::too_many_arguments \ + -A clippy::significant_drop_tightening \ + -A clippy::redundant_closure \ + -A clippy::missing_errors_doc \ + -A clippy::type_complexity + shell: bash - - name: build push = ${{ inputs.upload_artifacts }} - uses: docker/build-push-action@v6 - with: - file: docker/Dockerfile - push: ${{ inputs.upload_artifacts }} - tags: ${{ env.REGISTRY_USER }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} - sbom: true - provenance: true + - name: java-bridge doc + run: cargo doc --workspace --all-features --no-deps + + - name: java-bridge tests + if: matrix.os != 'windows-latest' + run: | + cd java-bridge + cargo test --release --all --all-features From 892e2e84e96b62c1ecc08310a3d96b2fc232545e Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 20:44:53 +0200 Subject: [PATCH 133/150] Update package_reusable.yaml Signed-off-by: Radu Marias <radumarias@gmail.com> --- .github/workflows/package_reusable.yaml | 135 +++++++++++------------- 1 file changed, 61 insertions(+), 74 deletions(-) diff --git a/.github/workflows/package_reusable.yaml b/.github/workflows/package_reusable.yaml index 578a524b..c4b09861 100644 --- a/.github/workflows/package_reusable.yaml +++ b/.github/workflows/package_reusable.yaml @@ -1,19 +1,20 @@ -name: '_build-and-tests' +name: '_package' on: workflow_call: - -env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 # TODO: remove this when we cache the builds + inputs: + upload_artifacts: + description: 'If we should upload artifacts' + type: boolean + required: true + version: + description: 'The current value in version file (type: string)' + type: string + required: true jobs: - tests: - name: build and tests - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ ubuntu-latest, macos-latest, windows-latest ] - fail-fast: true + aur_build: + name: build AUR + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -29,73 +30,59 @@ jobs: cargo install cargo-aur cargo install cargo-generate-rpm - - name: build - run: | - cargo build --all-targets --all-features - cargo build --release --all-targets --all-features - - - name: fmt check - run: cargo fmt --all -- --check - - - name: check - run: cargo check --all - - - name: clippy - run: | - cargo clippy --all-targets --release -- \ - -A clippy::similar_names \ - -A clippy::too_many_arguments \ - -A clippy::significant_drop_tightening \ - -A clippy::redundant_closure \ - -A clippy::missing_errors_doc \ - -A clippy::type_complexity - shell: bash - - - name: doc - run: cargo doc --workspace --all-features --no-deps - - - name: tests - if: matrix.os != 'windows-latest' - run: cargo test --release --all --all-features - - - name: test package - if: matrix.os == 'ubuntu-latest' + - name: run cargo aur and generate-rpm run: | cargo aur cargo generate-rpm - - name: java-bridge build - run: | - cd java-bridge - cargo build --all-targets --all-features - cargo build --release --all-targets --all-features - - - name: java-bridge fmt check - run: | - cd java-bridge - cargo fmt --all -- --check + - name: upload PKGBUILD artifact + if: inputs.upload_artifacts + uses: actions/upload-artifact@v4 + with: + name: PKGBUILD + path: ./target/cargo-aur/PKGBUILD - - name: java-bridge check - run: | - cd java-bridge - cargo check --all + - name: upload ${{ github.event.repository.name }}-${{ inputs.version }}-x86_64.tar.gz artifact + if: inputs.upload_artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }}-${{ inputs.version }}-x86_64.tar.gz + path: ./target/cargo-aur/${{ github.event.repository.name }}-${{ inputs.version }}-x86_64.tar.gz + + docker: + name: docker + runs-on: ubuntu-latest + permissions: + id-token: write + packages: write + contents: read + attestations: write + env: + # Hostname of your registry + REGISTRY: docker.io + REGISTRY_USER: xorio42 + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.event.repository.name }} + IMAGE_TAG: latest + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + steps: + - uses: actions/checkout@v4 - - name: java-bridge clippy - run: | - cargo clippy --all-targets --release -- \ - -A clippy::similar_names \ - -A clippy::too_many_arguments \ - -A clippy::significant_drop_tightening \ - -A clippy::redundant_closure \ - -A clippy::missing_errors_doc \ - -A clippy::type_complexity - shell: bash + - name: log in to Docker Hub + if: inputs.upload_artifacts + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - - name: java-bridge doc - run: cargo doc --workspace --all-features --no-deps + - name: set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: java-bridge tests - if: matrix.os != 'windows-latest' - run: | - cd java-bridge - cargo test --release --all --all-features + - name: build push = ${{ inputs.upload_artifacts }} + uses: docker/build-push-action@v6 + with: + file: docker/Dockerfile + push: ${{ inputs.upload_artifacts }} + tags: ${{ env.REGISTRY_USER }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} + sbom: true + provenance: true From 10194385de8fcb70d995c2b0af9788e331f791f4 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 20:45:09 +0200 Subject: [PATCH 134/150] Update build_and_tests_reusable.yaml Signed-off-by: Radu Marias <radumarias@gmail.com> --- .../workflows/build_and_tests_reusable.yaml | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index 562673c3..578a524b 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -51,17 +51,13 @@ jobs: -A clippy::type_complexity shell: bash - - name: tests - if: matrix.os != 'windows-latest' - run: cargo test --release --all --all-features -- --skip keyring - - - name: bench - if: matrix.os != 'windows-latest' - run: cargo bench --workspace --all-targets --all-features -- --skip keyring - - name: doc run: cargo doc --workspace --all-features --no-deps + - name: tests + if: matrix.os != 'windows-latest' + run: cargo test --release --all --all-features + - name: test package if: matrix.os == 'ubuntu-latest' run: | @@ -86,7 +82,6 @@ jobs: - name: java-bridge clippy run: | - cd java-bridge cargo clippy --all-targets --release -- \ -A clippy::similar_names \ -A clippy::too_many_arguments \ @@ -96,19 +91,11 @@ jobs: -A clippy::type_complexity shell: bash + - name: java-bridge doc + run: cargo doc --workspace --all-features --no-deps + - name: java-bridge tests if: matrix.os != 'windows-latest' run: | cd java-bridge cargo test --release --all --all-features - - - name: bench - if: matrix.os != 'windows-latest' - run: | - cd java-bridge - cargo bench --workspace --all-targets --all-features - - - name: java-bridge doc - run: | - cd java-bridge - cargo doc --workspace --all-features --no-deps From 79fafc185db40bcd83e4804ccf9bb2b63935c455 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 17 Dec 2024 20:48:13 +0200 Subject: [PATCH 135/150] Update build_and_tests_reusable.yaml Signed-off-by: Radu Marias <radumarias@gmail.com> --- .../workflows/build_and_tests_reusable.yaml | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_and_tests_reusable.yaml b/.github/workflows/build_and_tests_reusable.yaml index 578a524b..ae206ce7 100644 --- a/.github/workflows/build_and_tests_reusable.yaml +++ b/.github/workflows/build_and_tests_reusable.yaml @@ -51,12 +51,16 @@ jobs: -A clippy::type_complexity shell: bash - - name: doc - run: cargo doc --workspace --all-features --no-deps - - name: tests if: matrix.os != 'windows-latest' - run: cargo test --release --all --all-features + run: cargo test --release --all --all-features -- --skip keyring + + - name: bench + if: matrix.os != 'windows-latest' + run: cargo bench --workspace --all-targets --all-features -- --skip keyring + + - name: doc + run: cargo doc --workspace --all-features --no-deps - name: test package if: matrix.os == 'ubuntu-latest' @@ -82,6 +86,7 @@ jobs: - name: java-bridge clippy run: | + cd java-bridge cargo clippy --all-targets --release -- \ -A clippy::similar_names \ -A clippy::too_many_arguments \ @@ -91,11 +96,19 @@ jobs: -A clippy::type_complexity shell: bash - - name: java-bridge doc - run: cargo doc --workspace --all-features --no-deps - - name: java-bridge tests if: matrix.os != 'windows-latest' run: | cd java-bridge cargo test --release --all --all-features + + - name: java-bridge bench + if: matrix.os != 'windows-latest' + run: | + cd java-bridge + cargo bench --workspace --all-targets --all-features + + - name: java-bridge doc + run: | + cd java-bridge + cargo doc --workspace --all-features --no-deps From f378b578bc3c99f787312449cce2048add66170e Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Wed, 18 Dec 2024 14:13:33 +0200 Subject: [PATCH 136/150] Update README.md (#269) Signed-off-by: Radu Marias <radumarias@gmail.com> --- README.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9d486cc6..fb1fc450 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ > [!WARNING] > **This crate hasn't been audited; it's using `ring` crate, which is a well-known audited library, so in principle, at least the primitives should offer a similar level of security. -> This is still under development. Please do not use it with sensitive data for now; please wait for a +> This is still under development. Please do not use it with sensitive data now; please wait for a stable release. > It's mostly ideal for experimental and learning projects.** @@ -31,18 +31,13 @@ data. - Motivation Create a `simple,` `performant,` `modular` and `ergonomic` yet `very secure` `encrypted filesystem` to protect your `privacy`, which is also `open source` and is correctly and safely using `well-known audited` crates - as `cryptographic primitives.` -- A short story - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust.pdf) + as `cryptographic primitives`. +- Read an article [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://medium.com/system-weakness/hitchhikers-guide-to-building-a-distributed-filesystem-in-rust-the-very-beginning-2c02eb7313e7) +- A one-pager [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust.pdf). - Talks - - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) [@meetup.com/star-tech-rd-reloaded](https://www.meetup.com/star-tech-rd-reloaded/) - and [@OmniOpenCon](https://omniopencon.org/) - - [Basics of cryptography, Authenticated Encryption, Rust in cryptography and how to build an encrypted filesystem](https://www.youtube.com/live/HwmVxOl3pQg) - @ITDays and [slides](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323). - - Crate of the week - in [This Week in Rust](https://this-week-in-rust.org/blog/2024/08/07/this-week-in-rust-559/#cfp-projects) -- It was [crate of the week](https://this-week-in-rust.org/blog/2024/08/14/this-week-in-rust-560/#crate-of-the-week) in - Aug 2024. + - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) @[meetup.com/star-tech-rd-reloaded](https://www.meetup.com/star-tech-rd-reloaded/) and @[OmniOpenCon](https://omniopencon.org/). + - [Basics of cryptography, Authenticated Encryption, Rust in cryptography and how to build an encrypted filesystem](https://www.youtube.com/live/HwmVxOl3pQg) @ITDays and [slides](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=342563218323). +- It was [crate of the week](https://this-week-in-rust.org/blog/2024/08/14/this-week-in-rust-560/#crate-of-the-week) in Aug 2024. # Key features From c81a3f069dec6d58a839afd5d961be7d200767ab Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Wed, 18 Dec 2024 14:14:14 +0200 Subject: [PATCH 137/150] Update README.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb1fc450..99d51ca2 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ data. Create a `simple,` `performant,` `modular` and `ergonomic` yet `very secure` `encrypted filesystem` to protect your `privacy`, which is also `open source` and is correctly and safely using `well-known audited` crates as `cryptographic primitives`. -- Read an article [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://medium.com/system-weakness/hitchhikers-guide-to-building-a-distributed-filesystem-in-rust-the-very-beginning-2c02eb7313e7) +- Read an article [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://medium.com/system-weakness/hitchhikers-guide-to-building-a-distributed-filesystem-in-rust-the-very-beginning-2c02eb7313e7). - A one-pager [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](docs/The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust.pdf). - Talks - [The Hitchhiker’s Guide to Building an Encrypted Filesystem in Rust](https://startech-rd.io/hitchhikers-guide-to/) @[meetup.com/star-tech-rd-reloaded](https://www.meetup.com/star-tech-rd-reloaded/) and @[OmniOpenCon](https://omniopencon.org/). From 847e14a0b91feb5b405ab275f64c8a635d10ec20 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Wed, 18 Dec 2024 14:14:49 +0200 Subject: [PATCH 138/150] Update README.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99d51ca2..fed6d9c8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ > [!WARNING] > **This crate hasn't been audited; it's using `ring` crate, which is a well-known audited library, so in principle, at least the primitives should offer a similar level of security. -> This is still under development. Please do not use it with sensitive data now; please wait for a +> This is still under development. Please do not use it with sensitive data for now; please wait for a stable release. > It's mostly ideal for experimental and learning projects.** From 17c6aabe280cecb8841ac3a5de8be263359d82da Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Wed, 18 Dec 2024 16:49:13 +0200 Subject: [PATCH 139/150] Update Testing.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- docs/readme/Testing.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/readme/Testing.md b/docs/readme/Testing.md index db83c5d5..fdac71d2 100644 --- a/docs/readme/Testing.md +++ b/docs/readme/Testing.md @@ -4,11 +4,11 @@ We'd appreciate it if you could help test the app. For now, the filesystem mount Here are some ways you can do it. -## Testing in the browser or local VSCode +## Testing in VSCode in browser or local You'll need a GitHub account for this. -This will create a Codespace instance on GitHub, which is a Linux container, so we will be able to test it. +This will create a Codespace instance on GitHub, a Linux container, so we can test it. The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month free for Codespace, which means 60 hours for that instance. We will connect to it from the browser and the local VSCode. ### First setup @@ -19,11 +19,10 @@ The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month fre 3. Create codespace on main ![image](https://github.com/user-attachments/assets/5fee55f6-ef54-427c-b790-c135312d3355) 4. This will create the container on GitHub. If it asks you to setup config, select the minimum possible CPU and RAM -5. Start it and leave it to finish. This could take a bit longer -6. Goto terminal in the browser version of the VSCode editor you're presented with. It should be at the bottom, or open it from the menu `Terminal -> New Terminal` -7. You can find the menu in the top left, with 3 lines icon +5. Start it and leave it to finish. This could take a bit longer. This will open a VSCode in the browser +6. Goto terminal in the browser version of the VSCode editor you're presented with. It should be at the bottom. If not, open it from the `Terminal -> New Terminal` menu. You can find the menu in the top left, with 3 lines icon ![image](https://github.com/user-attachments/assets/48681023-e450-49b3-8526-ec0323be0d40) -8. Install Rust by pasting these in the terminal: +7. Install Rust by pasting these in the terminal: ```bash apt-get update && apt-get install fuse3 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh @@ -36,7 +35,7 @@ The instance config is 2 CPUs and 4 GB RAM. You have 120 CPU hours per month fre apt install rustc rustc ``` -9. Create a `tmp` folder, which we will use to copy files from our machine, by pasting this in the terminal: +8. Create needed folders and a `tmp` folder, which we will use to copy files from our machine, by pasting this in the terminal: ```bash mkdir tmp && mkdir final && mkdir data ``` From 4094ac88e768a2cc91eb46bc546f418bab3be113 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Wed, 18 Dec 2024 17:15:11 +0200 Subject: [PATCH 140/150] Update Build_from_Source.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- docs/readme/Build_from_Source.md | 35 +++++++++++++++++--------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/readme/Build_from_Source.md b/docs/readme/Build_from_Source.md index 4b6189cb..ab28a2ac 100644 --- a/docs/readme/Build_from_Source.md +++ b/docs/readme/Build_from_Source.md @@ -1,35 +1,39 @@ # Build from source -## Browser +## In the browser If you want to give it a quick try and not setup anything locally, you can [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/radumarias/rencfs) [![Open Rustlings On Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/?repo=radumarias%2Frencfs&ref=main) -You can compile it, run it, and give it a quick try in the browser. After you start it from above +You can compile, run, and try it quickly in the browser. After you start it from above ```bash apt-get update && apt-get install fuse3 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -mkdir mnt && mkdir data -cargo run --release -- mount -m mnt -d data +mkdir final && mkdir data +cargo run --release -- mount -m final -d data ``` Open another terminal ```bash -cd mnt +cd final mkdir a && cd a echo "test" > test.txt cat test.txt ``` +You can also: +- Copy files and folders from your local machine to `tmp` folder in VSCode in the browser. So that we eliminate network errors when testing +- Then copy files and folders from `tmp` to `final` and then do your operations in the `final` folder +- Ensure files were copied successfully by right-clicking a file and then `Download...` and saving it to the local machine to ensure it opens correctly. For popular formats like image text, you can preview them in the browser editor + ## Locally -For now, the `FUSE` (`fuse3` crate) only works on `Linux`, so to start the project, you will need to be on Linux. -Instead, you can [Develop inside a Container](#developing-inside-a-container), which will start a local Linux container, the IDE will connect to it, -and you can build and start the app there and also use the terminal to test it. +For now, the `FUSE` (`fuse3` crate) only works on `Linux`, so you must be on Linux to start the project. +Instead, you can [Develop inside a Container](#developing-inside-a-container) by starting a local Linux container to which the IDE will connect. You can build, run, and debug the app there and use the terminal to test it. On Windows, you can start it in [WSL](https://harsimranmaan.medium.com/install-and-setup-rust-development-environment-on-wsl2-dccb4bf63700). ### Getting the sources @@ -42,25 +46,24 @@ git clone git@github.com:radumarias/rencfs.git && cd rencfs #### Rust -To build from source, you need to have Rust installed, you can see more details on how to install -it [here](https://www.rust-lang.org/tools/install). +To build from the source, you need to have Rust installed. You can see more details on installing it [here](https://www.rust-lang.org/tools/install). ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ```` Accordingly, it is customary for Rust developers to include this directory in their `PATH` environment variable. -During installation `rustup` will attempt to configure the `PATH`. Because of differences between platforms, command +During installation, `rustup` will attempt to configure the `PATH`. Because of differences between platforms, command shells, -and bugs in `rustup`, the modifications to `PATH` may not take effect until the console is restarted, or the user is +and bugs in `rustup`, the modifications to `PATH` may not take effect until the console is restarted or the user is logged out, or it may not succeed at all. If, after installation, running `rustc --version` in the console fails, this is the most likely reason. -In that case please add it to the `PATH` manually. +In that case, please manually add it to the `PATH`. The project is set up to use the `nightly` toolchain in `rust-toolchain. tool`; on the first build, you will see it fetch the nightly. -Make sure to add this to your `$PATH` too +Make sure to add this to your `$PATH` too. ```bash export PATH="$PATH::$HOME/.cargo/bin" @@ -119,7 +122,7 @@ If you don't want to be prompted for a password, you can set this env var and ru RENCFS_PASSWORD=PASS cargo run --release -- mount --mount-point MOUNT_POINT --data-dir DATA_DIR ``` -For dev mode it is recommended to run with `DEBUG` log level: +For dev mode, it is recommended to run with `DEBUG` log level: ```bash cargo run --release -- --log-level DEBUG mount --mount-point MOUNT_POINT --data-dir DATA_DIR @@ -149,4 +152,4 @@ sudo dnf localinstall rencfs-xxx.x86_64.rpm See here how to configure for [RustRover](https://www.jetbrains.com/help/rust/connect-to-devcontainer.html) and for [VsCode](https://code.visualstudio.com/docs/devcontainers/containers). You can use the `.devcontainer` directory from the project to start a container with all the necessary tools to build -and run the app. \ No newline at end of file +and run the app. From 11d87caa345fcda41849ed5a562fcf357de76857 Mon Sep 17 00:00:00 2001 From: hardworking <161673729+hardworking-toptal-dev@users.noreply.github.com> Date: Sat, 21 Dec 2024 13:34:32 +0900 Subject: [PATCH 141/150] chore: Update Ramp-up.md style (#268) * chore: Update Ramp-up.md style Signed-off-by: hardworking <161673729+hardworking-toptal-dev@users.noreply.github.com> * update docs * update docs --------- Signed-off-by: hardworking <161673729+hardworking-toptal-dev@users.noreply.github.com> Co-authored-by: Radu Marias <radumarias@gmail.com> --- docs/readme/Ramp-up.md | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/docs/readme/Ramp-up.md b/docs/readme/Ramp-up.md index 85502637..08edf47f 100644 --- a/docs/readme/Ramp-up.md +++ b/docs/readme/Ramp-up.md @@ -1,30 +1,13 @@ # Ramp-up guide -1. Read - an [article](https://medium.com/system-weakness/hitchhikers-guide-to-building-a-distributed-filesystem-in-rust-the-very-beginning-2c02eb7313e7) - and an [one pager](The_Hitchhiker_s_Guide_to_Building_an_Encrypted_Filesystem_in_Rust-1.pdf) to get more details - about the project -2. -Read [Basics of cryptography for building a filesystem in Rust](https://miro.com/app/board/uXjVLccxeCE=/?share_link_id=962517464374) -and [Building an Encrypted Filesystem in Rust](https://miro.com/app/board/uXjVLa8i1h0=/?share_link_id=745134849333) -3. Become familiar with the [concepts and features](https://github.com/radumarias/rencfs) - and [lib docs](https://docs.rs/rencfs/latest/rencfs) -4. Understand the [layers](https://github.com/radumarias/rencfs/blob/main/website/resources/layers.png) -5. Detailed [sequence flows](flows.md) -6. [Talks](https://startech-rd.io/hitchhikers-guide-to/) -7. Give it a [quick try](https://github.com/radumarias/rencfs#give-it-a-quick-try-with-docker) with Docker -8. Or run it as [CLI](https://github.com/radumarias/rencfs?tab=readme-ov-file#command-line-tool) app -9. Clone or fork the repo. After being added to it, you can work in your branches in the original repo. No need to fork - it if you don't want to -10. [Build](https://github.com/radumarias/rencfs?tab=readme-ov-file#build-from-source) from source and start it. If you - don't have Linux, you - can [develop inside a container](https://github.com/radumarias/rencfs?tab=readme-ov-file#developing-inside-a-container). - This will start a new Linux container and remotely connecting the local IDE to the container, you can also connect - with IDE's terminal to it and run the code. On Windows, you can - use [WSL](https://harsimranmaan.medium.com/install-and-setup-rust-development-environment-on-wsl2-dccb4bf63700). As - a last resort, you can [develop in browser](https://github.com/radumarias/rencfs/blob/main/README.md#browser). -11. Run and understand [examples](../../examples). You can write some new ones to understand the flow and code better. - If you do, please create a `PR` back to the parent repo targeting the `main` branch to include those for others too -12. Become familiar with [tests](https://github.com/radumarias/rencfs/blob/main/src/encryptedfs/test.rs) (and in other - files) and benchmarks. You can write some new ones to understand the flow and code better. If you do, please create - a `PR` back to the parent repo targeting the `main` branch to include those for others too +1. Become familiar with the [basic concepts and features](https://github.com/xoriors/rencfs?tab=readme-ov-file#-rencfs) and [lib docs](https://docs.rs/rencfs/latest/rencfs). +2. Give it a [quick try](https://github.com/xoriors/rencfs#give-it-a-quick-try-with-docker) with Docker. +3. Or run it as [CLI app](https://github.com/xoriors/rencfs?tab=readme-ov-file#command-line-tool). +4. Clone or fork the repo. After being added to it, you can create your branches in the original repo, no need to fork it, only if you want to. +5. [Build from source](Build_from_Source.md) and start it. +6. For now the app works only on Linux, but if you don't have Linux, you can [develop inside a container](Build_from_Source.md#developing-inside-a-container). + This will start a new Linux container and remotely connecting the local IDE to it. You can also connect with the IDE's terminal to it, run and debug the code. + On Windows, you can use [WSL](https://harsimranmaan.medium.com/install-and-setup-rust-development-environment-on-wsl2-dccb4bf63700). +7. As a last resort, you can [develop in browser](Build_from_Source.md#browser). +8. Understand and run the [examples](../../examples). You can write your own to better understand the flow and code. If you do, please create a `PR` back to the parent repo targeting the `main` branch to include those for others too. +9. Become familiar with [tests](../src/encryptedfs/test.rs) and [benchmarks](../benches), and in other files. You can write your own to better understand the flow and code. If you do, please create a `PR` back to the parent repo targeting the `main` branch to include those for others too. From 7abe22ce720278847e82fbcc2ec09dc5810f28fd Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sat, 21 Dec 2024 09:24:54 +0200 Subject: [PATCH 142/150] Update README.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fed6d9c8..1567b128 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ If you find any issues or vulnerabilities or you'd like a feature, please follow Feel free to fork, change, and use it however you want. We always appreciate it if you build something interesting and feel like sharing pull requests. - How to contribute - Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md). + - Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md). # Follow us From 4c906a2d675eba2e8b63be823d4e3d46c3723d6f Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sat, 21 Dec 2024 09:25:43 +0200 Subject: [PATCH 143/150] Update README.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1567b128..5f34212e 100644 --- a/README.md +++ b/README.md @@ -114,8 +114,8 @@ If you find any issues or vulnerabilities or you'd like a feature, please follow Feel free to fork, change, and use it however you want. We always appreciate it if you build something interesting and feel like sharing pull requests. -- How to contribute - - Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md). +How to contribute: +- Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md). # Follow us From 3fa3492c2f922a7c537e0ce3c7edbfe35cc43074 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sat, 21 Dec 2024 09:26:54 +0200 Subject: [PATCH 144/150] Update CONTRIBUTING.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 427bc8db..bc67abd5 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,7 +6,7 @@ Unless you explicitly state otherwise, any Contribution intentionally submitted for inclusion in this project by you, as defined in the Apache License shall be dual-licensed as above, without any additional terms or conditions. -1. Join [slack](https://bit.ly/3UU1oXi) and join `#dev-beginners` channel +1. Join the project's [slack](https://bit.ly/3UU1oXi) (this is for the `rencfs` project, please use the one for your project) and join the `#dev-beginners` channel (or any relevant ones) 2. **Ask the owner of the repository to add your GitHub username to the repository** so that you can work on issues and be able to create your own branches and not need to fork the repo From 52bd67e8a05f6b9fc81803f4a0eb003b635a735d Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Sat, 21 Dec 2024 09:30:57 +0200 Subject: [PATCH 145/150] Update README.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f34212e..4c786d80 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ If you find any issues or vulnerabilities or you'd like a feature, please follow Feel free to fork, change, and use it however you want. We always appreciate it if you build something interesting and feel like sharing pull requests. How to contribute: -- Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md). +- Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md) # Follow us From 7b83cb0985e0f29e0c91c08d9a7fdc8e47fbf0c3 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Mon, 23 Dec 2024 19:25:36 +0200 Subject: [PATCH 146/150] Update Build_from_Source.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- docs/readme/Build_from_Source.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme/Build_from_Source.md b/docs/readme/Build_from_Source.md index ab28a2ac..5a05ebbf 100644 --- a/docs/readme/Build_from_Source.md +++ b/docs/readme/Build_from_Source.md @@ -149,7 +149,7 @@ sudo dnf localinstall rencfs-xxx.x86_64.rpm ## Developing inside a Container -See here how to configure for [RustRover](https://www.jetbrains.com/help/rust/connect-to-devcontainer.html) and for [VsCode](https://code.visualstudio.com/docs/devcontainers/containers). +See here how to configure for [RustRover](https://www.jetbrains.com/help/rust/connect-to-devcontainer.html) and for [VSCode](https://code.visualstudio.com/docs/devcontainers/containers). You can use the `.devcontainer` directory from the project to start a container with all the necessary tools to build and run the app. From c7e22d79dfd39e006f5d4ee8155fad72dfae2d3b Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Wed, 25 Dec 2024 03:45:20 +0200 Subject: [PATCH 147/150] Update README.md Signed-off-by: Radu Marias <radumarias@gmail.com> From b76a12ffb87bb22079b6f00ffc9a220f0c62ebff Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Wed, 25 Dec 2024 03:47:36 +0200 Subject: [PATCH 148/150] Update README.md Signed-off-by: Radu Marias <radumarias@gmail.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c786d80..89057a50 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![release](https://github.com/xoriors/rencfs/actions/workflows/release.yaml/badge.svg)](https://github.com/xoriors/rencfs/actions/workflows/release.yaml) [![codecov](https://codecov.io/gh/xoriors/rencfs/graph/badge.svg?token=NUQI6XGF2Y)](https://codecov.io/gh/xoriors/rencfs) <a href="https://bit.ly/3UU1oXi"><img src="website/resources/slack.png" style = "width: 20px; height: 20px;"/></a> -[![Open Source Helpers](https://www.codetriage.com/xoriors/rencfs/badges/users.svg)](https://www.codetriage.com/xoriors/rencfs) +[![Open Source Helpers](https://www.codetriage.com/xoriors/rencfs/badges/users.svg?count=20)](https://www.codetriage.com/xoriors/rencfs) > [!WARNING] > **This crate hasn't been audited; it's using `ring` crate, which is a well-known audited library, so in principle, at From 9cad41683daf6e8894fc037f9413d22f7aeacee7 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Tue, 24 Dec 2024 17:11:18 +0200 Subject: [PATCH 149/150] WAL example --- examples/wal.rs | 124 ++++++++++++++++++++++++++++++++++++++++++++++++ my-log/wal-5-cp | Bin 0 -> 1048576 bytes my-log/wal-6 | Bin 0 -> 1048576 bytes 3 files changed, 124 insertions(+) create mode 100644 examples/wal.rs create mode 100644 my-log/wal-5-cp create mode 100644 my-log/wal-6 diff --git a/examples/wal.rs b/examples/wal.rs new file mode 100644 index 00000000..91694d4e --- /dev/null +++ b/examples/wal.rs @@ -0,0 +1,124 @@ +use std::io::{self, Read}; + +use okaywal::{Entry, EntryId, LogManager, SegmentReader, WriteAheadLog}; + +fn main() -> io::Result<()> { + // begin rustme snippet: readme-example + // Open a log using an Checkpointer that echoes the information passed into each + // function that the Checkpointer trait defines. + let log = WriteAheadLog::recover("my-log", LoggingCheckpointer)?; + + // Begin writing an entry to the log. + let mut writer = log.begin_entry()?; + + // Each entry is one or more chunks of data. Each chunk can be individually + // addressed using its LogPosition. + let record = writer.write_chunk("this is the first entry".as_bytes())?; + + // To fully flush all written bytes to disk and make the new entry + // resilliant to a crash, the writer must be committed. + writer.commit()?; + // end rustme snippet + + log.checkpoint_active()?; + + { + // Begin writing an entry to the log. + let mut writer = log.begin_entry()?; + + // Each entry is one or more chunks of data. Each chunk can be individually + // addressed using its LogPosition. + let _ = writer.write_chunk("this is the second entry".as_bytes())?; + // panic!("this will cause the entry to be rolled back"); + } + + // don't commit this entry + + // Let's reopen the log. During this process, + // LoggingCheckpointer::should_recover_segment will be invoked for each segment + // file that has not been checkpointed yet. In this example, it will be called + // once. Once the Checkpointer confirms the data should be recovered, + // LoggingCheckpointer::recover will be invoked once for each entry in the WAL + // that hasn't been previously checkpointed. + drop(log); + let log = WriteAheadLog::recover("my-log", LoggingCheckpointer)?; + + // We can use the previously returned DataRecord to read the original data. + let mut reader = log.read_at(record.position)?; + let mut buffer = vec![0; usize::try_from(record.length).unwrap()]; + reader.read_exact(&mut buffer)?; + println!( + "Data read from log: {}", + String::from_utf8(buffer).expect("invalid utf-8") + ); + + // Cleanup + drop(reader); + drop(log); + std::fs::remove_dir_all("my-log")?; + + Ok(()) +} + +#[derive(Debug)] +struct LoggingCheckpointer; + +impl LogManager for LoggingCheckpointer { + fn recover(&mut self, entry: &mut Entry<'_>) -> io::Result<()> { + // This example uses read_all_chunks to load the entire entry into + // memory for simplicity. The crate also supports reading each chunk + // individually to minimize memory usage. + if let Some(all_chunks) = entry.read_all_chunks()? { + // Convert the Vec<u8>'s to Strings. + let all_chunks = all_chunks + .into_iter() + .map(String::from_utf8) + .collect::<Result<Vec<String>, _>>() + .expect("invalid utf-8"); + println!( + "LoggingCheckpointer::recover(entry_id: {:?}, data: {:?})", + entry.id(), + all_chunks, + ); + } else { + // This entry wasn't completely written. This could happen if a + // power outage or crash occurs while writing an entry. + println!( + "LoggingCheckpointer::recover(entry_id: {:?}, data: not fully written)", + entry.id(), + ); + } + + Ok(()) + } + + fn checkpoint_to( + &mut self, + last_checkpointed_id: EntryId, + _checkpointed_entries: &mut SegmentReader, + _wal: &WriteAheadLog, + ) -> io::Result<()> { + // checkpoint_to is called once enough data has been written to the + // WriteAheadLog. After this function returns, the log will recycle the + // file containing the entries being checkpointed. + // + // This function is where the entries must be persisted to the storage + // layer the WriteAheadLog is sitting in front of. To ensure ACID + // compliance of the combination of the WAL and the storage layer, the + // storage layer must be fully resilliant to losing any changes made by + // the checkpointed entries before this function returns. + println!("LoggingCheckpointer::checkpoint_to({last_checkpointed_id:?}"); + Ok(()) + } +} + +#[test] +fn test() -> io::Result<()> { + // Clean up any previous runs of this example. + let path = std::path::Path::new("my-log"); + if path.exists() { + std::fs::remove_dir_all("my-log")?; + } + + main() +} diff --git a/my-log/wal-5-cp b/my-log/wal-5-cp new file mode 100644 index 0000000000000000000000000000000000000000..a3f04b5c8f3170d206ee1e72164b8cce48d121d6 GIT binary patch literal 1048576 zcmeIuK?*=n6o%1<Y{10yEyPAFfJjlik{wwl#R5##x!w9F-{>sU9zs@z=kogAG>luk zKMh^%$8}4wo6~wUXVn&2{<$o^mTzYW5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk X1PBlyK!5-N0t5&UAV7csfxik|@<AW1 literal 0 HcmV?d00001 diff --git a/my-log/wal-6 b/my-log/wal-6 new file mode 100644 index 0000000000000000000000000000000000000000..8e2c172259fd80e78cf83d26d936af905ee137df GIT binary patch literal 1048576 zcmeIu%L#xm00YqanZS$J4&ovXpeTYuEr=UArh<Y?yb#DGr?)Lf2t^&<l<l*5n)moL zPeUB%wC6aiIi20r^wsw>1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ R009C72oNAZfB=DC;0AYM3vmDd literal 0 HcmV?d00001 From a3a615b46e548452a261e89b5dbd6a51060663c8 Mon Sep 17 00:00:00 2001 From: Radu Marias <radumarias@gmail.com> Date: Wed, 25 Dec 2024 16:14:46 +0200 Subject: [PATCH 150/150] v++. FIx path in wal example. Remove target on check-before-push --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/wal.rs | 6 +++--- java-bridge/Cargo.lock | 2 +- scripts/check-before-push.sh | 34 +++++++++++++++++----------------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 612ab960..594afb55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1845,7 +1845,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rencfs" -version = "0.13.78" +version = "0.13.79" dependencies = [ "anyhow", "argon2", diff --git a/Cargo.toml b/Cargo.toml index f00350de..eb40bda7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ cargo-features = ["profile-rustflags"] [package] name = "rencfs" description = "An encrypted file system that is mounted with FUSE on Linux. It can be used to create encrypted directories." -version = "0.13.78" +version = "0.13.79" edition = "2021" license = "MIT OR Apache-2.0" authors = ["Radu Marias <radumarias@gmail.com>"] diff --git a/examples/wal.rs b/examples/wal.rs index 91694d4e..19f1a73f 100644 --- a/examples/wal.rs +++ b/examples/wal.rs @@ -6,7 +6,7 @@ fn main() -> io::Result<()> { // begin rustme snippet: readme-example // Open a log using an Checkpointer that echoes the information passed into each // function that the Checkpointer trait defines. - let log = WriteAheadLog::recover("my-log", LoggingCheckpointer)?; + let log = WriteAheadLog::recover("/tmp/rencfs/wal/my-log", LoggingCheckpointer)?; // Begin writing an entry to the log. let mut writer = log.begin_entry()?; @@ -41,7 +41,7 @@ fn main() -> io::Result<()> { // LoggingCheckpointer::recover will be invoked once for each entry in the WAL // that hasn't been previously checkpointed. drop(log); - let log = WriteAheadLog::recover("my-log", LoggingCheckpointer)?; + let log = WriteAheadLog::recover("/tmp/rencfs/wal/my-log", LoggingCheckpointer)?; // We can use the previously returned DataRecord to read the original data. let mut reader = log.read_at(record.position)?; @@ -55,7 +55,7 @@ fn main() -> io::Result<()> { // Cleanup drop(reader); drop(log); - std::fs::remove_dir_all("my-log")?; + std::fs::remove_dir_all("/tmp/rencfs/wal/my-log-log")?; Ok(()) } diff --git a/java-bridge/Cargo.lock b/java-bridge/Cargo.lock index 82b9c1e7..af246e7d 100644 --- a/java-bridge/Cargo.lock +++ b/java-bridge/Cargo.lock @@ -1923,7 +1923,7 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rencfs" -version = "0.13.78" +version = "0.13.79" dependencies = [ "anyhow", "argon2", diff --git a/scripts/check-before-push.sh b/scripts/check-before-push.sh index 028407c3..cb090c8d 100755 --- a/scripts/check-before-push.sh +++ b/scripts/check-before-push.sh @@ -10,42 +10,42 @@ export CARGO_BUILD_JOBS=14 cargo fmt --all -cargo build --all-targets --all-features --target x86_64-unknown-linux-gnu -cargo build --release --all-targets --all-features --target x86_64-unknown-linux-gnu -cargo clippy --release --all-targets --fix --allow-dirty --allow-staged --target x86_64-unknown-linux-gnu +cargo build --all-targets --all-features +cargo build --release --all-targets --all-features +cargo clippy --release --all-targets --fix --allow-dirty --allow-staged cargo fmt --all -- --check -cargo check --all --target x86_64-unknown-linux-gnu -cargo clippy --all-targets --release --target x86_64-unknown-linux-gnu -- \ +cargo check --all +cargo clippy --all-targets --release -- \ -A clippy::similar_names \ -A clippy::too_many_arguments \ -A clippy::significant_drop_tightening \ -A clippy::redundant_closure \ -A clippy::missing_errors_doc \ -A clippy::type_complexity -cargo test --release --all --all-features --target x86_64-unknown-linux-gnu -cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu -j 14 -cargo doc --workspace --all-features --no-deps --target x86_64-unknown-linux-gnu +cargo test --release --all --all-features +cargo bench --workspace --all-targets --all-features -j 14 +cargo doc --workspace --all-features --no-deps -# cargo publish --dry-run --allow-dirty --target x86_64-unknown-linux-gnu +# cargo publish --dry-run --allow-dirty cargo aur cargo generate-rpm cd java-bridge cargo fmt --all -cargo build --all-targets --all-features --target x86_64-unknown-linux-gnu -cargo build --release --all-targets --all-features --target x86_64-unknown-linux-gnu -cargo clippy --release --all-targets --fix --allow-dirty --allow-staged --target x86_64-unknown-linux-gnu +cargo build --all-targets --all-features +cargo build --release --all-targets --all-features +cargo clippy --release --all-targets --fix --allow-dirty --allow-staged cargo fmt --all -- --check -cargo check --all --target x86_64-unknown-linux-gnu -cargo clippy --all-targets --release --target x86_64-unknown-linux-gnu -- \ +cargo check --all +cargo clippy --all-targets --release -- \ -A clippy::similar_names \ -A clippy::too_many_arguments \ -A clippy::significant_drop_tightening \ -A clippy::redundant_closure \ -A clippy::missing_errors_doc \ -A clippy::type_complexity -cargo test --release --all --all-features --target x86_64-unknown-linux-gnu -cargo bench --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu -j 14 -cargo doc --workspace --all-features --no-deps --target x86_64-unknown-linux-gnu +cargo test --release --all --all-features +cargo bench --workspace --all-targets --all-features -j 14 +cargo doc --workspace --all-features --no-deps cd ..