From 3cf535621dbdb682f529b83708751f0c79004404 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Thu, 20 Jan 2022 20:41:50 +0000 Subject: [PATCH] Support getattr in reality --- olivefs.toml | 3 + olivefs/src/configuration.rs | 6 + olivefs/src/main.rs | 2 + olivefs/src/requester.rs | 12 +- olivefs_common/src/messages.rs | 2 + olivefs_common/src/messages/messages_std.rs | 25 ++++ olivefsd.toml | 4 +- olivefsd/src/configuration.rs | 3 + olivefsd/src/server.rs | 43 ++++++- olivefsd/src/server/connections.rs | 24 ++-- olivefsd/src/server/file_access.rs | 136 ++++++++++++++++++++ 11 files changed, 246 insertions(+), 14 deletions(-) create mode 100644 olivefs_common/src/messages/messages_std.rs diff --git a/olivefs.toml b/olivefs.toml index dad0377..eb3e905 100644 --- a/olivefs.toml +++ b/olivefs.toml @@ -8,3 +8,6 @@ client_key = "ca/bread.key" client_certificate = "ca/bread.crt" ca_certificate = "ca/ca.crt" + +timeout = 60 +keep_alive = 30 diff --git a/olivefs/src/configuration.rs b/olivefs/src/configuration.rs index 540c865..5c9ab23 100644 --- a/olivefs/src/configuration.rs +++ b/olivefs/src/configuration.rs @@ -24,4 +24,10 @@ pub struct ConnectionConfiguration { /// Path to the DER-encoded CA certificate file (.crt). pub ca_certificate: PathBuf, + + /// Idle timeout in seconds. + pub timeout: u32, + + /// Keep-alive in seconds + pub keep_alive: u32, } diff --git a/olivefs/src/main.rs b/olivefs/src/main.rs index 39cb66c..66577e7 100644 --- a/olivefs/src/main.rs +++ b/olivefs/src/main.rs @@ -53,6 +53,8 @@ async fn main() -> anyhow::Result<()> { &config.connection.client_certificate, &config.connection.client_key, socket_addr, + config.connection.timeout, + config.connection.keep_alive, ) .await?; diff --git a/olivefs/src/requester.rs b/olivefs/src/requester.rs index 5cf558e..0252103 100644 --- a/olivefs/src/requester.rs +++ b/olivefs/src/requester.rs @@ -10,6 +10,7 @@ use std::net::SocketAddr; use std::path::Path; use std::str::FromStr; use std::sync::Arc; +use std::time::Duration; use olivefs_common::io::read_file; use olivefs_common::messages::{DataCommand, DataResponse, FileMetadata, VnodeId}; @@ -39,6 +40,8 @@ impl RequesterInternal { cert_path: &Path, cert_key_path: &Path, server_endpoint: SocketAddr, + timeout: u32, + keep_alive: u32, ) -> anyhow::Result { let ca_cert_der = read_file(ca_path).await?; let ca_cert = rustls::Certificate(ca_cert_der); @@ -58,7 +61,14 @@ impl RequesterInternal { // Set the protocol that's in use. client_crypto.alpn_protocols = vec![ALPN_PROTOCOL.to_vec()]; - let client_config = quinn::ClientConfig::new(Arc::new(client_crypto)); + let mut client_config = quinn::ClientConfig::new(Arc::new(client_crypto)); + let transport_config = Arc::get_mut(&mut client_config.transport).unwrap(); + transport_config.max_idle_timeout(Some( + Duration::from_secs(timeout as u64).try_into().unwrap(), + )); + transport_config.keep_alive_interval(Some( + Duration::from_secs(keep_alive as u64).try_into().unwrap(), + )); let mut client_endpoint = Endpoint::client(SocketAddr::from_str("0.0.0.0:0").unwrap()).unwrap(); diff --git a/olivefs_common/src/messages.rs b/olivefs_common/src/messages.rs index 6d89bf5..aaef45f 100644 --- a/olivefs_common/src/messages.rs +++ b/olivefs_common/src/messages.rs @@ -73,6 +73,8 @@ pub enum FileKind { Socket, } +mod messages_std; + #[cfg(feature = "fuser")] mod messages_fuser; diff --git a/olivefs_common/src/messages/messages_std.rs b/olivefs_common/src/messages/messages_std.rs new file mode 100644 index 0000000..cdf7e04 --- /dev/null +++ b/olivefs_common/src/messages/messages_std.rs @@ -0,0 +1,25 @@ +use crate::messages::FileKind; +use std::fs::FileType; +use std::os::unix::fs::FileTypeExt; + +impl From for FileKind { + fn from(ft: FileType) -> Self { + if ft.is_file() { + FileKind::RegularFile + } else if ft.is_dir() { + FileKind::Directory + } else if ft.is_symlink() { + FileKind::Symlink + } else if ft.is_block_device() { + FileKind::BlockDevice + } else if ft.is_char_device() { + FileKind::CharDevice + } else if ft.is_fifo() { + FileKind::NamedPipe + } else if ft.is_socket() { + FileKind::Socket + } else { + panic!("Unknown FileType"); + } + } +} diff --git a/olivefsd.toml b/olivefsd.toml index 7c67fab..94c2c60 100644 --- a/olivefsd.toml +++ b/olivefsd.toml @@ -7,7 +7,9 @@ server_key = "ca/server.key" server_certificate = "ca/server.crt" ca_certificate = "ca/ca.crt" +timeout = 60 + [service] -root = "/home/rei/Music" +root = "/home/quail/Music" [clients.bread] diff --git a/olivefsd/src/configuration.rs b/olivefsd/src/configuration.rs index c59f8ce..1c2570b 100644 --- a/olivefsd/src/configuration.rs +++ b/olivefsd/src/configuration.rs @@ -28,6 +28,9 @@ pub struct ListenConfiguration { /// Path to the CA's DER-encoded certificate (.crt) file. /// This is used to validate client certificates. pub ca_certificate: PathBuf, + + /// Idle timeout in seconds. + pub timeout: u32, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/olivefsd/src/server.rs b/olivefsd/src/server.rs index c3dda52..0376bf4 100644 --- a/olivefsd/src/server.rs +++ b/olivefsd/src/server.rs @@ -1,6 +1,8 @@ use crate::configuration::OlivefsServerConfiguration; use crate::server::connections::handle_new_streams; -use crate::server::file_access::{ClientInformation, ClientSpecificState, ServerwideState}; +use crate::server::file_access::{ + ClientInformation, ClientSpecificState, InodeInfo, ServerwideState, +}; use anyhow::{anyhow, Context}; use futures_util::StreamExt; use log::{error, info, warn}; @@ -10,7 +12,9 @@ use quinn::crypto::rustls::HandshakeData; use quinn::{Connecting, Endpoint}; use rustls::{PrivateKey, RootCertStore}; use std::net::ToSocketAddrs; +use std::path::PathBuf; use std::sync::Arc; +use std::time::Duration; use x509_parser::extensions::{GeneralName, ParsedExtension}; use x509_parser::prelude::X509Certificate; use x509_parser::traits::FromDer; @@ -52,8 +56,15 @@ pub async fn listen(config: Arc) -> anyhow::Result<( crypto.alpn_protocols = vec![ALPN_PROTOCOL.to_vec()]; let crypto = Arc::new(crypto); - let server_config = quinn::ServerConfig::with_crypto(crypto); - let (_ep, mut incoming) = Endpoint::server(server_config, socket_addr).unwrap(); + let mut server_config = quinn::ServerConfig::with_crypto(crypto); + Arc::get_mut(&mut server_config.transport) + .unwrap() + .max_idle_timeout(Some( + Duration::from_secs(config.listen.timeout as u64) + .try_into() + .unwrap(), + )); + let (endpoint, mut incoming) = Endpoint::server(server_config, socket_addr).unwrap(); info!("Listening on {:?}.", socket_addr); @@ -148,14 +159,38 @@ pub async fn establish_connection( .filter(|k| config.clients.contains_key(*k)) .next() .ok_or_else(|| anyhow!("No valid client names."))?; + info!("Selecting name: {:?}", chosen_name); let client_config = &config.clients[chosen_name]; let root = config.service.root.clone(); // Now determine what the client is allowed to do - let client_info = Arc::new(ClientInformation { root }); + let client_info = Arc::new(ClientInformation { root: root.clone() }); let client_state = Arc::new(ClientSpecificState::default()); + { + // Pre-populate the inode map with a null device (to fill slot 0) and the root (inode 1). + let mut inode_map = client_state.inode_map.write().await; + + let root_inode_info = InodeInfo { + real_path: root.clone(), + }; + let null_inode_info = InodeInfo { + real_path: PathBuf::from("/dev/null"), + }; + + assert_eq!( + inode_map.insert(null_inode_info), + 0usize, + "null device should be inode 0 for safety" + ); + assert_eq!( + inode_map.insert(root_inode_info), + 1usize, + "root should be inode 1 by fuser definition" + ); + } + tokio::spawn(async { if let Err(error) = handle_new_streams(new_conn, client_info, client_state, server_state).await diff --git a/olivefsd/src/server/connections.rs b/olivefsd/src/server/connections.rs index 06f5b06..26a8118 100644 --- a/olivefsd/src/server/connections.rs +++ b/olivefsd/src/server/connections.rs @@ -1,26 +1,34 @@ -use crate::server::file_access::{ClientInformation, ClientSpecificState, ServerwideState}; +use crate::server::file_access::{ + ClientInformation, ClientSpecificState, FileAccess, ServerwideState, +}; use anyhow::Context; use futures_util::StreamExt; use log::{error, info}; use olivefs_common::messages::DataCommand; -use olivefs_common::networking::read_bare_message; +use olivefs_common::networking::{read_bare_message, send_bare_message}; use quinn::{NewConnection, RecvStream, SendStream}; use std::sync::Arc; pub async fn handle_command_stream( - _tx: SendStream, + mut tx: SendStream, mut rx: RecvStream, - _client_info: Arc, - _client_state: Arc, - _server_state: Arc, + client_info: Arc, + client_state: Arc, + server_state: Arc, ) -> anyhow::Result<()> { + let file_access = FileAccess { + client_info, + client_state, + server_state, + }; + while let Some(command) = read_bare_message::(&mut rx) .await .context("Whilst waiting for Data Command")? { match command { - DataCommand::GetAttr { vnode: _ } => { - info!("getattr Not Implemented"); + DataCommand::GetAttr { vnode } => { + send_bare_message(&mut tx, &file_access.getattr(vnode).await?).await?; } } } diff --git a/olivefsd/src/server/file_access.rs b/olivefsd/src/server/file_access.rs index 5e46ecc..d4859d2 100644 --- a/olivefsd/src/server/file_access.rs +++ b/olivefsd/src/server/file_access.rs @@ -1,4 +1,6 @@ +use olivefs_common::messages::{DataResponse, FileMetadata, VnodeId}; use slab::Slab; +use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::RwLock; @@ -40,3 +42,137 @@ pub struct InodeInfo { /// The real path that this inode refers to. pub real_path: PathBuf, } + +pub struct FileAccess { + pub client_info: Arc, + pub client_state: Arc, + pub server_state: Arc, +} + +#[allow(unused)] +mod error_codes { + pub const ENOENT: i32 = 2; + pub const EFAULT: i32 = 14; + pub const EDEADLK: i32 = 35; + pub const ENAMETOOLONG: i32 = 36; + pub const ENOLCK: i32 = 37; + pub const ENOSYS: i32 = 38; + pub const ENOTEMPTY: i32 = 39; + pub const ELOOP: i32 = 40; + pub const ENOMSG: i32 = 42; + pub const EIDRM: i32 = 43; + pub const ECHRNG: i32 = 44; + pub const EL2NSYNC: i32 = 45; + pub const EL3HLT: i32 = 46; + pub const EL3RST: i32 = 47; + pub const ELNRNG: i32 = 48; + pub const EUNATCH: i32 = 49; + pub const ENOCSI: i32 = 50; + pub const EL2HLT: i32 = 51; + pub const EBADE: i32 = 52; + pub const EBADR: i32 = 53; + pub const EXFULL: i32 = 54; + pub const ENOANO: i32 = 55; + pub const EBADRQC: i32 = 56; + pub const EBADSLT: i32 = 57; + pub const EMULTIHOP: i32 = 72; + pub const EOVERFLOW: i32 = 75; + pub const ENOTUNIQ: i32 = 76; + pub const EBADFD: i32 = 77; + pub const EBADMSG: i32 = 74; + pub const EREMCHG: i32 = 78; + pub const ELIBACC: i32 = 79; + pub const ELIBBAD: i32 = 80; + pub const ELIBSCN: i32 = 81; + pub const ELIBMAX: i32 = 82; + pub const ELIBEXEC: i32 = 83; + pub const EILSEQ: i32 = 84; + pub const ERESTART: i32 = 85; + pub const ESTRPIPE: i32 = 86; + pub const EUSERS: i32 = 87; + pub const ENOTSOCK: i32 = 88; + pub const EDESTADDRREQ: i32 = 89; + pub const EMSGSIZE: i32 = 90; + pub const EPROTOTYPE: i32 = 91; + pub const ENOPROTOOPT: i32 = 92; + pub const EPROTONOSUPPORT: i32 = 93; + pub const ESOCKTNOSUPPORT: i32 = 94; + pub const EOPNOTSUPP: i32 = 95; + pub const EPFNOSUPPORT: i32 = 96; + pub const EAFNOSUPPORT: i32 = 97; + pub const EADDRINUSE: i32 = 98; + pub const EADDRNOTAVAIL: i32 = 99; + pub const ENETDOWN: i32 = 100; + pub const ENETUNREACH: i32 = 101; + pub const ENETRESET: i32 = 102; + pub const ECONNABORTED: i32 = 103; + pub const ECONNRESET: i32 = 104; + pub const ENOBUFS: i32 = 105; + pub const EISCONN: i32 = 106; + pub const ENOTCONN: i32 = 107; + pub const ESHUTDOWN: i32 = 108; + pub const ETOOMANYREFS: i32 = 109; + pub const ETIMEDOUT: i32 = 110; + pub const ECONNREFUSED: i32 = 111; + pub const EHOSTDOWN: i32 = 112; + pub const EHOSTUNREACH: i32 = 113; + pub const EALREADY: i32 = 114; + pub const EINPROGRESS: i32 = 115; + pub const ESTALE: i32 = 116; + pub const EDQUOT: i32 = 122; + pub const ENOMEDIUM: i32 = 123; + pub const EMEDIUMTYPE: i32 = 124; + pub const ECANCELED: i32 = 125; + pub const ENOKEY: i32 = 126; + pub const EKEYEXPIRED: i32 = 127; + pub const EKEYREVOKED: i32 = 128; + pub const EKEYREJECTED: i32 = 129; + pub const EOWNERDEAD: i32 = 130; + pub const ENOTRECOVERABLE: i32 = 131; + pub const EHWPOISON: i32 = 133; + pub const ERFKILL: i32 = 132; +} + +impl FileAccess { + pub async fn getattr(&self, vnode: VnodeId) -> anyhow::Result> { + let inode_map = self.client_state.inode_map.read().await; + if let Some(inode_info) = inode_map.get(vnode.0 as usize) { + match tokio::fs::metadata(&inode_info.real_path).await { + Ok(metadata) => { + let file_metadata = FileMetadata { + ino: vnode, + size: metadata.size(), + blocks: metadata.blocks(), + atime: metadata.accessed().unwrap(), + mtime: metadata.modified().unwrap(), + ctime: metadata.created().unwrap(), + kind: metadata.file_type().into(), + perm: metadata.permissions().mode() as u16, + nlink: metadata.nlink() as u32, + uid: metadata.uid(), + gid: metadata.gid(), + rdev: metadata.rdev() as u32, + blksize: metadata.blksize() as u32, + }; + Ok(DataResponse::Success(file_metadata)) + } + Err(error) => { + let err_code = if let Some(err_code) = error.raw_os_error() { + err_code + } else { + error_codes::ENOENT + }; + Ok(DataResponse::Error { + code: err_code, + message: format!("{:?} ({:?})", error, inode_info.real_path), + }) + } + } + } else { + Ok(DataResponse::Error { + code: error_codes::EFAULT, + message: "No inode info".to_string(), + }) + } + } +}