mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
fix: unify path handling to use S3-standard forward slashes on all platforms (#1555)
Signed-off-by: houseme <housemecn@gmail.com> Signed-off-by: 安正超 <anzhengchao@gmail.com> Co-authored-by: heihutu <30542132+heihutu@users.noreply.github.com> Co-authored-by: LeonWang0735 <wlywly0735@126.com> Co-authored-by: loverustfs <hello@rustfs.com> Co-authored-by: 安正超 <anzhengchao@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Generated
+56
-63
@@ -224,11 +224,11 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "ar_archive_writer"
|
||||
version = "0.2.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a"
|
||||
checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
|
||||
dependencies = [
|
||||
"object 0.32.2",
|
||||
"object",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -687,9 +687,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.15.3"
|
||||
version = "1.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86"
|
||||
checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"untrusted 0.7.1",
|
||||
@@ -698,9 +698,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.36.0"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8"
|
||||
checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
@@ -1156,7 +1156,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object 0.37.3",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-link",
|
||||
]
|
||||
@@ -1453,9 +1453,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.53"
|
||||
version = "1.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -3169,7 +3169,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3481,7 +3481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4466,9 +4466,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0"
|
||||
checksum = "b41fb3dc24fe72c2e3a4685eed55917c2fb228851257f4a8f2d985da9443c3e5"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"zeroize",
|
||||
@@ -4859,7 +4859,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4927,7 +4927,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5201,9 +5201,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "liblzma-sys"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736"
|
||||
checksum = "9f2db66f3268487b5033077f266da6777d057949b8f93c8ad82e441df25e6186"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -5212,9 +5212,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
@@ -5557,9 +5557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
version = "0.12.12"
|
||||
version = "0.12.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a"
|
||||
checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"crossbeam-channel",
|
||||
@@ -5707,9 +5707,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "notify-types"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
|
||||
checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
@@ -5726,7 +5729,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5903,15 +5906,6 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
@@ -5980,9 +5974,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry"
|
||||
@@ -6536,9 +6530,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.11.0-rc.8"
|
||||
version = "0.11.0-rc.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77089aec8290d0b7bb01b671b091095cf1937670725af4fd73d47249f03b12c0"
|
||||
checksum = "b226d2cc389763951db8869584fd800cbbe2962bf454e2edeb5172b31ee99774"
|
||||
dependencies = [
|
||||
"der 0.8.0-rc.10",
|
||||
"spki 0.8.0-rc.4",
|
||||
@@ -6661,9 +6655,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppmd-rust"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4"
|
||||
checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
|
||||
|
||||
[[package]]
|
||||
name = "pprof"
|
||||
@@ -6766,9 +6760,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.105"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -6926,9 +6920,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.28"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01"
|
||||
checksum = "1fa96cb91275ed31d6da3e983447320c4eb219ac180fa1679a0889ff32861e2d"
|
||||
dependencies = [
|
||||
"ar_archive_writer",
|
||||
"cc",
|
||||
@@ -7045,14 +7039,14 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.43"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -7452,7 +7446,7 @@ dependencies = [
|
||||
"crypto-primes",
|
||||
"digest 0.11.0-rc.5",
|
||||
"pkcs1",
|
||||
"pkcs8 0.11.0-rc.8",
|
||||
"pkcs8 0.11.0-rc.10",
|
||||
"rand_core 0.10.0-rc-3",
|
||||
"sha2 0.11.0-rc.3",
|
||||
"signature 3.0.0-rc.6",
|
||||
@@ -7888,7 +7882,6 @@ dependencies = [
|
||||
"bytesize",
|
||||
"chrono",
|
||||
"criterion",
|
||||
"dunce",
|
||||
"enumset",
|
||||
"faster-hex",
|
||||
"flatbuffers",
|
||||
@@ -8447,7 +8440,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8709,9 +8702,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.8.0-rc.11"
|
||||
version = "0.8.0-rc.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2568531a8ace88b848310caa98fb2115b151ef924d54aa523e659c21b9d32d71"
|
||||
checksum = "7a2400ed44a13193820aa528a19f376c3843141a8ce96ff34b11104cc79763f2"
|
||||
dependencies = [
|
||||
"base16ct 1.0.0",
|
||||
"hybrid-array",
|
||||
@@ -9333,7 +9326,7 @@ dependencies = [
|
||||
"ed25519-dalek 3.0.0-pre.4",
|
||||
"rand_core 0.10.0-rc-3",
|
||||
"rsa",
|
||||
"sec1 0.8.0-rc.11",
|
||||
"sec1 0.8.0-rc.13",
|
||||
"sha2 0.11.0-rc.3",
|
||||
"signature 3.0.0-rc.6",
|
||||
"ssh-cipher 0.3.0-rc.5",
|
||||
@@ -9679,7 +9672,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix 1.1.3",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10300,9 +10293,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "tz-rs"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14eff19b8dc1ace5bf7e4d920b2628ae3837f422ff42210cb1567cbf68b5accf"
|
||||
checksum = "8b2df019c305771deac375d9657fc75fa394bde69102a870c923e779c3746642"
|
||||
|
||||
[[package]]
|
||||
name = "tzdb"
|
||||
@@ -10681,7 +10674,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11103,18 +11096,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.33"
|
||||
version = "0.8.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
|
||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.33"
|
||||
version = "0.8.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
|
||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11231,9 +11224,9 @@ checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.15"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2"
|
||||
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
|
||||
+1
-2
@@ -187,7 +187,6 @@ criterion = { version = "0.8", features = ["html_reports"] }
|
||||
crossbeam-queue = "0.3.12"
|
||||
datafusion = "52.1.0"
|
||||
derive_builder = "0.20.2"
|
||||
dunce = "1.0.5"
|
||||
enumset = "1.1.10"
|
||||
faster-hex = "0.10.0"
|
||||
flate2 = "1.1.8"
|
||||
@@ -209,7 +208,7 @@ matchit = "0.9.1"
|
||||
md-5 = "0.11.0-rc.3"
|
||||
md5 = "0.8.0"
|
||||
mime_guess = "2.0.5"
|
||||
moka = { version = "0.12.12", features = ["future"] }
|
||||
moka = { version = "0.12.13", features = ["future"] }
|
||||
netif = "0.1.6"
|
||||
nix = { version = "0.31.1", features = ["fs"] }
|
||||
nu-ansi-term = "0.50.3"
|
||||
|
||||
@@ -48,7 +48,6 @@ async-trait.workspace = true
|
||||
bytes.workspace = true
|
||||
byteorder = { workspace = true }
|
||||
chrono.workspace = true
|
||||
dunce.workspace = true
|
||||
glob = { workspace = true }
|
||||
thiserror.workspace = true
|
||||
flatbuffers.workspace = true
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use crate::disk::RUSTFS_META_BUCKET;
|
||||
use crate::error::{Error, Result, StorageError};
|
||||
use regex::Regex;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR_STR;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR;
|
||||
use s3s::xml;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -193,7 +193,7 @@ pub fn is_valid_object_name(object: &str) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
if object.ends_with(SLASH_SEPARATOR_STR) {
|
||||
if object.ends_with(SLASH_SEPARATOR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ pub fn check_object_name_for_length_and_slash(bucket: &str, object: &str) -> Res
|
||||
return Err(StorageError::ObjectNameTooLong(bucket.to_owned(), object.to_owned()));
|
||||
}
|
||||
|
||||
if object.starts_with(SLASH_SEPARATOR_STR) {
|
||||
if object.starts_with(SLASH_SEPARATOR) {
|
||||
return Err(StorageError::ObjectNamePrefixAsSlash(bucket.to_owned(), object.to_owned()));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::error::{Error, Result};
|
||||
use crate::store_api::{ObjectInfo, ObjectOptions, PutObjReader, StorageAPI};
|
||||
use http::HeaderMap;
|
||||
use rustfs_config::DEFAULT_DELIMITER;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR_STR;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::sync::LazyLock;
|
||||
@@ -29,7 +29,7 @@ const CONFIG_FILE: &str = "config.json";
|
||||
|
||||
pub const STORAGE_CLASS_SUB_SYS: &str = "storage_class";
|
||||
|
||||
static CONFIG_BUCKET: LazyLock<String> = LazyLock::new(|| format!("{RUSTFS_META_BUCKET}{SLASH_SEPARATOR_STR}{CONFIG_PREFIX}"));
|
||||
static CONFIG_BUCKET: LazyLock<String> = LazyLock::new(|| format!("{RUSTFS_META_BUCKET}{SLASH_SEPARATOR}{CONFIG_PREFIX}"));
|
||||
|
||||
static SUB_SYSTEMS_DYNAMIC: LazyLock<HashSet<String>> = LazyLock::new(|| {
|
||||
let mut h = HashSet::new();
|
||||
@@ -129,7 +129,7 @@ async fn new_and_save_server_config<S: StorageAPI>(api: Arc<S>) -> Result<Config
|
||||
}
|
||||
|
||||
fn get_config_file() -> String {
|
||||
format!("{CONFIG_PREFIX}{SLASH_SEPARATOR_STR}{CONFIG_FILE}")
|
||||
format!("{CONFIG_PREFIX}{SLASH_SEPARATOR}{CONFIG_FILE}")
|
||||
}
|
||||
|
||||
/// Handle the situation where the configuration file does not exist, create and save a new configuration
|
||||
|
||||
@@ -26,7 +26,7 @@ pub use local_snapshot::{
|
||||
use rustfs_common::data_usage::{
|
||||
BucketTargetUsageInfo, BucketUsageInfo, DataUsageCache, DataUsageEntry, DataUsageInfo, DiskUsageStatus, SizeSummary,
|
||||
};
|
||||
use rustfs_utils::path::SLASH_SEPARATOR_STR;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR;
|
||||
use std::{
|
||||
collections::{HashMap, hash_map::Entry},
|
||||
sync::{Arc, OnceLock},
|
||||
@@ -37,7 +37,7 @@ use tokio::sync::RwLock;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
// Data usage storage constants
|
||||
pub const DATA_USAGE_ROOT: &str = SLASH_SEPARATOR_STR;
|
||||
pub const DATA_USAGE_ROOT: &str = SLASH_SEPARATOR;
|
||||
const DATA_USAGE_OBJ_NAME: &str = ".usage.json";
|
||||
const DATA_USAGE_BLOOM_NAME: &str = ".bloomcycle.bin";
|
||||
pub const DATA_USAGE_CACHE_NAME: &str = ".usage-cache.bin";
|
||||
@@ -61,17 +61,17 @@ fn cache_updating() -> &'static CacheUpdating {
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref DATA_USAGE_BUCKET: String = format!("{}{}{}",
|
||||
crate::disk::RUSTFS_META_BUCKET,
|
||||
SLASH_SEPARATOR_STR,
|
||||
SLASH_SEPARATOR,
|
||||
crate::disk::BUCKET_META_PREFIX
|
||||
);
|
||||
pub static ref DATA_USAGE_OBJ_NAME_PATH: String = format!("{}{}{}",
|
||||
crate::disk::BUCKET_META_PREFIX,
|
||||
SLASH_SEPARATOR_STR,
|
||||
SLASH_SEPARATOR,
|
||||
DATA_USAGE_OBJ_NAME
|
||||
);
|
||||
pub static ref DATA_USAGE_BLOOM_NAME_PATH: String = format!("{}{}{}",
|
||||
crate::disk::BUCKET_META_PREFIX,
|
||||
SLASH_SEPARATOR_STR,
|
||||
SLASH_SEPARATOR,
|
||||
DATA_USAGE_BLOOM_NAME
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ use rustfs_filemeta::{
|
||||
use rustfs_utils::HashAlgorithm;
|
||||
use rustfs_utils::os::get_info;
|
||||
use rustfs_utils::path::{
|
||||
GLOBAL_DIR_SUFFIX, GLOBAL_DIR_SUFFIX_WITH_SLASH, SLASH_SEPARATOR_STR, clean, decode_dir_object, encode_dir_object,
|
||||
has_suffix, path_join, path_join_buf,
|
||||
GLOBAL_DIR_SUFFIX, GLOBAL_DIR_SUFFIX_WITH_SLASH, SLASH_SEPARATOR, clean, decode_dir_object, encode_dir_object, has_suffix,
|
||||
path_join, path_join_buf,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
@@ -122,7 +122,7 @@ impl LocalDisk {
|
||||
debug!("Creating local disk");
|
||||
// Use optimized path resolution instead of absolutize() for better performance
|
||||
// Use dunce::canonicalize instead of std::fs::canonicalize to avoid UNC paths on Windows
|
||||
let root = match dunce::canonicalize(ep.get_file_path()) {
|
||||
let root = match rustfs_utils::canonicalize(ep.get_file_path()) {
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::NotFound {
|
||||
@@ -269,8 +269,22 @@ impl LocalDisk {
|
||||
}
|
||||
|
||||
async fn cleanup_deleted_objects(root: PathBuf) -> Result<()> {
|
||||
let trash = path_join(&[root, RUSTFS_META_TMP_DELETED_BUCKET.into()]);
|
||||
let mut entries = fs::read_dir(&trash).await?;
|
||||
#[cfg(windows)]
|
||||
let trash_path = RUSTFS_META_TMP_DELETED_BUCKET.replace('/', "\\");
|
||||
#[cfg(not(windows))]
|
||||
let trash_path = RUSTFS_META_TMP_DELETED_BUCKET.to_string();
|
||||
|
||||
let trash = root.join(trash_path);
|
||||
let mut entries = match fs::read_dir(&trash).await {
|
||||
Ok(entries) => entries,
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::NotFound {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(entry) = entries.next_entry().await? {
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
if name.is_empty() || name == "." || name == ".." {
|
||||
@@ -279,7 +293,7 @@ impl LocalDisk {
|
||||
|
||||
let file_type = entry.file_type().await?;
|
||||
|
||||
let path = path_join(&[trash.clone(), name.into()]);
|
||||
let path = trash.join(name);
|
||||
|
||||
if file_type.is_dir() {
|
||||
if let Err(e) = tokio::fs::remove_dir_all(path).await
|
||||
@@ -362,7 +376,14 @@ impl LocalDisk {
|
||||
let abs_path = if path_ref.is_absolute() {
|
||||
path_ref.to_path_buf()
|
||||
} else {
|
||||
self.root.join(path_ref)
|
||||
#[cfg(windows)]
|
||||
{
|
||||
self.root.join(path_str.replace('/', "\\"))
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
self.root.join(path_ref)
|
||||
}
|
||||
};
|
||||
|
||||
// Normalize path components to avoid filesystem calls
|
||||
@@ -396,14 +417,22 @@ impl LocalDisk {
|
||||
path_join_buf(&[bucket, key])
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let path = self.root.join(cache_key.replace('/', "\\"));
|
||||
#[cfg(not(windows))]
|
||||
let path = self.root.join(cache_key);
|
||||
|
||||
self.check_valid_path(&path)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
// Get the absolute path of a bucket
|
||||
pub fn get_bucket_path(&self, bucket: &str) -> Result<PathBuf> {
|
||||
#[cfg(windows)]
|
||||
let bucket_path = self.root.join(bucket.replace('/', "\\"));
|
||||
#[cfg(not(windows))]
|
||||
let bucket_path = self.root.join(bucket);
|
||||
|
||||
self.check_valid_path(&bucket_path)?;
|
||||
Ok(bucket_path)
|
||||
}
|
||||
@@ -440,7 +469,11 @@ impl LocalDisk {
|
||||
if !cache_misses.is_empty() {
|
||||
let mut new_entries = Vec::new();
|
||||
for (i, _bucket, _key, cache_key) in cache_misses {
|
||||
#[cfg(windows)]
|
||||
let path = self.root.join(cache_key.replace('/', "\\"));
|
||||
#[cfg(not(windows))]
|
||||
let path = self.root.join(&cache_key);
|
||||
|
||||
results.push((i, path.clone()));
|
||||
new_entries.push((cache_key, path));
|
||||
}
|
||||
@@ -555,7 +588,7 @@ impl LocalDisk {
|
||||
.err()
|
||||
};
|
||||
|
||||
if immediate_purge || delete_path.to_string_lossy().ends_with(SLASH_SEPARATOR_STR) {
|
||||
if immediate_purge || delete_path.to_string_lossy().ends_with(SLASH_SEPARATOR) {
|
||||
let trash_path2 = self.get_object_path(RUSTFS_META_TMP_DELETED_BUCKET, Uuid::new_v4().to_string().as_str())?;
|
||||
let _ = rename_all(
|
||||
encode_dir_object(delete_path.to_string_lossy().as_ref()),
|
||||
@@ -1022,16 +1055,15 @@ impl LocalDisk {
|
||||
continue;
|
||||
}
|
||||
|
||||
if entry.ends_with(SLASH_SEPARATOR_STR) {
|
||||
if entry.ends_with(SLASH_SEPARATOR) {
|
||||
if entry.ends_with(GLOBAL_DIR_SUFFIX_WITH_SLASH) {
|
||||
let entry =
|
||||
format!("{}{}", entry.as_str().trim_end_matches(GLOBAL_DIR_SUFFIX_WITH_SLASH), SLASH_SEPARATOR_STR);
|
||||
let entry = format!("{}{}", entry.as_str().trim_end_matches(GLOBAL_DIR_SUFFIX_WITH_SLASH), SLASH_SEPARATOR);
|
||||
dir_objes.insert(entry.clone());
|
||||
*item = entry;
|
||||
continue;
|
||||
}
|
||||
|
||||
*item = entry.trim_end_matches(SLASH_SEPARATOR_STR).to_owned();
|
||||
*item = entry.trim_end_matches(SLASH_SEPARATOR).to_owned();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1043,7 +1075,7 @@ impl LocalDisk {
|
||||
.await?;
|
||||
|
||||
let entry = entry.strip_suffix(STORAGE_FORMAT_FILE).unwrap_or_default().to_owned();
|
||||
let name = entry.trim_end_matches(SLASH_SEPARATOR_STR);
|
||||
let name = entry.trim_end_matches(SLASH_SEPARATOR);
|
||||
let name = decode_dir_object(format!("{}/{}", ¤t, &name).as_str());
|
||||
|
||||
// if opts.limit > 0
|
||||
@@ -1126,7 +1158,7 @@ impl LocalDisk {
|
||||
Ok(res) => {
|
||||
if is_dir_obj {
|
||||
meta.name = meta.name.trim_end_matches(GLOBAL_DIR_SUFFIX_WITH_SLASH).to_owned();
|
||||
meta.name.push_str(SLASH_SEPARATOR_STR);
|
||||
meta.name.push_str(SLASH_SEPARATOR);
|
||||
}
|
||||
|
||||
meta.metadata = res.to_vec();
|
||||
@@ -1144,7 +1176,7 @@ impl LocalDisk {
|
||||
// NOT an object, append to stack (with slash)
|
||||
// If dirObject, but no metadata (which is unexpected) we skip it.
|
||||
if !is_dir_obj && !is_empty_dir(self.get_object_path(&opts.bucket, &meta.name)?).await {
|
||||
meta.name.push_str(SLASH_SEPARATOR_STR);
|
||||
meta.name.push_str(SLASH_SEPARATOR);
|
||||
dir_stack.push(meta.name);
|
||||
}
|
||||
}
|
||||
@@ -1625,8 +1657,8 @@ impl DiskAPI for LocalDisk {
|
||||
super::fs::access_std(&dst_volume_dir).map_err(|e| to_access_error(e, DiskError::VolumeAccessDenied))?
|
||||
}
|
||||
|
||||
let src_is_dir = has_suffix(src_path, SLASH_SEPARATOR_STR);
|
||||
let dst_is_dir = has_suffix(dst_path, SLASH_SEPARATOR_STR);
|
||||
let src_is_dir = has_suffix(src_path, SLASH_SEPARATOR);
|
||||
let dst_is_dir = has_suffix(dst_path, SLASH_SEPARATOR);
|
||||
|
||||
if !src_is_dir && dst_is_dir || src_is_dir && !dst_is_dir {
|
||||
warn!(
|
||||
@@ -1692,8 +1724,8 @@ impl DiskAPI for LocalDisk {
|
||||
.map_err(|e| to_access_error(e, DiskError::VolumeAccessDenied))?;
|
||||
}
|
||||
|
||||
let src_is_dir = has_suffix(src_path, SLASH_SEPARATOR_STR);
|
||||
let dst_is_dir = has_suffix(dst_path, SLASH_SEPARATOR_STR);
|
||||
let src_is_dir = has_suffix(src_path, SLASH_SEPARATOR);
|
||||
let dst_is_dir = has_suffix(dst_path, SLASH_SEPARATOR);
|
||||
if (dst_is_dir || src_is_dir) && (!dst_is_dir || !src_is_dir) {
|
||||
return Err(Error::from(DiskError::FileAccessDenied));
|
||||
}
|
||||
@@ -1844,7 +1876,7 @@ impl DiskAPI for LocalDisk {
|
||||
}
|
||||
|
||||
let volume_dir = self.get_bucket_path(volume)?;
|
||||
let dir_path_abs = self.get_object_path(volume, dir_path.trim_start_matches(SLASH_SEPARATOR_STR))?;
|
||||
let dir_path_abs = self.get_object_path(volume, dir_path.trim_start_matches(SLASH_SEPARATOR))?;
|
||||
|
||||
let entries = match os::read_dir(&dir_path_abs, count).await {
|
||||
Ok(res) => res,
|
||||
@@ -1880,12 +1912,12 @@ impl DiskAPI for LocalDisk {
|
||||
|
||||
let mut objs_returned = 0;
|
||||
|
||||
if opts.base_dir.ends_with(SLASH_SEPARATOR_STR) {
|
||||
if opts.base_dir.ends_with(SLASH_SEPARATOR) {
|
||||
if let Ok(data) = self
|
||||
.read_metadata(
|
||||
&opts.bucket,
|
||||
path_join_buf(&[
|
||||
format!("{}{}", opts.base_dir.trim_end_matches(SLASH_SEPARATOR_STR), GLOBAL_DIR_SUFFIX).as_str(),
|
||||
format!("{}{}", opts.base_dir.trim_end_matches(SLASH_SEPARATOR), GLOBAL_DIR_SUFFIX).as_str(),
|
||||
STORAGE_FORMAT_FILE,
|
||||
])
|
||||
.as_str(),
|
||||
@@ -2135,7 +2167,7 @@ impl DiskAPI for LocalDisk {
|
||||
let entries = os::read_dir(&self.root, -1).await.map_err(to_volume_error)?;
|
||||
|
||||
for entry in entries {
|
||||
if !has_suffix(&entry, SLASH_SEPARATOR_STR) || !Self::is_valid_volname(clean(&entry).as_str()) {
|
||||
if !has_suffix(&entry, SLASH_SEPARATOR) || !Self::is_valid_volname(clean(&entry).as_str()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2357,7 +2389,7 @@ impl DiskAPI for LocalDisk {
|
||||
force_del_marker: bool,
|
||||
opts: DeleteOptions,
|
||||
) -> Result<()> {
|
||||
if path.starts_with(SLASH_SEPARATOR_STR) {
|
||||
if path.starts_with(SLASH_SEPARATOR) {
|
||||
return self
|
||||
.delete(
|
||||
volume,
|
||||
@@ -2418,7 +2450,7 @@ impl DiskAPI for LocalDisk {
|
||||
if !meta.versions.is_empty() {
|
||||
let buf = meta.marshal_msg()?;
|
||||
return self
|
||||
.write_all_meta(volume, format!("{path}{SLASH_SEPARATOR_STR}{STORAGE_FORMAT_FILE}").as_str(), &buf, true)
|
||||
.write_all_meta(volume, format!("{path}{SLASH_SEPARATOR}{STORAGE_FORMAT_FILE}").as_str(), &buf, true)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -2428,11 +2460,11 @@ impl DiskAPI for LocalDisk {
|
||||
{
|
||||
let src_path = path_join(&[
|
||||
file_path.as_path(),
|
||||
Path::new(format!("{old_data_dir}{SLASH_SEPARATOR_STR}{STORAGE_FORMAT_FILE_BACKUP}").as_str()),
|
||||
Path::new(format!("{old_data_dir}{SLASH_SEPARATOR}{STORAGE_FORMAT_FILE_BACKUP}").as_str()),
|
||||
]);
|
||||
let dst_path = path_join(&[
|
||||
file_path.as_path(),
|
||||
Path::new(format!("{path}{SLASH_SEPARATOR_STR}{STORAGE_FORMAT_FILE}").as_str()),
|
||||
Path::new(format!("{path}{SLASH_SEPARATOR}{STORAGE_FORMAT_FILE}").as_str()),
|
||||
]);
|
||||
return rename_all(src_path, dst_path, file_path).await;
|
||||
}
|
||||
@@ -2584,7 +2616,7 @@ async fn get_disk_info(drive_path: PathBuf) -> Result<(rustfs_utils::os::DiskInf
|
||||
if root_disk_threshold > 0 {
|
||||
disk_info.total <= root_disk_threshold
|
||||
} else {
|
||||
is_root_disk(&drive_path, SLASH_SEPARATOR_STR).unwrap_or_default()
|
||||
is_root_disk(&drive_path, SLASH_SEPARATOR).unwrap_or_default()
|
||||
}
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::disk::error::Result;
|
||||
use crate::disk::error_conv::to_file_error;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR_STR;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR;
|
||||
use std::{
|
||||
io,
|
||||
path::{Component, Path},
|
||||
@@ -116,7 +116,7 @@ pub async fn read_dir(path: impl AsRef<Path>, count: i32) -> std::io::Result<Vec
|
||||
if file_type.is_file() {
|
||||
volumes.push(name);
|
||||
} else if file_type.is_dir() {
|
||||
volumes.push(format!("{name}{SLASH_SEPARATOR_STR}"));
|
||||
volumes.push(format!("{name}{SLASH_SEPARATOR}"));
|
||||
}
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
|
||||
@@ -38,7 +38,7 @@ use rustfs_common::defer;
|
||||
use rustfs_common::heal_channel::HealOpts;
|
||||
use rustfs_filemeta::{MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams};
|
||||
use rustfs_rio::{HashReader, WarpReader};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR_STR, encode_dir_object, path_join};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, encode_dir_object, path_join};
|
||||
use rustfs_workers::workers::Workers;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
@@ -451,10 +451,10 @@ pub fn path2_bucket_object_with_base_path(base_path: &str, path: &str) -> (Strin
|
||||
let trimmed_path = path
|
||||
.strip_prefix(base_path)
|
||||
.unwrap_or(path)
|
||||
.strip_prefix(SLASH_SEPARATOR_STR)
|
||||
.strip_prefix(SLASH_SEPARATOR)
|
||||
.unwrap_or(path);
|
||||
// Find the position of the first '/'
|
||||
let Some(pos) = trimmed_path.find(SLASH_SEPARATOR_STR) else {
|
||||
let Some(pos) = trimmed_path.find(SLASH_SEPARATOR) else {
|
||||
return (trimmed_path.to_string(), "".to_string());
|
||||
};
|
||||
// Split into bucket and prefix
|
||||
|
||||
@@ -83,7 +83,7 @@ use rustfs_utils::http::headers::{AMZ_OBJECT_TAGGING, RESERVED_METADATA_PREFIX,
|
||||
use rustfs_utils::{
|
||||
HashAlgorithm,
|
||||
crypto::hex,
|
||||
path::{SLASH_SEPARATOR_STR, encode_dir_object, has_suffix, path_join_buf},
|
||||
path::{SLASH_SEPARATOR, encode_dir_object, has_suffix, path_join_buf},
|
||||
};
|
||||
use rustfs_workers::workers::Workers;
|
||||
use s3s::header::X_AMZ_RESTORE;
|
||||
@@ -5232,7 +5232,7 @@ impl StorageAPI for SetDisks {
|
||||
&upload_id_path,
|
||||
fi.data_dir.map(|v| v.to_string()).unwrap_or_default().as_str(),
|
||||
]),
|
||||
SLASH_SEPARATOR_STR
|
||||
SLASH_SEPARATOR
|
||||
);
|
||||
|
||||
let mut part_numbers = match Self::list_parts(&online_disks, &part_path, read_quorum).await {
|
||||
@@ -5370,7 +5370,7 @@ impl StorageAPI for SetDisks {
|
||||
let mut populated_upload_ids = HashSet::new();
|
||||
|
||||
for upload_id in upload_ids.iter() {
|
||||
let upload_id = upload_id.trim_end_matches(SLASH_SEPARATOR_STR).to_string();
|
||||
let upload_id = upload_id.trim_end_matches(SLASH_SEPARATOR).to_string();
|
||||
if populated_upload_ids.contains(&upload_id) {
|
||||
continue;
|
||||
}
|
||||
@@ -6125,7 +6125,7 @@ impl StorageAPI for SetDisks {
|
||||
None
|
||||
};
|
||||
|
||||
if has_suffix(object, SLASH_SEPARATOR_STR) {
|
||||
if has_suffix(object, SLASH_SEPARATOR) {
|
||||
let (result, err) = self.heal_object_dir_locked(bucket, object, opts.dry_run, opts.remove).await?;
|
||||
return Ok((result, err.map(|e| e.into())));
|
||||
}
|
||||
@@ -6672,6 +6672,16 @@ async fn get_disks_info(disks: &[Option<DiskStore>], eps: &[Endpoint]) -> Vec<ru
|
||||
ret
|
||||
}
|
||||
async fn get_storage_info(disks: &[Option<DiskStore>], eps: &[Endpoint]) -> rustfs_madmin::StorageInfo {
|
||||
// let mut disks = get_disks_info(disks, eps).await;
|
||||
// disks.sort_by(|a, b| a.total_space.cmp(&b.total_space));
|
||||
//
|
||||
// rustfs_madmin::StorageInfo {
|
||||
// disks,
|
||||
// backend: rustfs_madmin::BackendInfo {
|
||||
// backend_type: rustfs_madmin::BackendByte::Erasure,
|
||||
// ..Default::default()
|
||||
// },
|
||||
// }
|
||||
let mut disks = get_disks_info(disks, eps).await;
|
||||
disks.sort_by(|a, b| a.total_space.cmp(&b.total_space));
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ use rustfs_filemeta::{
|
||||
MetaCacheEntries, MetaCacheEntriesSorted, MetaCacheEntriesSortedResult, MetaCacheEntry, MetadataResolutionParams,
|
||||
merge_file_meta_versions,
|
||||
};
|
||||
use rustfs_utils::path::{self, SLASH_SEPARATOR_STR, base_dir_from_prefix};
|
||||
use rustfs_utils::path::{self, SLASH_SEPARATOR, base_dir_from_prefix};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::broadcast::{self};
|
||||
@@ -132,7 +132,7 @@ impl ListPathOptions {
|
||||
return;
|
||||
}
|
||||
|
||||
let s = SLASH_SEPARATOR_STR.chars().next().unwrap_or_default();
|
||||
let s = SLASH_SEPARATOR.chars().next().unwrap_or_default();
|
||||
self.filter_prefix = {
|
||||
let fp = self.prefix.trim_start_matches(&self.base_dir).trim_matches(s);
|
||||
|
||||
@@ -350,7 +350,7 @@ impl ECStore {
|
||||
if let Some(delimiter) = &delimiter {
|
||||
if obj.is_dir && obj.mod_time.is_none() {
|
||||
let mut found = false;
|
||||
if delimiter != SLASH_SEPARATOR_STR {
|
||||
if delimiter != SLASH_SEPARATOR {
|
||||
for p in prefixes.iter() {
|
||||
if found {
|
||||
break;
|
||||
@@ -477,7 +477,7 @@ impl ECStore {
|
||||
if let Some(delimiter) = &delimiter {
|
||||
if obj.is_dir && obj.mod_time.is_none() {
|
||||
let mut found = false;
|
||||
if delimiter != SLASH_SEPARATOR_STR {
|
||||
if delimiter != SLASH_SEPARATOR {
|
||||
for p in prefixes.iter() {
|
||||
if found {
|
||||
break;
|
||||
@@ -509,7 +509,7 @@ impl ECStore {
|
||||
// warn!("list_path opt {:?}", &o);
|
||||
|
||||
check_list_objs_args(&o.bucket, &o.prefix, &o.marker)?;
|
||||
// if opts.prefix.ends_with(SLASH_SEPARATOR_STR) {
|
||||
// if opts.prefix.ends_with(SLASH_SEPARATOR) {
|
||||
// return Err(Error::msg("eof"));
|
||||
// }
|
||||
|
||||
@@ -527,11 +527,11 @@ impl ECStore {
|
||||
return Err(Error::Unexpected);
|
||||
}
|
||||
|
||||
if o.prefix.starts_with(SLASH_SEPARATOR_STR) {
|
||||
if o.prefix.starts_with(SLASH_SEPARATOR) {
|
||||
return Err(Error::Unexpected);
|
||||
}
|
||||
|
||||
let slash_separator = Some(SLASH_SEPARATOR_STR.to_owned());
|
||||
let slash_separator = Some(SLASH_SEPARATOR.to_owned());
|
||||
|
||||
o.include_directories = o.separator == slash_separator;
|
||||
|
||||
@@ -781,8 +781,8 @@ impl ECStore {
|
||||
let mut filter_prefix = {
|
||||
prefix
|
||||
.trim_start_matches(&path)
|
||||
.trim_start_matches(SLASH_SEPARATOR_STR)
|
||||
.trim_end_matches(SLASH_SEPARATOR_STR)
|
||||
.trim_start_matches(SLASH_SEPARATOR)
|
||||
.trim_end_matches(SLASH_SEPARATOR)
|
||||
.to_owned()
|
||||
};
|
||||
|
||||
@@ -1137,7 +1137,7 @@ async fn merge_entry_channels(
|
||||
if path::clean(&best_entry.name) == path::clean(&other_entry.name) {
|
||||
let dir_matches = best_entry.is_dir() && other_entry.is_dir();
|
||||
let suffix_matches =
|
||||
best_entry.name.ends_with(SLASH_SEPARATOR_STR) == other_entry.name.ends_with(SLASH_SEPARATOR_STR);
|
||||
best_entry.name.ends_with(SLASH_SEPARATOR) == other_entry.name.ends_with(SLASH_SEPARATOR);
|
||||
|
||||
if dir_matches && suffix_matches {
|
||||
to_merge.push(other_idx);
|
||||
|
||||
@@ -51,7 +51,7 @@ use crate::{
|
||||
store_api::{ObjectOptions, PutObjReader},
|
||||
};
|
||||
use rustfs_rio::HashReader;
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR_STR, path_join};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, path_join};
|
||||
use s3s::S3ErrorCode;
|
||||
|
||||
use super::{
|
||||
@@ -403,7 +403,7 @@ impl TierConfigMgr {
|
||||
pub async fn save_tiering_config<S: StorageAPI>(&self, api: Arc<S>) -> std::result::Result<(), std::io::Error> {
|
||||
let data = self.marshal()?;
|
||||
|
||||
let config_file = format!("{}{}{}", CONFIG_PREFIX, SLASH_SEPARATOR_STR, TIER_CONFIG_FILE);
|
||||
let config_file = format!("{}{}{}", CONFIG_PREFIX, SLASH_SEPARATOR, TIER_CONFIG_FILE);
|
||||
|
||||
self.save_config(api, &config_file, data).await
|
||||
}
|
||||
@@ -483,7 +483,7 @@ async fn new_and_save_tiering_config<S: StorageAPI>(api: Arc<S>) -> Result<TierC
|
||||
|
||||
#[tracing::instrument(level = "debug", name = "load_tier_config", skip(api))]
|
||||
async fn load_tier_config(api: Arc<ECStore>) -> std::result::Result<TierConfigMgr, std::io::Error> {
|
||||
let config_file = format!("{}{}{}", CONFIG_PREFIX, SLASH_SEPARATOR_STR, TIER_CONFIG_FILE);
|
||||
let config_file = format!("{}{}{}", CONFIG_PREFIX, SLASH_SEPARATOR, TIER_CONFIG_FILE);
|
||||
let data = read_config(api.clone(), config_file.as_str()).await;
|
||||
if let Err(err) = data {
|
||||
if is_err_config_not_found(&err) {
|
||||
|
||||
@@ -34,7 +34,7 @@ use crate::tier::{
|
||||
tier_config::TierS3,
|
||||
warm_backend::{WarmBackend, WarmBackendGetOpts},
|
||||
};
|
||||
use rustfs_utils::path::SLASH_SEPARATOR_STR;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR;
|
||||
|
||||
pub struct WarmBackendS3 {
|
||||
pub client: Arc<TransitionClient>,
|
||||
@@ -176,7 +176,7 @@ impl WarmBackend for WarmBackendS3 {
|
||||
async fn in_use(&self) -> Result<bool, std::io::Error> {
|
||||
let result = self
|
||||
.core
|
||||
.list_objects_v2(&self.bucket, &self.prefix, "", "", SLASH_SEPARATOR_STR, 1)
|
||||
.list_objects_v2(&self.bucket, &self.prefix, "", "", SLASH_SEPARATOR, 1)
|
||||
.await?;
|
||||
|
||||
Ok(result.common_prefixes.len() > 0 || result.contents.len() > 0)
|
||||
|
||||
@@ -32,7 +32,7 @@ use rustfs_ecstore::{
|
||||
store_api::{ObjectInfo, ObjectOptions},
|
||||
};
|
||||
use rustfs_policy::{auth::UserIdentity, policy::PolicyDoc};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR_STR, path_join_buf};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, path_join_buf};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use std::sync::LazyLock;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
@@ -188,7 +188,7 @@ impl ObjectStore {
|
||||
} else {
|
||||
info.name
|
||||
};
|
||||
let name = object_name.trim_start_matches(&prefix).trim_end_matches(SLASH_SEPARATOR_STR);
|
||||
let name = object_name.trim_start_matches(&prefix).trim_end_matches(SLASH_SEPARATOR);
|
||||
let _ = sender
|
||||
.send(StringOrErr {
|
||||
item: Some(name.to_owned()),
|
||||
|
||||
@@ -32,12 +32,12 @@ use rustfs_ecstore::{
|
||||
error::{Error, Result as StorageResult, StorageError},
|
||||
store_api::{ObjectInfo, ObjectOptions},
|
||||
};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR_STR, path_join_buf};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, path_join_buf};
|
||||
use tokio::time::{Duration, sleep, timeout};
|
||||
use tracing::{error, warn};
|
||||
|
||||
// Data usage constants
|
||||
pub const DATA_USAGE_ROOT: &str = SLASH_SEPARATOR_STR;
|
||||
pub const DATA_USAGE_ROOT: &str = SLASH_SEPARATOR;
|
||||
|
||||
const DATA_USAGE_OBJ_NAME: &str = ".usage.json";
|
||||
|
||||
@@ -47,16 +47,16 @@ pub const DATA_USAGE_CACHE_NAME: &str = ".usage-cache.bin";
|
||||
|
||||
// Data usage paths (computed at runtime)
|
||||
pub static DATA_USAGE_BUCKET: LazyLock<String> =
|
||||
LazyLock::new(|| format!("{RUSTFS_META_BUCKET}{SLASH_SEPARATOR_STR}{BUCKET_META_PREFIX}"));
|
||||
LazyLock::new(|| format!("{RUSTFS_META_BUCKET}{SLASH_SEPARATOR}{BUCKET_META_PREFIX}"));
|
||||
|
||||
pub static DATA_USAGE_OBJ_NAME_PATH: LazyLock<String> =
|
||||
LazyLock::new(|| format!("{BUCKET_META_PREFIX}{SLASH_SEPARATOR_STR}{DATA_USAGE_OBJ_NAME}"));
|
||||
LazyLock::new(|| format!("{BUCKET_META_PREFIX}{SLASH_SEPARATOR}{DATA_USAGE_OBJ_NAME}"));
|
||||
|
||||
pub static DATA_USAGE_BLOOM_NAME_PATH: LazyLock<String> =
|
||||
LazyLock::new(|| format!("{BUCKET_META_PREFIX}{SLASH_SEPARATOR_STR}{DATA_USAGE_BLOOM_NAME}"));
|
||||
LazyLock::new(|| format!("{BUCKET_META_PREFIX}{SLASH_SEPARATOR}{DATA_USAGE_BLOOM_NAME}"));
|
||||
|
||||
pub static BACKGROUND_HEAL_INFO_PATH: LazyLock<String> =
|
||||
LazyLock::new(|| format!("{BUCKET_META_PREFIX}{SLASH_SEPARATOR_STR}.background-heal.json"));
|
||||
LazyLock::new(|| format!("{BUCKET_META_PREFIX}{SLASH_SEPARATOR}.background-heal.json"));
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TierStats {
|
||||
|
||||
@@ -44,7 +44,7 @@ use rustfs_ecstore::pools::{path2_bucket_object, path2_bucket_object_with_base_p
|
||||
use rustfs_ecstore::store_api::{ObjectInfo, ObjectToDelete};
|
||||
use rustfs_ecstore::store_utils::is_reserved_or_invalid_bucket;
|
||||
use rustfs_filemeta::{MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams, ReplicationStatusType};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR_STR, path_join_buf};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, path_join_buf};
|
||||
use s3s::dto::{BucketLifecycleConfiguration, ObjectLockConfiguration};
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc;
|
||||
@@ -110,7 +110,7 @@ impl ScannerItem {
|
||||
/// This converts a directory path like "bucket/dir1/dir2/file" to prefix="bucket/dir1/dir2" and object_name="file"
|
||||
pub fn transform_meta_dir(&mut self) {
|
||||
let prefix = self.prefix.clone(); // Clone to avoid borrow checker issues
|
||||
let split: Vec<&str> = prefix.split(SLASH_SEPARATOR_STR).collect();
|
||||
let split: Vec<&str> = prefix.split(SLASH_SEPARATOR).collect();
|
||||
|
||||
if split.len() > 1 {
|
||||
let prefix_parts: Vec<&str> = split[..split.len() - 1].to_vec();
|
||||
|
||||
@@ -22,7 +22,7 @@ use rustfs_ecstore::set_disk::SetDisks;
|
||||
use rustfs_ecstore::store_api::{BucketInfo, BucketOptions, ObjectInfo};
|
||||
use rustfs_ecstore::{StorageAPI, error::Result, store::ECStore};
|
||||
use rustfs_filemeta::FileMeta;
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR_STR, path_join_buf};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, path_join_buf};
|
||||
use s3s::dto::{BucketLifecycleConfiguration, ReplicationConfiguration};
|
||||
use std::collections::HashMap;
|
||||
use std::time::SystemTime;
|
||||
@@ -469,7 +469,7 @@ impl ScannerIOCache for SetDisks {
|
||||
#[async_trait::async_trait]
|
||||
impl ScannerIODisk for Disk {
|
||||
async fn get_size(&self, mut item: ScannerItem) -> Result<SizeSummary> {
|
||||
if !item.path.ends_with(&format!("{SLASH_SEPARATOR_STR}{STORAGE_FORMAT_FILE}")) {
|
||||
if !item.path.ends_with(&format!("{SLASH_SEPARATOR}{STORAGE_FORMAT_FILE}")) {
|
||||
return Err(StorageError::other("skip file".to_string()));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
// 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.
|
||||
|
||||
//! Utility functions to handle Windows paths, specifically converting UNC paths to regular paths where possible.
|
||||
//! This is useful for compatibility with tools that do not support UNC paths (starting with `\\?\`).
|
||||
|
||||
#[cfg(any(windows, test))]
|
||||
use std::ffi::OsStr;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::path::{Component, Prefix};
|
||||
use std::{
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Takes any path, and when possible, converts Windows UNC paths to regular paths.
|
||||
/// If the path can't be converted, it's returned unmodified.
|
||||
///
|
||||
/// On non-Windows this is a no-op.
|
||||
///
|
||||
/// `\\?\C:\Windows` will be converted to `C:\Windows`,
|
||||
/// but `\\?\C:\COM` will be left as-is (due to a reserved filename).
|
||||
///
|
||||
/// Use this to pass arbitrary paths to programs that may not be UNC-aware.
|
||||
///
|
||||
/// It's generally safe to pass UNC paths to legacy programs, because
|
||||
/// these paths contain a reserved prefix, so will gracefully fail
|
||||
/// if used with legacy APIs that don't support UNC.
|
||||
///
|
||||
/// This function does not perform any I/O.
|
||||
///
|
||||
/// Currently paths with unpaired surrogates aren't converted even if they
|
||||
/// could be, due to limitations of Rust's `OsStr` API.
|
||||
///
|
||||
/// To check if a path remained as UNC, use [`is_simplified()`] or `path.as_os_str().as_encoded_bytes().starts_with(b"\\\\")`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - The path to simplify.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A reference to the simplified path if it could be simplified, or the original path otherwise.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::path::Path;
|
||||
/// use rustfs_utils::simplified;
|
||||
///
|
||||
/// #[cfg(windows)]
|
||||
/// {
|
||||
/// let unc_path = Path::new(r"\\?\C:\Windows");
|
||||
/// let simple_path = simplified(unc_path);
|
||||
/// assert_eq!(simple_path, Path::new(r"C:\Windows"));
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn simplified(path: &Path) -> &Path {
|
||||
try_simplified(path).unwrap_or(path)
|
||||
}
|
||||
|
||||
/// Like `std::fs::canonicalize()`, but on Windows it outputs the most
|
||||
/// compatible form of a path instead of UNC.
|
||||
///
|
||||
/// This function first canonicalizes the path (resolving symlinks and absolute path),
|
||||
/// and then attempts to simplify it using [`simplified()`] on Windows.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - The path to canonicalize.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The canonicalized and simplified path, or an error if canonicalization fails.
|
||||
#[inline(always)]
|
||||
pub fn canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::canonicalize(path)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
canonicalize_win(path)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn canonicalize_win(path: &Path) -> io::Result<PathBuf> {
|
||||
let real_path = fs::canonicalize(path)?;
|
||||
Ok(try_simplified(&real_path).map(PathBuf::from).unwrap_or(real_path))
|
||||
}
|
||||
|
||||
/// Returns `true` if the path is relative or starts with a disk prefix (like "C:").
|
||||
///
|
||||
/// This checks if the path is in a "simplified" format. On Windows, this means it
|
||||
/// is either a relative path, a rooted path without a prefix, or starts with a drive letter (e.g., `C:\`).
|
||||
/// UNC paths (starting with `\\`) and verbatim paths (starting with `\\?\`) will return `false`.
|
||||
///
|
||||
/// On non-Windows platforms, this always returns `true`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - The path to check.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `true` if the path is simplified, `false` otherwise.
|
||||
#[cfg_attr(not(windows), allow(unused))]
|
||||
#[must_use]
|
||||
pub fn is_simplified(path: &Path) -> bool {
|
||||
#[cfg(windows)]
|
||||
if let Some(Component::Prefix(prefix)) = path.components().next() {
|
||||
return matches!(prefix.kind(), Prefix::Disk(..));
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::canonicalize as realpath;
|
||||
|
||||
/// Checks if the filename is valid for a regular Windows path.
|
||||
///
|
||||
/// This checks for:
|
||||
/// - Length limit (255 chars)
|
||||
/// - Invalid characters (< > : " / \ | ? * and control chars)
|
||||
/// - Trailing spaces or dots
|
||||
#[cfg(any(windows, test))]
|
||||
fn is_valid_filename(file_name: &OsStr) -> bool {
|
||||
// Convert to str first. If not valid UTF-8, it's not a valid "simplified" filename candidate
|
||||
// (since we require the whole path to be UTF-8 at the end).
|
||||
let s = match file_name.to_str() {
|
||||
Some(s) => s,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// Windows filename limit is 255 characters (UTF-16 code units)
|
||||
// We check byte length first as a fast path optimization.
|
||||
if s.len() > 255 && s.encode_utf16().count() > 255 {
|
||||
return false;
|
||||
}
|
||||
|
||||
if s.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only ASCII subset is checked, and WTF-8/UTF-8 is safe for that
|
||||
// Invalid chars: < > : " / \ | ? * and control chars (0-31)
|
||||
let byte_str = s.as_bytes();
|
||||
if byte_str
|
||||
.iter()
|
||||
.any(|&c| matches!(c, 0..=31 | b'<' | b'>' | b':' | b'"' | b'/' | b'\\' | b'|' | b'?' | b'*'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Filename can't end with . or space
|
||||
if matches!(byte_str.last(), Some(b' ' | b'.')) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Checks if the filename is a reserved DOS device name (e.g., CON, PRN, COM1).
|
||||
///
|
||||
/// Reserved names are case-insensitive and apply even if an extension is present (e.g., "CON.txt" is reserved).
|
||||
#[cfg(any(windows, test))]
|
||||
fn is_reserved<P: AsRef<OsStr>>(file_name: P) -> bool {
|
||||
let s = match file_name.as_ref().to_str() {
|
||||
Some(s) => s,
|
||||
None => return false, // Reserved names are all ASCII
|
||||
};
|
||||
|
||||
if s.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the stem (part before the first dot)
|
||||
// "CON.txt" -> "CON"
|
||||
// "CON" -> "CON"
|
||||
let stem = match s.find('.') {
|
||||
Some(idx) => &s[..idx],
|
||||
None => s,
|
||||
};
|
||||
|
||||
// Trim trailing spaces (Windows ignores them, so "CON " is "CON")
|
||||
let stem = stem.trim_end();
|
||||
|
||||
// Reserved names are 3 or 4 chars
|
||||
if stem.len() != 3 && stem.len() != 4 {
|
||||
return false;
|
||||
}
|
||||
|
||||
if stem.len() == 3 {
|
||||
return stem.eq_ignore_ascii_case("CON")
|
||||
|| stem.eq_ignore_ascii_case("PRN")
|
||||
|| stem.eq_ignore_ascii_case("AUX")
|
||||
|| stem.eq_ignore_ascii_case("NUL");
|
||||
}
|
||||
|
||||
// Check COM1-9, LPT1-9
|
||||
let (base, suffix) = stem.split_at(3);
|
||||
if base.eq_ignore_ascii_case("COM") || base.eq_ignore_ascii_case("LPT") {
|
||||
return suffix.chars().next().map(|c| c.is_ascii_digit() && c != '0').unwrap_or(false);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[inline]
|
||||
const fn try_simplified(_path: &Path) -> Option<&Path> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn try_simplified(path: &Path) -> Option<&Path> {
|
||||
let mut components = path.components();
|
||||
// Check if it starts with \\?\
|
||||
match components.next() {
|
||||
Some(Component::Prefix(p)) => match p.kind() {
|
||||
Prefix::VerbatimDisk(..) => {}
|
||||
_ => return None, // Other kinds of UNC paths or regular paths
|
||||
},
|
||||
_ => return None, // relative or empty
|
||||
}
|
||||
|
||||
// Validate all subsequent components
|
||||
for component in components {
|
||||
match component {
|
||||
Component::RootDir => {}
|
||||
Component::Normal(file_name) => {
|
||||
if !is_valid_filename(file_name) || is_reserved(file_name) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => return None, // UNC paths take things like ".." literally, which we can't simplify to
|
||||
};
|
||||
}
|
||||
|
||||
// We use to_str() here because we need to slice the string to remove the prefix.
|
||||
// This implicitly requires the path to be valid UTF-8.
|
||||
// If it's not, we return None, which is safe (we just don't simplify).
|
||||
let s = path.to_str()?;
|
||||
// Remove "\\?\" (4 chars)
|
||||
let simplified = &s[4..];
|
||||
|
||||
// Check length limit (MAX_PATH is 260)
|
||||
if simplified.len() > 260 && simplified.encode_utf16().count() > 260 {
|
||||
return None;
|
||||
}
|
||||
Some(Path::new(simplified))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_reserved_names() {
|
||||
let reserved = [
|
||||
"CON",
|
||||
"PRN",
|
||||
"AUX",
|
||||
"NUL",
|
||||
"COM1",
|
||||
"COM2",
|
||||
"COM3",
|
||||
"COM4",
|
||||
"COM5",
|
||||
"COM6",
|
||||
"COM7",
|
||||
"COM8",
|
||||
"COM9",
|
||||
"LPT1",
|
||||
"LPT2",
|
||||
"LPT3",
|
||||
"LPT4",
|
||||
"LPT5",
|
||||
"LPT6",
|
||||
"LPT7",
|
||||
"LPT8",
|
||||
"LPT9",
|
||||
"con",
|
||||
"Con",
|
||||
"CON.txt",
|
||||
"con.txt",
|
||||
"CON .txt",
|
||||
"con .txt",
|
||||
"CON.tar.gz",
|
||||
"con.",
|
||||
"con .",
|
||||
];
|
||||
for name in reserved {
|
||||
assert!(is_reserved(name), "Should be reserved: {}", name);
|
||||
}
|
||||
|
||||
let not_reserved = [
|
||||
"CON0", "COM0", "LPT0", "COM10", "LPT10", "CONsole", "PrNter", "NuLly", "not_con", "con_not", ".CON", "@CON",
|
||||
"CON。", "foo.con",
|
||||
];
|
||||
for name in not_reserved {
|
||||
assert!(!is_reserved(name), "Should NOT be reserved: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_filename() {
|
||||
let invalid = [
|
||||
"",
|
||||
".",
|
||||
"..",
|
||||
" ",
|
||||
" .",
|
||||
"foo ",
|
||||
"foo.",
|
||||
"foo/bar",
|
||||
"foo\\bar",
|
||||
"foo:bar",
|
||||
"foo*bar",
|
||||
"foo?bar",
|
||||
"foo\"bar",
|
||||
"foo<bar",
|
||||
"foo>bar",
|
||||
"foo|bar",
|
||||
"foo\0bar",
|
||||
"foo\x1fbar",
|
||||
];
|
||||
for name in invalid {
|
||||
assert!(!is_valid_filename(OsStr::new(name)), "Should be invalid: {:?}", name);
|
||||
}
|
||||
|
||||
let valid = [
|
||||
"foo",
|
||||
"foo.bar",
|
||||
".foo",
|
||||
"foo.bar.baz",
|
||||
"foo bar",
|
||||
"foo. bar", // space inside is ok
|
||||
"中文",
|
||||
"😀",
|
||||
];
|
||||
for name in valid {
|
||||
assert!(is_valid_filename(OsStr::new(name)), "Should be valid: {:?}", name);
|
||||
}
|
||||
|
||||
// Test length limit
|
||||
let long_name = "a".repeat(256);
|
||||
assert!(!is_valid_filename(OsStr::new(&long_name)));
|
||||
let ok_name = "a".repeat(255);
|
||||
assert!(is_valid_filename(OsStr::new(&ok_name)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_simplified_windows() {
|
||||
// Basic
|
||||
assert_eq!(simplified(Path::new(r"\\?\C:\Windows")), Path::new(r"C:\Windows"));
|
||||
|
||||
// Reserved
|
||||
assert_eq!(simplified(Path::new(r"\\?\C:\CON")), Path::new(r"\\?\C:\CON"));
|
||||
assert_eq!(simplified(Path::new(r"\\?\C:\aux.txt")), Path::new(r"\\?\C:\aux.txt"));
|
||||
|
||||
// Long paths
|
||||
let long_path = format!(r"\\?\C:\{}", "a".repeat(300));
|
||||
assert_eq!(simplified(Path::new(&long_path)), Path::new(&long_path));
|
||||
|
||||
let ok_path = format!(r"\\?\C:\{}", "a".repeat(200));
|
||||
assert_eq!(simplified(Path::new(&ok_path)), Path::new(&ok_path[4..]));
|
||||
|
||||
// Relative/other prefixes
|
||||
assert_eq!(simplified(Path::new(r"C:\Windows")), Path::new(r"C:\Windows"));
|
||||
assert_eq!(simplified(Path::new(r"\\server\share")), Path::new(r"\\server\share"));
|
||||
assert_eq!(simplified(Path::new(r"\\.\C:\Windows")), Path::new(r"\\.\C:\Windows"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn test_simplified_unix() {
|
||||
let p = Path::new("/tmp/foo");
|
||||
assert_eq!(simplified(p), p);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_simplified() {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
assert!(is_simplified(Path::new(r"C:\Windows")));
|
||||
assert!(is_simplified(Path::new(r"relative\path")));
|
||||
assert!(!is_simplified(Path::new(r"\\?\C:\Windows")));
|
||||
assert!(!is_simplified(Path::new(r"\\server\share")));
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
assert!(is_simplified(Path::new("/tmp/foo")));
|
||||
assert!(is_simplified(Path::new("relative/path")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,5 +85,10 @@ pub use notify::*;
|
||||
#[cfg(feature = "obj")]
|
||||
pub mod obj;
|
||||
|
||||
#[cfg(feature = "path")]
|
||||
mod dunce;
|
||||
#[cfg(feature = "path")]
|
||||
pub use dunce::*;
|
||||
mod envs;
|
||||
|
||||
pub use envs::*;
|
||||
|
||||
@@ -44,7 +44,7 @@ pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<DiskInfo> {
|
||||
}
|
||||
|
||||
let total = total_number_of_bytes;
|
||||
let free = total_number_of_free_bytes;
|
||||
let free = free_bytes_available;
|
||||
|
||||
if free > total {
|
||||
return Err(Error::other(format!(
|
||||
@@ -180,8 +180,6 @@ pub fn get_drive_stats(_major: u32, _minor: u32) -> std::io::Result<IOStats> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::os::{get_info, same_disk};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn test_get_info_valid_path() {
|
||||
|
||||
+413
-706
File diff suppressed because it is too large
Load Diff
@@ -44,7 +44,7 @@ use rustfs_policy::policy::{
|
||||
BucketPolicy,
|
||||
action::{Action, AdminAction},
|
||||
};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR_STR, path_join_buf};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, path_join_buf};
|
||||
use s3s::{
|
||||
Body, S3Request, S3Response, S3Result,
|
||||
dto::{
|
||||
@@ -423,7 +423,7 @@ impl Operation for ImportBucketMetadata {
|
||||
// Extract bucket names
|
||||
let mut bucket_names = Vec::new();
|
||||
for (file_path, _) in &file_contents {
|
||||
let file_path_split = file_path.split(SLASH_SEPARATOR_STR).collect::<Vec<&str>>();
|
||||
let file_path_split = file_path.split(SLASH_SEPARATOR).collect::<Vec<&str>>();
|
||||
|
||||
if file_path_split.len() < 2 {
|
||||
warn!("file path is invalid: {}", file_path);
|
||||
@@ -462,7 +462,7 @@ impl Operation for ImportBucketMetadata {
|
||||
|
||||
// Second pass: process file contents
|
||||
for (file_path, content) in file_contents {
|
||||
let file_path_split = file_path.split(SLASH_SEPARATOR_STR).collect::<Vec<&str>>();
|
||||
let file_path_split = file_path.split(SLASH_SEPARATOR).collect::<Vec<&str>>();
|
||||
|
||||
if file_path_split.len() < 2 {
|
||||
warn!("file path is invalid: {}", file_path);
|
||||
|
||||
Reference in New Issue
Block a user