Support getattr in reality

This commit is contained in:
Olivier 'reivilibre' 2022-01-20 20:41:50 +00:00
parent 5a08dc1c88
commit 3cf535621d
11 changed files with 246 additions and 14 deletions

View File

@ -8,3 +8,6 @@ client_key = "ca/bread.key"
client_certificate = "ca/bread.crt"
ca_certificate = "ca/ca.crt"
timeout = 60
keep_alive = 30

View File

@ -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,
}

View File

@ -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?;

View File

@ -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<RequesterInternal> {
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();

View File

@ -73,6 +73,8 @@ pub enum FileKind {
Socket,
}
mod messages_std;
#[cfg(feature = "fuser")]
mod messages_fuser;

View File

@ -0,0 +1,25 @@
use crate::messages::FileKind;
use std::fs::FileType;
use std::os::unix::fs::FileTypeExt;
impl From<std::fs::FileType> 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");
}
}
}

View File

@ -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]

View File

@ -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)]

View File

@ -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<OlivefsServerConfiguration>) -> 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

View File

@ -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<ClientInformation>,
_client_state: Arc<ClientSpecificState>,
_server_state: Arc<ServerwideState>,
client_info: Arc<ClientInformation>,
client_state: Arc<ClientSpecificState>,
server_state: Arc<ServerwideState>,
) -> anyhow::Result<()> {
let file_access = FileAccess {
client_info,
client_state,
server_state,
};
while let Some(command) = read_bare_message::<DataCommand>(&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?;
}
}
}

View File

@ -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<ClientInformation>,
pub client_state: Arc<ClientSpecificState>,
pub server_state: Arc<ServerwideState>,
}
#[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<DataResponse<FileMetadata>> {
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(),
})
}
}
}