Fix DELETE cleanup cost with single-syscall rmdir walk, tighten FS segment validation, stream CopyObject
This commit is contained in:
@@ -2395,7 +2395,7 @@ async fn copy_object_handler(
|
|||||||
source_metadata_existing.clone()
|
source_metadata_existing.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_meta, mut reader) = match src_version_id.as_deref() {
|
let (_meta, reader) = match src_version_id.as_deref() {
|
||||||
Some(version_id) if version_id != "null" => {
|
Some(version_id) if version_id != "null" => {
|
||||||
match state
|
match state
|
||||||
.storage
|
.storage
|
||||||
@@ -2411,19 +2411,10 @@ async fn copy_object_handler(
|
|||||||
Err(e) => return storage_err_response(e),
|
Err(e) => return storage_err_response(e),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let mut data = Vec::new();
|
|
||||||
if let Err(e) = reader.read_to_end(&mut data).await {
|
|
||||||
return storage_err_response(myfsio_storage::error::StorageError::Io(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
let copy_result = state
|
let copy_result = state
|
||||||
.storage
|
.storage
|
||||||
.put_object(
|
.put_object(dst_bucket, dst_key, reader, Some(dst_metadata))
|
||||||
dst_bucket,
|
|
||||||
dst_key,
|
|
||||||
Box::pin(std::io::Cursor::new(data)),
|
|
||||||
Some(dst_metadata),
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match copy_result {
|
match copy_result {
|
||||||
|
|||||||
@@ -882,17 +882,11 @@ impl FsStorageBackend {
|
|||||||
fn cleanup_empty_parents(path: &Path, stop_at: &Path) {
|
fn cleanup_empty_parents(path: &Path, stop_at: &Path) {
|
||||||
let mut parent = path.parent();
|
let mut parent = path.parent();
|
||||||
while let Some(p) = parent {
|
while let Some(p) = parent {
|
||||||
if p == stop_at || !p.exists() {
|
if p == stop_at {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match std::fs::read_dir(p) {
|
if std::fs::remove_dir(p).is_err() {
|
||||||
Ok(mut entries) => {
|
break;
|
||||||
if entries.next().is_some() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let _ = std::fs::remove_dir(p);
|
|
||||||
}
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
}
|
||||||
parent = p.parent();
|
parent = p.parent();
|
||||||
}
|
}
|
||||||
@@ -1937,29 +1931,6 @@ impl FsStorageBackend {
|
|||||||
obj.version_id = new_version_id;
|
obj.version_id = new_version_id;
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn put_object_sync(
|
|
||||||
&self,
|
|
||||||
bucket_name: &str,
|
|
||||||
key: &str,
|
|
||||||
data: &[u8],
|
|
||||||
metadata: Option<HashMap<String, String>>,
|
|
||||||
) -> StorageResult<ObjectMeta> {
|
|
||||||
self.validate_key(key)?;
|
|
||||||
|
|
||||||
let tmp_dir = self.tmp_dir();
|
|
||||||
std::fs::create_dir_all(&tmp_dir).map_err(StorageError::Io)?;
|
|
||||||
let tmp_path = tmp_dir.join(format!("{}.tmp", Uuid::new_v4()));
|
|
||||||
|
|
||||||
let mut hasher = Md5::new();
|
|
||||||
hasher.update(data);
|
|
||||||
let etag = format!("{:x}", hasher.finalize());
|
|
||||||
|
|
||||||
std::fs::write(&tmp_path, data).map_err(StorageError::Io)?;
|
|
||||||
let new_size = data.len() as u64;
|
|
||||||
|
|
||||||
self.finalize_put_sync(bucket_name, key, &tmp_path, etag, new_size, metadata)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::traits::StorageEngine for FsStorageBackend {
|
impl crate::traits::StorageEngine for FsStorageBackend {
|
||||||
@@ -2453,9 +2424,54 @@ impl crate::traits::StorageEngine for FsStorageBackend {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = std::fs::read(&src_path).map_err(StorageError::Io)?;
|
self.validate_key(dst_key)?;
|
||||||
|
let tmp_dir = self.tmp_dir();
|
||||||
|
std::fs::create_dir_all(&tmp_dir).map_err(StorageError::Io)?;
|
||||||
|
let tmp_path = tmp_dir.join(format!("{}.tmp", Uuid::new_v4()));
|
||||||
|
|
||||||
|
let (etag, new_size) = {
|
||||||
|
use std::io::{BufReader, BufWriter, Read, Write};
|
||||||
|
let src_file = std::fs::File::open(&src_path).map_err(|e| {
|
||||||
|
let _ = std::fs::remove_file(&tmp_path);
|
||||||
|
StorageError::Io(e)
|
||||||
|
})?;
|
||||||
|
let mut reader = BufReader::with_capacity(256 * 1024, src_file);
|
||||||
|
let tmp_file = std::fs::File::create(&tmp_path).map_err(StorageError::Io)?;
|
||||||
|
let mut writer = BufWriter::with_capacity(256 * 1024, tmp_file);
|
||||||
|
let mut hasher = Md5::new();
|
||||||
|
let mut buf = [0u8; 256 * 1024];
|
||||||
|
let mut total: u64 = 0;
|
||||||
|
loop {
|
||||||
|
let n = reader.read(&mut buf).map_err(|e| {
|
||||||
|
let _ = std::fs::remove_file(&tmp_path);
|
||||||
|
StorageError::Io(e)
|
||||||
|
})?;
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hasher.update(&buf[..n]);
|
||||||
|
writer.write_all(&buf[..n]).map_err(|e| {
|
||||||
|
let _ = std::fs::remove_file(&tmp_path);
|
||||||
|
StorageError::Io(e)
|
||||||
|
})?;
|
||||||
|
total += n as u64;
|
||||||
|
}
|
||||||
|
writer.flush().map_err(|e| {
|
||||||
|
let _ = std::fs::remove_file(&tmp_path);
|
||||||
|
StorageError::Io(e)
|
||||||
|
})?;
|
||||||
|
(format!("{:x}", hasher.finalize()), total)
|
||||||
|
};
|
||||||
|
|
||||||
let src_metadata = self.read_metadata_sync(src_bucket, src_key);
|
let src_metadata = self.read_metadata_sync(src_bucket, src_key);
|
||||||
self.put_object_sync(dst_bucket, dst_key, &data, Some(src_metadata))
|
self.finalize_put_sync(
|
||||||
|
dst_bucket,
|
||||||
|
dst_key,
|
||||||
|
&tmp_path,
|
||||||
|
etag,
|
||||||
|
new_size,
|
||||||
|
Some(src_metadata),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_object_metadata(
|
async fn get_object_metadata(
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ pub fn validate_object_key(
|
|||||||
|
|
||||||
if part.len() > 255 {
|
if part.len() > 255 {
|
||||||
return Some(
|
return Some(
|
||||||
"Object key contains a path segment that exceeds 255 bytes".to_string(),
|
"Object key contains a path segment longer than 255 bytes (filesystem backend limit)"
|
||||||
|
.to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,10 +198,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_object_key_max_length() {
|
fn test_object_key_max_length() {
|
||||||
let long_key = "a".repeat(1025);
|
let too_long_total = "a/".repeat(513) + "a";
|
||||||
assert!(validate_object_key(&long_key, 1024, false, None).is_some());
|
assert!(validate_object_key(&too_long_total, 1024, false, None).is_some());
|
||||||
let ok_key = "a".repeat(1024);
|
|
||||||
|
let too_long_segment = "a".repeat(256);
|
||||||
|
assert!(validate_object_key(&too_long_segment, 1024, false, None).is_some());
|
||||||
|
|
||||||
|
let ok_key = vec!["a".repeat(255); 4].join("/");
|
||||||
|
assert_eq!(ok_key.len(), 255 * 4 + 3);
|
||||||
assert!(validate_object_key(&ok_key, 1024, false, None).is_none());
|
assert!(validate_object_key(&ok_key, 1024, false, None).is_none());
|
||||||
|
|
||||||
|
let ok_max_segment = "a".repeat(255);
|
||||||
|
assert!(validate_object_key(&ok_max_segment, 1024, false, None).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user