mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-18 18:46:17 +00:00
7cb91a0190
Replace every bare `#[allow(dead_code)]` in ecstore with either a deletion or a per-item allow carrying a `reason`. Blanket allows at module, struct, and impl level silence the lint for future members too, so each is narrowed to the members that are actually dead. Delete the dead cluster in `config/heal.rs` (`Config`, its three methods, `RUSTFS_BITROT_CYCLE_IN_MONTHS`, `parse_bitrot_config`) rather than annotate it: it has no callers and is unreachable outside the crate, and `parse_bitrot_config` would panic on its disabled path via `Duration::from_secs_f64(-1.0)`. `DEFAULT_KVS` stays, since the config registry uses it. Correct two `reason` strings on `Checksum::new` and `PutObjReader::md5_current_hex_string`, which are methods but carried a field-only rationale. Refs backlog#1823 Co-authored-by: houseme <housemecn@gmail.com>
279 lines
8.5 KiB
Rust
279 lines
8.5 KiB
Rust
#![allow(clippy::map_entry)]
|
|
// Copyright 2024 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
#![allow(unused_imports)]
|
|
#![allow(unused_variables)]
|
|
#![allow(unused_mut)]
|
|
#![allow(unused_assignments)]
|
|
#![allow(unused_must_use)]
|
|
#![allow(clippy::all)]
|
|
|
|
use http::HeaderMap;
|
|
use s3s::dto::ETag;
|
|
use std::{collections::HashMap, io::Cursor, sync::Arc};
|
|
use tokio::io::BufReader;
|
|
|
|
use crate::error::ErrorResponse;
|
|
use crate::object_api::{GetObjectReader, ObjectInfo, ObjectOptions};
|
|
use crate::storage_api_contracts::range::HTTPRangeSpec;
|
|
use rustfs_filemeta::ObjectPartInfo;
|
|
use rustfs_rio::HashReader;
|
|
use s3s::S3ErrorCode;
|
|
|
|
//#[derive(Clone)]
|
|
pub struct PutObjReader {
|
|
pub reader: HashReader,
|
|
//pub sealMD5Fn: SealMD5CurrFn,
|
|
}
|
|
|
|
impl PutObjReader {
|
|
pub fn new(reader: HashReader) -> Self {
|
|
Self { reader }
|
|
}
|
|
|
|
#[allow(dead_code, reason = "MinIO-parity surface with no caller in this port (backlog#1823)")]
|
|
fn md5_current_hex_string(&self) -> String {
|
|
self.reader.checksum().map(|v| v.encoded).unwrap_or_default()
|
|
}
|
|
|
|
#[allow(dead_code, reason = "MinIO-parity surface with no caller in this port (backlog#1823)")]
|
|
fn with_encryption(&mut self, enc_reader: HashReader) -> Result<(), std::io::Error> {
|
|
self.reader = enc_reader;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub type ObjReaderFn<'a> = Arc<dyn Fn(BufReader<Cursor<Vec<u8>>>, HeaderMap) -> GetObjectReader + Send + Sync + 'a>;
|
|
|
|
fn part_number_to_rangespec(oi: ObjectInfo, part_number: usize) -> Option<HTTPRangeSpec> {
|
|
if oi.size == 0 || oi.parts.len() == 0 {
|
|
return None;
|
|
}
|
|
|
|
let mut start: i64 = 0;
|
|
let mut end: i64 = -1;
|
|
let mut i = 0;
|
|
while i < oi.parts.len() && i < part_number {
|
|
start = end + 1;
|
|
end = start + oi.parts[i].actual_size as i64 - 1;
|
|
i += 1;
|
|
}
|
|
|
|
Some(HTTPRangeSpec {
|
|
start,
|
|
end,
|
|
is_suffix_length: false,
|
|
})
|
|
}
|
|
|
|
pub fn new_getobjectreader<'a>(
|
|
rs: &Option<HTTPRangeSpec>,
|
|
oi: &'a ObjectInfo,
|
|
opts: &ObjectOptions,
|
|
_h: &HeaderMap,
|
|
) -> Result<(ObjReaderFn<'a>, i64, i64), ErrorResponse> {
|
|
//let (_, mut is_encrypted) = crypto.is_encrypted(oi.user_defined)?;
|
|
let mut is_encrypted = false;
|
|
let is_compressed = false; //oi.is_compressed_ok();
|
|
|
|
let rs_;
|
|
if rs.is_none()
|
|
&& let Some(part_number) = opts.part_number
|
|
&& part_number > 0
|
|
{
|
|
rs_ = part_number_to_rangespec(oi.clone(), part_number);
|
|
} else {
|
|
rs_ = rs.clone();
|
|
}
|
|
|
|
let mut get_fn: ObjReaderFn;
|
|
|
|
if let Some(rs_) = rs_ {
|
|
let (off, length) = match rs_.get_offset_length(oi.size) {
|
|
Ok(x) => x,
|
|
Err(err) => {
|
|
return Err(ErrorResponse {
|
|
code: S3ErrorCode::InvalidRange,
|
|
message: err.to_string(),
|
|
key: None,
|
|
bucket_name: None,
|
|
region: None,
|
|
request_id: None,
|
|
host_id: "".to_string(),
|
|
});
|
|
}
|
|
};
|
|
get_fn = Arc::new(move |input_reader: BufReader<Cursor<Vec<u8>>>, _: HeaderMap| {
|
|
//Box::pin({
|
|
let r = GetObjectReader {
|
|
object_info: oi.clone(),
|
|
stream: Box::new(input_reader),
|
|
buffered_body: None,
|
|
body_source: Default::default(),
|
|
};
|
|
r
|
|
//})
|
|
});
|
|
|
|
return Ok((get_fn, off as i64, length as i64));
|
|
}
|
|
if rs.is_none() && opts.part_number.is_none() && oi.size >= 0 {
|
|
get_fn = Arc::new(move |input_reader: BufReader<Cursor<Vec<u8>>>, _: HeaderMap| GetObjectReader {
|
|
object_info: oi.clone(),
|
|
stream: Box::new(input_reader),
|
|
buffered_body: None,
|
|
body_source: Default::default(),
|
|
});
|
|
|
|
return Ok((get_fn, 0, oi.size));
|
|
}
|
|
Err(ErrorResponse {
|
|
code: S3ErrorCode::InvalidRange,
|
|
message: "Invalid range".to_string(),
|
|
key: Some(oi.name.clone()),
|
|
bucket_name: Some(oi.bucket.clone()),
|
|
region: Some("".to_string()),
|
|
request_id: None,
|
|
host_id: "".to_string(),
|
|
})
|
|
}
|
|
|
|
/// Convert a raw stored ETag into the strongly-typed `s3s::dto::ETag`.
|
|
///
|
|
/// Supports already quoted (`"abc"`), weak (`W/"abc"`), or plain (`abc`) values.
|
|
pub fn to_s3s_etag(etag: &str) -> ETag {
|
|
if let Some(rest) = etag.strip_prefix("W/\"") {
|
|
if let Some(body) = rest.strip_suffix('"') {
|
|
return ETag::Weak(body.to_string());
|
|
}
|
|
return ETag::Weak(rest.to_string());
|
|
}
|
|
|
|
if let Some(body) = etag.strip_prefix('"').and_then(|rest| rest.strip_suffix('"')) {
|
|
return ETag::Strong(body.to_string());
|
|
}
|
|
|
|
ETag::Strong(etag.to_string())
|
|
}
|
|
|
|
pub fn get_raw_etag(metadata: &HashMap<String, String>) -> String {
|
|
metadata
|
|
.get("etag")
|
|
.cloned()
|
|
.or_else(|| metadata.get("md5Sum").cloned())
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::sync::Arc;
|
|
|
|
fn multipart_object_info() -> ObjectInfo {
|
|
ObjectInfo {
|
|
bucket: "bucket".to_string(),
|
|
name: "object".to_string(),
|
|
size: 6 * 1024 * 1024,
|
|
actual_size: 6 * 1024 * 1024,
|
|
parts: Arc::new(vec![
|
|
ObjectPartInfo {
|
|
number: 1,
|
|
size: 5 * 1024 * 1024,
|
|
actual_size: 5 * 1024 * 1024,
|
|
..Default::default()
|
|
},
|
|
ObjectPartInfo {
|
|
number: 2,
|
|
size: 1024 * 1024,
|
|
actual_size: 1024 * 1024,
|
|
..Default::default()
|
|
},
|
|
]),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_new_getobjectreader_uses_full_range_for_unranged_multipart_object() {
|
|
let oi = multipart_object_info();
|
|
let opts = ObjectOptions::default();
|
|
|
|
let result = new_getobjectreader(&None, &oi, &opts, &HeaderMap::new())
|
|
.expect("unranged multipart object should build a full-object reader");
|
|
|
|
assert_eq!(result.1, 0);
|
|
assert_eq!(result.2, oi.size);
|
|
}
|
|
|
|
#[test]
|
|
fn test_new_getobjectreader_keeps_part_number_range_for_multipart_object() {
|
|
let oi = multipart_object_info();
|
|
let opts = ObjectOptions {
|
|
part_number: Some(2),
|
|
..Default::default()
|
|
};
|
|
|
|
let result = new_getobjectreader(&None, &oi, &opts, &HeaderMap::new()).expect("part number should build that part range");
|
|
|
|
assert_eq!(result.1, 5 * 1024 * 1024);
|
|
assert_eq!(result.2, 1024 * 1024);
|
|
}
|
|
|
|
#[test]
|
|
fn test_to_s3s_etag() {
|
|
// Test unquoted ETag - should become strong etag
|
|
assert_eq!(
|
|
to_s3s_etag("6af8d12c0c74b78094884349f3c8a079"),
|
|
ETag::Strong("6af8d12c0c74b78094884349f3c8a079".to_string())
|
|
);
|
|
|
|
assert_eq!(
|
|
to_s3s_etag("\"6af8d12c0c74b78094884349f3c8a079\""),
|
|
ETag::Strong("6af8d12c0c74b78094884349f3c8a079".to_string())
|
|
);
|
|
|
|
assert_eq!(
|
|
to_s3s_etag("W/\"6af8d12c0c74b78094884349f3c8a079\""),
|
|
ETag::Weak("6af8d12c0c74b78094884349f3c8a079".to_string())
|
|
);
|
|
|
|
assert_eq!(to_s3s_etag(""), ETag::Strong(String::new()));
|
|
|
|
assert_eq!(to_s3s_etag("\"incomplete"), ETag::Strong("\"incomplete".to_string()));
|
|
|
|
assert_eq!(to_s3s_etag("incomplete\""), ETag::Strong("incomplete\"".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_etag() {
|
|
let mut metadata = HashMap::new();
|
|
|
|
// Test with etag field
|
|
metadata.insert("etag".to_string(), "abc123".to_string());
|
|
assert_eq!(get_raw_etag(&metadata), "abc123");
|
|
|
|
metadata.insert("etag".to_string(), "\"def456\"".to_string());
|
|
assert_eq!(get_raw_etag(&metadata), "\"def456\"");
|
|
|
|
// Test fallback to md5Sum
|
|
metadata.remove("etag");
|
|
metadata.insert("md5Sum".to_string(), "xyz789".to_string());
|
|
assert_eq!(get_raw_etag(&metadata), "xyz789");
|
|
|
|
metadata.clear();
|
|
assert_eq!(get_raw_etag(&metadata), "");
|
|
}
|
|
}
|