diff --git a/Cargo.lock b/Cargo.lock index c2c900c..401fdc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,7 +71,7 @@ dependencies = [ "anyhow", "bare-metrics-core", "crossbeam-channel", - "dashmap", + "dashmap 4.0.2", "fxhash", "hdrhistogram", "log", @@ -293,6 +293,47 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "dashmap" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b799062aaf67eb976af3bdca031ee6f846d2f0a5710ddbb0d2efee33f3cc4760" +dependencies = [ + "cfg-if 1.0.0", + "num_cpus", + "parking_lot 0.11.2", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "der-oid-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c73af209b6a5dc8ca7cbaba720732304792cddc933cfea3d74509c2b1ef2f436" +dependencies = [ + "num-bigint", + "num-traits", + "syn", +] + +[[package]] +name = "der-parser" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9807efb310ce4ea172924f3a69d82f9fd6c9c3a19336344591153e665b31c43e" +dependencies = [ + "der-oid-macro", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "ed25519" version = "1.3.0" @@ -748,6 +789,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -777,6 +829,15 @@ dependencies = [ "libc", ] +[[package]] +name = "oid-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe554cb2393bc784fd678c82c84cc0599c31ceadc7f03a594911f822cb8d1815" +dependencies = [ + "der-parser", +] + [[package]] name = "olivefs" version = "0.1.0" @@ -789,7 +850,6 @@ dependencies = [ "libc", "log", "quinn", - "rcgen", "serde", "serde_bare", "sodiumoxide", @@ -821,10 +881,13 @@ version = "0.1.0" dependencies = [ "anyhow", "bare-metrics-recorder", + "clap", + "dashmap 5.0.0", "env_logger", "futures-util", "log", "quinn", + "rcgen", "rustls", "serde", "serde_bare", @@ -1115,6 +1178,7 @@ dependencies = [ "chrono", "pem", "ring", + "x509-parser", "yasna", ] @@ -1174,6 +1238,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c52377bb2288aa522a0c8208947fada1e0c76397f108cc08f57efe6077b50d" +dependencies = [ + "nom", +] + [[package]] name = "rustls" version = "0.20.2" @@ -1888,6 +1961,24 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "x509-parser" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc90836a84cb72e6934137b1504d0cae304ef5d83904beb0c8d773bbfe256ed" +dependencies = [ + "base64", + "chrono", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror", +] + [[package]] name = "yasna" version = "0.4.0" diff --git a/olivefs/Cargo.toml b/olivefs/Cargo.toml index 6f58588..463dd04 100644 --- a/olivefs/Cargo.toml +++ b/olivefs/Cargo.toml @@ -29,7 +29,6 @@ quinn = { version = "0.8.0", features = [] } # Compression and Encryption zstd = "0.9.2+zstd.1.5.1" sodiumoxide = "0.2.7" -rcgen = "0.8.14" # Filesystem fuser = "0.10.0" diff --git a/olivefs/src/filesystem.rs b/olivefs/src/filesystem.rs index 4dcd700..a0042b2 100644 --- a/olivefs/src/filesystem.rs +++ b/olivefs/src/filesystem.rs @@ -1,16 +1,26 @@ +use fuser::{ + Filesystem, KernelConfig, ReplyAttr, ReplyBmap, ReplyCreate, ReplyData, + ReplyDirectory, ReplyDirectoryPlus, ReplyEmpty, ReplyEntry, ReplyIoctl, ReplyLock, ReplyLseek, + ReplyOpen, ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow, +}; +use libc::{ENOSYS, EPERM}; +use log::{debug, warn}; use std::ffi::OsStr; + use std::os::raw::c_int; use std::path::Path; -use std::time::SystemTime; -use fuser::{Filesystem, KernelConfig, ReplyAttr, ReplyBmap, ReplyCreate, ReplyData, ReplyDirectory, ReplyDirectoryPlus, ReplyEmpty, ReplyEntry, ReplyIoctl, ReplyLock, ReplyLseek, ReplyOpen, ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow}; -use libc::{ENOSYS, EPERM}; -use log::{warn, debug, info}; +use std::time::{Duration, SystemTime}; pub mod encryption; +pub const ROOT_INODE: u64 = 0x01; +pub struct OliveFilesystemSettings {} + +// TODO support multiple filesystems per FUSE fs so that operations between multiple of them +// can be made efficient. pub struct OliveFilesystem { - + pub settings: OliveFilesystemSettings, } impl Filesystem for OliveFilesystem { @@ -18,6 +28,7 @@ impl Filesystem for OliveFilesystem { /// Called before any other filesystem method. /// The kernel module connection can be configured using the KernelConfig object fn init(&mut self, _req: &Request<'_>, _config: &mut KernelConfig) -> Result<(), c_int> { + // TODO config has some interesting values. Ok(()) } @@ -31,6 +42,29 @@ impl Filesystem for OliveFilesystem { "[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name ); + + let _ttl = Duration::from_secs(5); + + // let file_attr = FileAttr { + // ino: 0, + // size: 0, + // blocks: 0, + // atime: (), + // mtime: (), + // ctime: (), + // crtime: (), + // kind: FileType::NamedPipe, + // perm: 0, + // nlink: 0, + // uid: 0, + // gid: 0, + // rdev: 0, + // blksize: 0, + // flags: 0 + // }; + + // TODO reply.entry(&ttl, ) + reply.error(ENOSYS); } @@ -690,4 +724,4 @@ impl Filesystem for OliveFilesystem { debug!("[Not Implemented] getxtimes(ino: {:#x?})", ino); reply.error(ENOSYS); } -} \ No newline at end of file +} diff --git a/olivefs/src/filesystem/encryption.rs b/olivefs/src/filesystem/encryption.rs index 4cdc2d3..74fae47 100644 --- a/olivefs/src/filesystem/encryption.rs +++ b/olivefs/src/filesystem/encryption.rs @@ -1,10 +1,5 @@ - // TODO Trait needed to provide encryption -pub trait EncryptionProvider { - -} +pub trait EncryptionProvider {} // No encryption -impl EncryptionProvider for () { - -} \ No newline at end of file +impl EncryptionProvider for () {} diff --git a/olivefs/src/main.rs b/olivefs/src/main.rs index 2d910ea..a5a90e7 100644 --- a/olivefs/src/main.rs +++ b/olivefs/src/main.rs @@ -1,18 +1,17 @@ -use std::path::{Path, PathBuf}; -use crate::filesystem::OliveFilesystem; +use crate::filesystem::{OliveFilesystem, OliveFilesystemSettings}; use clap::Parser; use env_logger::Env; +use std::path::{PathBuf}; pub mod filesystem; pub mod requester; - #[derive(Parser)] pub enum OlivefsCommands { Mount { configuration_file: PathBuf, - mount_at: PathBuf - } + mount_at: PathBuf, + }, } fn main() { @@ -23,9 +22,12 @@ fn main() { let command: OlivefsCommands = OlivefsCommands::parse(); match command { - OlivefsCommands::Mount { configuration_file, mount_at } => { + OlivefsCommands::Mount { + configuration_file: _, + mount_at, + } => { let fs = OliveFilesystem { - + settings: OliveFilesystemSettings {}, }; //fuser::MountOption:: fuser::mount2(fs, mount_at, &[]).unwrap(); diff --git a/olivefs/src/requester.rs b/olivefs/src/requester.rs index cee9645..c0f106f 100644 --- a/olivefs/src/requester.rs +++ b/olivefs/src/requester.rs @@ -1,28 +1,31 @@ +use quinn::{Endpoint}; use std::net::SocketAddr; use std::str::FromStr; -use quinn::{Connection, Endpoint, EndpointConfig}; use tokio::sync::mpsc; -pub struct Requester { - -} +pub struct Requester {} pub struct RequesterInternal { rx: mpsc::Receiver<()>, - connection: quinn::Connection + connection: quinn::Connection, } impl RequesterInternal { + //pub async fn connect() pub async fn send(&mut self) -> anyhow::Result<()> { // TODO use with_roots and only use the desired CA! ... - let x = quinn::ClientConfig::with_native_roots(); + let _x = quinn::ClientConfig::with_native_roots(); let ep = Endpoint::client(SocketAddr::from_str("0.0.0.0:0").unwrap()).unwrap(); - let conn = ep.connect(SocketAddr::from_str("127.0.0.1:5051").unwrap(), "blah").unwrap().await.unwrap(); + let conn = ep + .connect(SocketAddr::from_str("127.0.0.1:5051").unwrap(), "blah") + .unwrap() + .await + .unwrap(); // conn.bi_streams and uni_streams are streams needed to listen out for new streams from the server. // conn.datagrams is similar but for unreliable datagrams - let bi = conn.connection.open_bi().await?; + let _bi = conn.connection.open_bi().await?; Ok(()) } -} \ No newline at end of file +} diff --git a/olivefs_common/src/lib.rs b/olivefs_common/src/lib.rs index eb1923e..984e0d3 100644 --- a/olivefs_common/src/lib.rs +++ b/olivefs_common/src/lib.rs @@ -1,4 +1,3 @@ - pub mod messages; pub mod networking; diff --git a/olivefs_common/src/messages.rs b/olivefs_common/src/messages.rs index 3d1ec57..9ba5205 100644 --- a/olivefs_common/src/messages.rs +++ b/olivefs_common/src/messages.rs @@ -1,6 +1,8 @@ -use std::fmt::Debug; -use serde::{Serialize, Deserialize}; use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::fs::FileType; +use std::time::SystemTime; pub const COMMON_VERSION: &'static str = env!("CARGO_PKG_VERSION"); @@ -22,20 +24,16 @@ impl HelloMessage { pub fn new(software_version: String) -> HelloMessage { HelloMessage { protocol_version: COMMON_VERSION.to_string(), - software_version + software_version, } } } #[derive(Serialize, Deserialize, Debug, Clone)] /// Sent by the client on any data stream. -pub enum DataCommand { +pub enum DataCommand {} -} - -pub trait DataResponseBase: Serialize + DeserializeOwned + Debug + Clone + 'static { - -} +pub trait DataResponseBase: Serialize + DeserializeOwned + Debug + Clone + 'static {} #[derive(Serialize, Deserialize, Debug, Clone)] /// Sent by the server in response to a DataCommand on the same data stream. @@ -50,3 +48,38 @@ pub enum DataResponse { message: String, }, } + +/// Copy of fuser's FileAttr. Used to describe a file. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct FileMetadata { + /// Inode number + pub ino: u64, + /// Size in bytes + pub size: u64, + /// Size in blocks + pub blocks: u64, + /// Time of last access + pub atime: SystemTime, + /// Time of last modification + pub mtime: SystemTime, + /// Time of last change + pub ctime: SystemTime, + // /// Time of creation (macOS only) + // pub crtime: SystemTime, + /// Kind of file (directory, file, pipe, etc) + pub kind: FileType, + /// Permissions + pub perm: u16, + /// Number of hard links + pub nlink: u32, + /// User id + pub uid: u32, + /// Group id + pub gid: u32, + /// Rdev + pub rdev: u32, + /// Block size + pub blksize: u32, + // /// Flags (macOS only, see chflags(2)) + // pub flags: u32, +} diff --git a/olivefs_common/src/networking.rs b/olivefs_common/src/networking.rs index e745c9c..40ac161 100644 --- a/olivefs_common/src/networking.rs +++ b/olivefs_common/src/networking.rs @@ -1,8 +1,9 @@ use quinn::{ReadExactError, RecvStream}; use serde::de::DeserializeOwned; - -pub async fn read_control_message(recv_stream: &mut RecvStream) -> anyhow::Result> { +pub async fn read_control_message( + recv_stream: &mut RecvStream, +) -> anyhow::Result> { let mut u16_buf = [0u8; 2]; if let Err(err) = recv_stream.read_exact(&mut u16_buf).await { return if err == ReadExactError::FinishedEarly { @@ -16,7 +17,9 @@ pub async fn read_control_message(recv_stream: &mut RecvStr let mut control_message_bytes: Vec = vec![0u8; control_message_length]; - recv_stream.read_exact(&mut control_message_bytes[..]).await?; + recv_stream + .read_exact(&mut control_message_bytes[..]) + .await?; Ok(todo!()) -} \ No newline at end of file +} diff --git a/olivefsd/Cargo.toml b/olivefsd/Cargo.toml index 1ba20bb..8930825 100644 --- a/olivefsd/Cargo.toml +++ b/olivefsd/Cargo.toml @@ -17,15 +17,18 @@ tracing-futures = { version = "0.2.5", features = ["tokio"] } # Asynchronous tokio = { version = "1.15.0", features = ["full"] } futures-util = "0.3.19" +dashmap = "5.0.0" # Serialisation serde = { version = "1.0.133", features = ["derive"] } serde_bare = "0.5.0" toml = "0.5.8" +clap = { version = "3.0.7", features = ["derive"] } # Networking quinn = { version = "0.8.0", features = [] } -rustls = "0.20.2" # Compression and Encryption zstd = "0.9.2+zstd.1.5.1" +rustls = "0.20.2" +rcgen = { version = "0.8.14", features = ["pem", "x509-parser"] } diff --git a/olivefsd/src/main.rs b/olivefsd/src/main.rs index 8e28c9b..a62584c 100644 --- a/olivefsd/src/main.rs +++ b/olivefsd/src/main.rs @@ -1,11 +1,95 @@ -use std::net::SocketAddr; -use std::str::FromStr; -use std::sync::Arc; +use anyhow::anyhow; +use clap::Parser; use futures_util::StreamExt; use quinn::Endpoint; +use rcgen::{BasicConstraints, Certificate, CertificateParams, IsCa, KeyPair}; +use rustls::internal::msgs::codec::Codec; +use std::io::Read; +use std::net::SocketAddr; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::sync::Arc; +use std::thread::yield_now; +use tokio::fs::File; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +pub mod server; + +#[derive(clap::Parser)] +pub enum Command { + Serve {}, + /// Generates a certificate + GenerateCertificate { + /// The certificate authority directory + ca_dir: PathBuf, + /// Name of the client or server to generate a certificate for + name: String, + }, +} + +pub async fn write_file(path: &Path, content: &str) -> anyhow::Result<()> { + let mut file = File::create(path).await?; + file.write_all(content.as_bytes()).await?; + Ok(()) +} + +pub async fn read_file(path: &Path) -> anyhow::Result { + let mut file = File::open(path).await?; + let mut string = String::new(); + file.read_to_string(&mut string).await?; + Ok(string) +} #[tokio::main] async fn main() -> anyhow::Result<()> { + let command: Command = Command::parse(); + + match command { + Command::Serve { .. } => {} + Command::GenerateCertificate { ca_dir, name } => { + let ca_key_path = ca_dir.join("ca.key"); + let ca_cert_path = ca_dir.join("ca.pem"); + if !ca_key_path.exists() { + // Generate a CA first + let mut ca_params = CertificateParams::new(Vec::with_capacity(0)); + ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); + // TODO don't hard code a date :-). + ca_params.not_after = rcgen::date_time_ymd(2042, 1, 1); + let ca_cert = Certificate::from_params(ca_params)?; + let cert_pem = ca_cert.serialize_pem()?; + let key_pem = ca_cert.serialize_private_key_pem(); + + write_file(&ca_key_path, &key_pem).await?; + write_file(&ca_cert_path, &cert_pem).await?; + } + + let ca_key_pem = read_file(&ca_key_path).await?; + let ca_cert_pem = read_file(&ca_cert_path).await?; + + let ca_keypair = KeyPair::from_pem(&ca_key_pem)?; + let ca_cert = Certificate::from_params(CertificateParams::from_ca_cert_pem( + &ca_cert_pem, + ca_keypair, + )?)?; + + let mut cert_params = CertificateParams::new(vec![name.clone()]); + // TODO don't hard code a date :-). + cert_params.not_after = rcgen::date_time_ymd(2042, 1, 1); + + let leaf_cert = Certificate::from_params(cert_params)?; + write_file( + &ca_dir.join(format!("{}.key", &name)), + &leaf_cert.serialize_private_key_pem(), + ) + .await?; + write_file( + &ca_dir.join(format!("{}.pem", &name)), + &leaf_cert.serialize_pem_with_signer(&ca_cert)?, + ) + .await?; + } + } + println!("Hello, world!"); //let x = quinn::ServerConfig::with_native_roots(); @@ -15,7 +99,8 @@ async fn main() -> anyhow::Result<()> { .with_single_cert(todo!(), todo!())?; let crypto = Arc::new(crypto); let x = quinn::ServerConfig::with_crypto(crypto); - let (ep, mut incoming) = Endpoint::server(x, SocketAddr::from_str("127.0.0.1:5051").unwrap()).unwrap(); + let (ep, mut incoming) = + Endpoint::server(x, SocketAddr::from_str("127.0.0.1:5051").unwrap()).unwrap(); loop { let next = incoming.next().await; diff --git a/olivefsd/src/server.rs b/olivefsd/src/server.rs new file mode 100644 index 0000000..870805c --- /dev/null +++ b/olivefsd/src/server.rs @@ -0,0 +1,11 @@ +use dashmap::DashMap; +use std::sync::Arc; + +pub struct FileDescriptor {} + +pub struct InodeInfo {} + +pub struct ServerHandle { + pub inode_map: Arc>, + pub file_descriptors: Arc>, +}