Skip to content

Commit

Permalink
add cache for decrypting dir entry names
Browse files Browse the repository at this point in the history
add cache for reading meta for dir entries
do some operations concurrently when creating a nod
on encryption use block index as AAD to make sure blocks are not reordered
add bech tests
force unmount
  • Loading branch information
radumarias committed May 16, 2024
1 parent 125c007 commit 73d66ea
Show file tree
Hide file tree
Showing 13 changed files with 1,599 additions and 1,029 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

An encrypted file system that mounts with FUSE on Linux. It can be used to create encrypted directories.

You can then safely backup the encrypted folder on an untrusted server without worrying about the data being exposed.\
You can then safely backup the encrypted folder on an untrusted server without worrying about the data being exposed.
You can also store it in any cloud storage like Google Drive, Dropbox, etc. and have it synced across multiple devices.

[![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/)
Expand All @@ -14,7 +14,7 @@ You can also store it in any cloud storage like Google Drive, Dropbox, etc. and

> ⚠️ **Warning**
> ***This is early in development. Please do not use it with sensitive data just yet. Please wait for a
stable release.\
stable release.
> It's mostly ideal for experimental and learning projects.***
# Functionality
Expand Down Expand Up @@ -117,10 +117,10 @@ You can specify the encryption algorithm adding this argument to the command lin
--cipher CIPHER
```

Where `CIPHER` is the encryption algorithm.\
Where `CIPHER` is the encryption algorithm.
You can check the available ciphers with `rencfs --help`.

Default values are `ChaCha20` and `600_000` respectively.
Default value is `ChaCha20Poly1305`.

### Log level

Expand Down Expand Up @@ -235,7 +235,7 @@ cargo run -- --mount-point MOUNT_POINT --data-dir DATA_DIR
## Developing inside a Container
See here how to configure for [VsCode](https://code.visualstudio.com/docs/devcontainers/containers)\
See here how to configure for [VsCode](https://code.visualstudio.com/docs/devcontainers/containers)
And here for [RustRover](https://www.jetbrains.com/help/rust/connect-to-devcontainer.html)
You can use the `.devcontainer` directory from the project to start a container with all the necessary tools to build
Expand Down Expand Up @@ -268,7 +268,7 @@ sharing pull requests are always appreciated.
see [here](https://pubs.opengroup.org/onlinepubs/009695399/functions/rename.html) `That specification requires that the action of the function be atomic.`
- Phantom reads: reading older content from a file, this is not possible. While writing, data is kept in a buffer and
tmp file and on releasing the file handle we write the new content to the file (as per above the tmp file is moved
into place with `mv`). After that we reset all opened readers so any reads after that will pick up the new content\
into place with `mv`). After that we reset all opened readers so any reads after that will pick up the new content
One problem that may occur is if we do a truncate we change the content of the file but the process is killed before
we write the metadata with the new filesize. In this case next time we mount the system we are still seeing the old
filesize but the content of the file could be bigger, and we read until the old size offset, se we would not pick up
Expand Down
140 changes: 140 additions & 0 deletions examples/okay_wal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::io::{self, Read};

use okaywal::{Entry, EntryId, LogManager, ReadChunkResult, 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("/tmp/my-log", LoggingCheckpointer)?;
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.
for i in 0..10 {
writer.write_chunk(format!("this is the {} entry", i).as_bytes())?;
}
// 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()?;

// 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("/tmp/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")?;

log.shutdown()?;
// drop(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.
}

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:?}");
while let Some(mut entry) = _checkpointed_entries.read_entry()? {
println!(
"LoggingCheckpointer::checkpoint_to(entry_id: {:?})",
entry.id()
);
while let chunk = entry.read_chunk() {
match chunk {
Ok(res) => match res {
ReadChunkResult::Chunk(chunk) => {
println!(
"LoggingCheckpointer::checkpoint_to(chunk bytes: {:?})",
chunk.bytes_remaining()
);
}
ReadChunkResult::EndOfEntry => {
break;
}
ReadChunkResult::AbortedEntry => {
continue;
}
},
Err(err) => {}
}
}
}
Ok(())
}
}

#[test]
fn test() -> io::Result<()> {
// Clean up any previous runs of this example.
// let path = std::path::Path::new("/tmp/my-log");
// if path.exists() {
// std::fs::remove_dir_all("/tmp/my-log")?;
// }

main()
}
97 changes: 6 additions & 91 deletions src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,10 @@ use thiserror::Error;
use tokio::io::{AsyncRead, AsyncReadExt};
use tracing::{debug, error, instrument};

use crate::crypto::reader::{
ChunkedFileCryptoReader, CryptoReader, FileCryptoReader, RingCryptoReader,
};
use crate::crypto::reader::{CryptoReader, FileCryptoReader, RingCryptoReader};
use crate::crypto::writer::{
ChunkedTmpFileCryptoWriter, CryptoWriter, CryptoWriterSeek, FileCryptoWriter,
FileCryptoWriterCallback, FileCryptoWriterMetadataProvider, RingCryptoWriter,
SequenceLockProvider,
CryptoWriter, CryptoWriterSeek, FileCryptoWriter, FileCryptoWriterCallback,
FileCryptoWriterMetadataProvider, RingCryptoWriter,
};
use crate::encryptedfs::FsResult;
use crate::{fs_util, stream_util};
Expand Down Expand Up @@ -124,10 +121,10 @@ pub fn create_writer<W: Write + Send + Sync>(

/// **`callback`** is called when the file content changes. It receives the position from where the file content changed and the last write position
///
/// **`lock`** is used to write lock the file when accessing it. If not provided, it will not ensure that other instances are not writing to the file while we do\
/// **`lock`** is used to write lock the file when accessing it. If not provided, it will not ensure that other instances are not writing to the file while we do
/// You need to provide the same lock to all writers and readers of this file, you should obtain a new [`Holder`] that wraps the same lock
///
/// **`metadata_provider`** it's used to do some optimizations to reduce some copy operations from original file\
/// **`metadata_provider`** it's used to do some optimizations to reduce some copy operations from original file
/// If the file exists or is created before flushing, in worse case scenarios, it can reduce the overall write speed by half, so it's recommended to provide it
#[allow(clippy::missing_errors_doc)]
pub fn create_file_writer(
Expand All @@ -148,48 +145,6 @@ pub fn create_file_writer(
)?))
}

/// **`callback`** is called when the file content changes. It receives the position from where the file content changed and the last write position
///
/// **`locks`** is used to write lock the chunks files when accessing them. This ensures that we have exclusive write to a given chunk when we need to change it's content\
/// If not provided, it will not ensure that other instances are not accessing the chunks while we do\
/// You need to provide the same locks to all writers and readers of this file, you should obtain a new [`Holder`] that wraps the same locks
///
/// **`metadata_provider`** it's used to do some optimizations to reduce some copy operations from original file\
/// If the file exists or is created before flushing, in worse case scenarios, it can reduce the overall write speed by half, so it's recommended to provide it\
#[allow(clippy::missing_errors_doc)]
pub fn create_chunked_tmp_file_writer(
file_dir: &Path,
cipher: Cipher,
key: Arc<SecretVec<u8>>,
callback: Option<Box<dyn FileCryptoWriterCallback>>,
locks: Option<Holder<Box<dyn SequenceLockProvider>>>,
metadata_provider: Option<Box<dyn FileCryptoWriterMetadataProvider>>,
) -> io::Result<Box<dyn CryptoWriterSeek<File>>> {
Ok(Box::new(ChunkedTmpFileCryptoWriter::new(
file_dir,
cipher,
key,
callback,
locks,
metadata_provider,
)?))
}

/// **`locks`** is used to read lock the chunks files when accessing them. This ensures offer multiple reads but exclusive writes to a given chunk\
/// If not provided, it will not ensure that other instances are not writing the chunks while we read them\
/// You need to provide the same locks to all writers and readers of this file, you should obtain a new [`Holder`] that wraps the same locks
#[allow(clippy::missing_errors_doc)]
pub fn create_chunked_file_reader(
file_dir: &Path,
cipher: Cipher,
key: Arc<SecretVec<u8>>,
locks: Option<Holder<Box<dyn SequenceLockProvider>>>,
) -> io::Result<Box<dyn CryptoReader>> {
Ok(Box::new(ChunkedFileCryptoReader::new(
file_dir, cipher, key, locks,
)?))
}

fn create_ring_writer<W: Write + Send + Sync>(
writer: W,
cipher: Cipher,
Expand All @@ -214,23 +169,6 @@ fn create_ring_reader<R: Read + Seek + Send + Sync>(
RingCryptoReader::new(reader, algorithm, key)
}

// fn _create_cryptostream_crypto_writer(mut file: File, cipher: &Cipher, key: &SecretVec<u8>) -> impl CryptoWriter<File> {
// let iv_len = match cipher {
// Cipher::ChaCha20 => 16,
// Cipher::Aes256Gcm => 16,
// };
// let mut iv: Vec<u8> = vec![0; iv_len];
// if file.metadata().unwrap().size() == 0 {
// // generate random IV
// thread_rng().fill_bytes(&mut iv);
// file.write_all(&iv).unwrap();
// } else {
// // read IV from file
// file.read_exact(&mut iv).unwrap();
// }
// CryptostreamCryptoWriter::new(file, get_cipher(cipher), &key.expose_secret(), &iv).unwrap()
// }

pub fn create_reader<R: Read + Seek + Send + Sync>(
reader: R,
cipher: Cipher,
Expand All @@ -239,7 +177,7 @@ pub fn create_reader<R: Read + Seek + Send + Sync>(
create_ring_reader(reader, cipher, key)
}

/// **`lock`** is used to read lock the file when accessing it. If not provided, it will not ensure that other instances are not writing to the file while we read\
/// **`lock`** is used to read lock the file when accessing it. If not provided, it will not ensure that other instances are not writing to the file while we read
/// You need to provide the same lock to all writers and readers of this file, you should obtain a new [`Holder`] that wraps the same lock
#[allow(clippy::missing_errors_doc)]
pub fn create_file_reader(
Expand All @@ -251,29 +189,6 @@ pub fn create_file_reader(
Ok(Box::new(FileCryptoReader::new(file, cipher, key, lock)?))
}

// fn _create_cryptostream_crypto_reader(mut file: File, cipher: &Cipher, key: &SecretVec<u8>) -> CryptostreamCryptoReader<File> {
// let iv_len = match cipher {
// Cipher::ChaCha20 => 16,
// Cipher::Aes256Gcm => 16,
// };
// let mut iv: Vec<u8> = vec![0; iv_len];
// if file.metadata().unwrap().size() == 0 {
// // generate random IV
// thread_rng().fill_bytes(&mut iv);
// file.write_all(&iv).map_err(|err| {
// error!("{err}");
// err
// }).unwrap();
// } else {
// // read IV from file
// file.read_exact(&mut iv).map_err(|err| {
// error!("{err}");
// err
// }).unwrap();
// }
// CryptostreamCryptoReader::new(file, get_cipher(cipher), &key.expose_secret(), &iv).unwrap()
// }

#[allow(clippy::missing_errors_doc)]
pub fn encrypt_string(s: &SecretString, cipher: Cipher, key: Arc<SecretVec<u8>>) -> Result<String> {
let mut cursor = io::Cursor::new(vec![]);
Expand Down
Loading

0 comments on commit 73d66ea

Please sign in to comment.