Support setattr (partially; only for truncation)
Some checks are pending
continuous-integration/drone the build is running
Some checks are pending
continuous-integration/drone the build is running
This commit is contained in:
parent
0c893c02d4
commit
39df58851d
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -889,6 +889,7 @@ dependencies = [
|
||||
"dashmap 5.0.0",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"libc",
|
||||
"log",
|
||||
"olivefs_common",
|
||||
"path-absolutize",
|
||||
|
@ -47,6 +47,13 @@ fn test_bits(haystack: i32, needle: i32) -> bool {
|
||||
(haystack & needle) == needle
|
||||
}
|
||||
|
||||
fn absolutise_time_or_now(input: TimeOrNow) -> SystemTime {
|
||||
match input {
|
||||
TimeOrNow::SpecificTime(time) => time,
|
||||
TimeOrNow::Now => SystemTime::now(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Filesystem for OliveFilesystem {
|
||||
/// Initialize filesystem.
|
||||
/// Called before any other filesystem method.
|
||||
@ -164,8 +171,8 @@ impl Filesystem for OliveFilesystem {
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
size: Option<u64>,
|
||||
_atime: Option<TimeOrNow>,
|
||||
_mtime: Option<TimeOrNow>,
|
||||
atime: Option<TimeOrNow>,
|
||||
mtime: Option<TimeOrNow>,
|
||||
_ctime: Option<SystemTime>,
|
||||
fh: Option<u64>,
|
||||
_crtime: Option<SystemTime>,
|
||||
@ -174,12 +181,44 @@ impl Filesystem for OliveFilesystem {
|
||||
flags: Option<u32>,
|
||||
reply: ReplyAttr,
|
||||
) {
|
||||
debug!(
|
||||
"[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \
|
||||
gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})",
|
||||
ino, mode, uid, gid, size, fh, flags
|
||||
let requester = self.requester.clone();
|
||||
|
||||
self.spawn_with_error_handler(
|
||||
async move {
|
||||
let vnode = VnodeId(
|
||||
ino.try_into()
|
||||
.context("Converting u64 inode to u32 VnodeId.")?,
|
||||
);
|
||||
|
||||
match requester
|
||||
.setattr(
|
||||
vnode,
|
||||
mode,
|
||||
uid,
|
||||
gid,
|
||||
size,
|
||||
atime.map(absolutise_time_or_now),
|
||||
mtime.map(absolutise_time_or_now),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
DataResponse::Success(file_metadata) => {
|
||||
reply.attr(&Duration::from_secs(5), &file_metadata.into());
|
||||
}
|
||||
DataResponse::Error { code, message } => {
|
||||
warn!(
|
||||
"setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \
|
||||
gid: {:?}, size: {:?}, fh: {:?}, flags: {:?}): {:?}",
|
||||
ino, mode, uid, gid, size, fh, flags, message
|
||||
);
|
||||
reply.error(code as c_int);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
"setattr",
|
||||
);
|
||||
reply.error(ENOSYS);
|
||||
}
|
||||
|
||||
/// Read symbolic link.
|
||||
|
@ -12,7 +12,7 @@ use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use crate::configuration::StreamingReaderConfig;
|
||||
use crate::requester::streaming_reader::StreamingReader;
|
||||
@ -269,6 +269,29 @@ impl Requester {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn setattr(
|
||||
&self,
|
||||
vnode: VnodeId,
|
||||
mode: Option<u32>,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
size: Option<u64>,
|
||||
atime: Option<SystemTime>,
|
||||
mtime: Option<SystemTime>,
|
||||
) -> anyhow::Result<DataResponse<FileMetadata>> {
|
||||
self.internal
|
||||
.command(&DataCommand::SetAttr {
|
||||
vnode,
|
||||
mode,
|
||||
uid,
|
||||
gid,
|
||||
size,
|
||||
atime,
|
||||
mtime,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn start_streaming_reader(
|
||||
&self,
|
||||
file_handle: u32,
|
||||
|
@ -79,6 +79,15 @@ pub enum DataCommand {
|
||||
FlushFile {
|
||||
file_handle: u32,
|
||||
},
|
||||
SetAttr {
|
||||
vnode: VnodeId,
|
||||
mode: Option<u32>,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
size: Option<u64>,
|
||||
atime: Option<SystemTime>,
|
||||
mtime: Option<SystemTime>,
|
||||
},
|
||||
}
|
||||
|
||||
pub trait DataResponseBase: Serialize + DeserializeOwned + Debug + Clone + 'static {}
|
||||
|
@ -39,5 +39,8 @@ x509-parser = "0.12.0"
|
||||
# consider also: sharded-slab (concurrent access), thunderdome (generational indices)
|
||||
slab = "0.4.5"
|
||||
|
||||
## System
|
||||
libc = "0.2.112"
|
||||
|
||||
## Common
|
||||
olivefs_common = { path = "../olivefs_common" }
|
||||
|
@ -94,6 +94,24 @@ pub async fn handle_command_stream(
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
DataCommand::SetAttr {
|
||||
vnode,
|
||||
mode,
|
||||
uid,
|
||||
gid,
|
||||
size,
|
||||
atime,
|
||||
mtime,
|
||||
} => {
|
||||
send_bare_message(
|
||||
&mut tx,
|
||||
&file_access
|
||||
.setattr(vnode, mode, uid, gid, size, atime, mtime)
|
||||
.await
|
||||
.unwrap_or_else(error_to_response),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use olivefs_common::error_codes;
|
||||
use olivefs_common::error_codes::{EBADFD, EFAULT, ENOENT};
|
||||
use olivefs_common::error_codes::{EBADFD, EFAULT, ENOENT, ENOSYS};
|
||||
use olivefs_common::messages::{
|
||||
DataResponse, DirectoryEntry, FileKind, FileMetadata, OpenMode, VnodeId,
|
||||
};
|
||||
@ -7,18 +7,23 @@ use path_absolutize::Absolutize;
|
||||
use slab::Slab;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::io::SeekFrom;
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::{anyhow, bail};
|
||||
use libc::{ELOOP, O_NOFOLLOW};
|
||||
use log::{error, warn};
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use log::{error, warn};
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
use tokio::fs::{OpenOptions, ReadDir};
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
// 4 symlinks ought to be enough for anybody!
|
||||
pub const MAX_SYMLINK_RESOLVE_TRIES: u32 = 4;
|
||||
|
||||
/// Server-wide state that all clients might need to mess with
|
||||
#[derive(Default)]
|
||||
pub struct ServerwideState {
|
||||
@ -109,6 +114,7 @@ impl FileAccess {
|
||||
if let Some(inode_info) = inode_map.get(vnode.0 as usize) {
|
||||
Ok(inode_info.real_path.clone())
|
||||
} else {
|
||||
// TODO just make this an io::Error.
|
||||
Err(DataResponse::Error {
|
||||
code: error_codes::EFAULT,
|
||||
message: "No inode info".to_string(),
|
||||
@ -116,6 +122,45 @@ impl FileAccess {
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve_vnode_including_follow_symlinks_safely_best_effort(
|
||||
&self,
|
||||
vnode: VnodeId,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
let real_path = self
|
||||
.resolve_vnode::<()>(vnode)
|
||||
.await
|
||||
.map_err(|_| anyhow!("No inode info"))?;
|
||||
Ok(self.safely_resolve_symlinks_best_effort(real_path).await?)
|
||||
}
|
||||
|
||||
/// Guarantees that the end file was not a symlink at the time of checking.
|
||||
/// The caller must still prevent themselves from opening symlinks (but they may safely fail
|
||||
/// on opening a symlink if it gets changed under their feet).
|
||||
/// The symlinks will stop resolving if they ever point outside of the root directory, even
|
||||
/// if they point to a symlink that points back into the restricted tree.
|
||||
async fn safely_resolve_symlinks_best_effort(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
) -> Result<PathBuf, io::Error> {
|
||||
let mut path = path;
|
||||
let root = self.client_info.root.clone();
|
||||
tokio::task::spawn_blocking(move || -> Result<PathBuf, io::Error> {
|
||||
for _ in 0..MAX_SYMLINK_RESOLVE_TRIES {
|
||||
let abs_path = &path.absolutize_virtually(&root)?;
|
||||
let metadata = abs_path.symlink_metadata()?;
|
||||
|
||||
if !metadata.file_type().is_symlink() {
|
||||
return Ok(abs_path.to_path_buf());
|
||||
}
|
||||
|
||||
path = abs_path.read_link()?;
|
||||
}
|
||||
Err(io::Error::from_raw_os_error(ELOOP)).into()
|
||||
})
|
||||
.await
|
||||
.map_err(|_| io::Error::from_raw_os_error(EFAULT))?
|
||||
}
|
||||
|
||||
async fn allocate_vnode(&self, path: PathBuf) -> anyhow::Result<VnodeId> {
|
||||
let paths_to_vnodes = self.client_state.existing_paths_to_inodes.read().await;
|
||||
if let Some(vi) = paths_to_vnodes.get(&path) {
|
||||
@ -314,6 +359,9 @@ impl FileAccess {
|
||||
// We'll allocate the Vnode after we know the target doesn't already exist. Fill in 0 for now.
|
||||
let mut open_options = OpenOptions::new();
|
||||
|
||||
// IMPORTANT: Don't follow symlinks
|
||||
open_options.custom_flags(O_NOFOLLOW);
|
||||
|
||||
// Only create new files
|
||||
open_options.create_new(true);
|
||||
|
||||
@ -580,4 +628,37 @@ impl FileAccess {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn setattr(
|
||||
&self,
|
||||
vnode: VnodeId,
|
||||
mode: Option<u32>,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
size: Option<u64>,
|
||||
atime: Option<SystemTime>,
|
||||
mtime: Option<SystemTime>,
|
||||
) -> anyhow::Result<DataResponse<FileMetadata>> {
|
||||
if mode.is_some() || uid.is_some() || gid.is_some() || atime.is_some() || mtime.is_some() {
|
||||
return Err(io::Error::from_raw_os_error(ENOSYS).into());
|
||||
}
|
||||
|
||||
let path = self
|
||||
.resolve_vnode_including_follow_symlinks_safely_best_effort(vnode)
|
||||
.await?;
|
||||
|
||||
if let Some(truncate_to) = size {
|
||||
let mut open_options = OpenOptions::new();
|
||||
|
||||
// IMPORTANT: Don't follow symlinks
|
||||
open_options.custom_flags(O_NOFOLLOW);
|
||||
open_options.write(true);
|
||||
let file = open_options.open(&path).await?;
|
||||
file.set_len(truncate_to).await?;
|
||||
}
|
||||
|
||||
let metadata = self.read_metadata(&path, vnode).await?;
|
||||
|
||||
Ok(DataResponse::Success(metadata))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user