Commit Graph

7178 Commits

Author SHA1 Message Date
InitCool e2583ddcd6 feat(oidc): make the Console session duration configurable (#8568)
* feat(oidc): make the Console session duration configurable

The OIDC callback hard-coded the Console credential lifetime at 3600
seconds, so a Console session always ended after about an hour.
RUSTFS_CONSOLE_AUTH_TIMEOUT, the documented knob next to this behaviour,
is the Console HTTP request timeout and cannot extend a session.

Resolve the lifetime from the process-level
RUSTFS_CONSOLE_OIDC_SESSION_DURATION instead and forward it to
complete_authorization_code(). Unset, unparseable, zero, and negative
values keep DEFAULT_CONSOLE_OIDC_SESSION_DURATION_SECS (3600), so
existing deployments keep the previous behaviour and a misconfigured
variable can never issue an already expired credential. Values above
CONSOLE_OIDC_SESSION_DURATION_MAX_SECS (7 days) are clamped to that
maximum with a warning, which also caps how far an operator can stretch a
credential by accident.

Keep the Console logout token for that same lifetime. OidcStateStore
pinned the logout cache to a hard-coded hour, so a session configured to
last longer than that dropped its logout token while the credential was
still valid: RP-initiated logout could no longer resolve, and the browser
was sent back to the Console login page with the IdP session still
active. The logout cache now derives its TTL from the same
console_session_duration_secs(), which applies the same clamp, and the
callback reads its lifetime from that one function instead of a local
copy, so the credential lifetime and the logout-token retention cannot
drift apart. Add tests covering the default hour, an extended session,
the maximum, and out-of-range values.

The variable is deliberately not part of ENV_IDENTITY_OPENID_KEYS: that
list is suffix-scanned per provider, and this setting applies to every
provider. It has no identity_openid KVS equivalent and needs a restart,
like the other process-level settings in the same document.

Document the setting in the login flow, the process-level table, and the
troubleshooting table, and clarify that RUSTFS_CONSOLE_AUTH_TIMEOUT is a
request timeout rather than a session lifetime.

* test(ecstore): retain offline marker fixtures across reconnect

Keep simulated drive handles attached and reject I/O until explicit rejoin.
Attempt reconnect in the current-marker case so the former CI race is
deterministic while retaining physical cleanup and historical-data checks.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(oidc): align session diagnostics and lifetime regression tests

Use structured IAM diagnostics for clamping and move configuration tests
to their owning crate. Cover duration boundaries, logout retention and
signed credential expiration through the federation binding.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: Chris <anzhengchao@gmail.com>
Co-authored-by: Hauser <housemecn@gmail.com>
Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-19 06:52:05 +09:00
Henry Guo 317191ce00 fix(scanner): scale backup deadline to checkpoint save cost (#8659) 2026-10-19 06:21:33 +09:00
Chris f3c4a790dd fix(listing): accept literal prefixes with repeated slashes (#8605)
Co-authored-by: Hauser <housemecn@gmail.com>
2026-10-19 01:27:51 +08:00
Hauser 6681dd12e3 fix(scanner): expose mixed sweep checkpoint transitions (#8657)
* fix(scanner): expose mixed sweep checkpoint transitions

Correlate checkpoint admission, incomplete sweeps, and successful terminal
Partial persistence by bucket/source and cycle/leader epoch. Share the
traversal-position predicate and local/remote save diagnostic without
changing certification, acknowledgement, or persistence contracts.

Extend real-disk cache regressions across cycles and leader changes and
verify that failed terminal saves retain an older periodic frontier until
a successful terminal save permits fresh certification.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(scanner): report the aggregate incomplete sweep gate

Expose the existing unresolved_objects decision in the incomplete-sweep
event. Nested cache-entry failures can keep a sweep partial while both
the root counter and the capped diagnostic failure map are empty.

Extend the real-disk regression with diagnostics disabled and distinguish
this case from mixed-only coverage without adding another cache walk.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-18 16:50:42 +08:00
Henry Guo 3b0ee83466 fix(scanner): pace checkpoints by snapshot construction cost (#8656) 2026-10-18 13:36:58 +08:00
Henry Guo 02c4f4c97f fix(ecstore): clean completed PUT staging on pre-commit cancellation (#8654)
fix(ecstore): clean staged PUT data on pre-commit cancellation
2026-10-18 13:36:33 +08:00
Hauser c28e02a722 fix(ecstore): preserve inline heal rollback staging lifetime (#8650)
* fix(ecstore): preserve inline heal rollback staging lifetime

Retain the TMP source prefix in the existing publication lease until the owned receiver finishes rollback. Serialize recursive cleanup and stale GC with the same key, and avoid decoding an unused old version during inline healing.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(ecstore): reuse staging lease during parent cleanup

Reuse the existing staging lease when pruning its parent directory, including already-missing prefixes, and retain it in the physical rmdir executor after cancellation. Match the inline-heal fsync test hook to the canonical group path on Linux.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-18 12:58:57 +08:00
Henry Guo 1df96624df feat(table-catalog): support conditional maintenance worker retries (#8562)
* feat(table-catalog): support conditional maintenance worker retries

* fix(table-catalog): refresh conditional worker claim time

* style(table-catalog): format conditional worker expiry test

---------

Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-10-18 10:03:58 +08:00
Henry Guo ee534fa19b test(table-catalog): validate current backing contracts (#8559)
* test(table-catalog): qualify current backing contracts

* fix(table-catalog): verify smoke cleanup after object rename

* fix(ci): include the baseline writer test in full E2E selection

* fix(ci): reserve test capacity for disk-backed lock probes

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-10-18 10:03:30 +08:00
Chris 668bc7a4ba test(utils): cover mixed-case metadata alias conflicts (#8651) 2026-10-18 10:02:47 +08:00
Hauser e29a76f391 fix(heal): verify checkpoint integrity before applying defaults (#8649)
* fix(heal): verify checkpoint integrity before applying defaults

Validate the persisted JSON shape so intact signed legacy checkpoints can
reach the existing safe schema rewind. Preserve deployed field ordering and
read historical sorted-key digests without changing the checkpoint schema.
Reuse the signing value when saving and retain strict duplicate-field parsing.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(heal): preserve checkpoint writer rollback compatibility

Keep ordered checkpoint parsing local rather than enabling preserve_order
throughout the server. Sort writer keys recursively to retain the production
base reader's digest format while accepting both historical read orders.

Cover real writer canonicalization, nested scalar ordering and depth limits.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-18 04:03:16 +08:00
Chris 3ddd307304 chore(release): align installation references for 1.0.3 (#8646)
Co-authored-by: Hauser <housemecn@gmail.com>
2026-10-18 02:58:32 +08:00
Ramakrishna Chilaka 9946c67b53 perf(filemeta): avoid clones and redundant map scans in into_fileinfo (#8647)
FileMetaShallowVersion::into_fileinfo and to_fileinfo_with_part_checksums previously decoded an owned FileMetaVersion and then converted it via shared reference (&self), cloning meta_user, meta_sys, part_etags, and part_indices before dropping the temporary FileMetaVersion and scanning the metadata map up to 10 times for optional internal suffixes. Additionally, metadata_compat helpers (get_str, contains_key_str, remove_str, get_consistent_value) allocated two temporary heap Strings via both_keys(suffix) on every absent-key fallback.

- Replace both_keys(suffix) in case-insensitive fallback scans with allocation-free prefix+suffix slice matching.
- Add consuming into_fileinfo_with_part_checksums on FileMetaVersion and MetaObject to move decoded part vectors and metadata maps in place.
- Fold transition-version metadata formatting and optional suffix presence detection into the single meta_sys pass, and skip target_delete_marker_versions in get_internal_replication_state when no delete-marker version keys are present.
2026-10-18 02:57:21 +08:00
Hauser 286e4cf30b perf(ecstore): keep inline heal rollback backups in staging (#8648)
* perf(ecstore): keep inline heal rollback backups in staging

Keep inline repair rollback local to the disk commit and avoid publishing
a live backup that needs a second cleanup operation. Preserve legacy peer
and non-inline backup reclamation, with production-mode rollback coverage.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>
Co-Authored-By: Ramakrishna Chilaka <rkchilaka378@gmail.com>

* test(ecstore): route inline heal imports through test facade

Keep ECStore integration-test API access behind storage_api.rs to satisfy
the architecture migration boundary without changing test behavior.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
Co-authored-by: Ramakrishna Chilaka <rkchilaka378@gmail.com>
2026-10-18 01:51:52 +08:00
Hauser 453be3dd84 fix(scanner): attribute incomplete census and lease waits (#8643)
* fix(scanner): attribute incomplete census and lease waits

Report bucket sweep blockers and correlate partial remote work and rejected
roots with their set and cycle. Keep distinct accepted bucket names in the
result collector so completion counts and missing-bucket attribution share
one source of truth, including during cancellation drain.

Distinguish worker completion from coverage, retain peer/purpose context in
lease failures without changing retry suffixes, and avoid claiming that an
unverified leader fence changed or a waiting scan was complete.

Document the census stages and preserve publication, dirty acknowledgement,
remote failure classification and checkpoint safety rules.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* perf(scanner): move set caches through the result collector

Give the sole result collector exclusive ownership of the aggregate cache
and return it together with the accepted bucket names after channel drain.
Move that cache into complete persistence or incomplete updates instead of
sharing a mutex and cloning the terminal cache.

Return each accepted result's owned name from cache application so coverage
tracking also avoids an extra name allocation. Preserve worker joins,
cancellation drain, publication metadata and all certification fences.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-18 01:05:14 +08:00
唐小鸭 222beaba00 fix(scanner): certify dirty usage per bucket and complete cycles with pending buckets (#8542)
* fix(scanner): treat replication status writebacks as status-class hits

A replication status writeback (persist_replication_state_if_current in
Update mode, including the heals the scanner queues during its own walk)
marked the bucket dirty on the namespace path: it advanced the dirty-usage
generation, woke the scanner, reached the durable dirty journal and
entered the activity snapshot. Any replicated bucket therefore made every
scanner cycle Superseded, the authoritative usage baseline was never
certified and usage freshness stayed stale for as long as replication ran
(#8375).

The writeback is now status class: record_status_class_mutation records
only the process-local producer coverage bit and nothing else. There is
no rescan queue for it, because a deployment with an active replication
rule already walks every bucket on every cycle
(ScannerMaintenanceFeatures::needs_regular_cycle) and that walk refreshes
replication_stats. The routing lives in a named, unit-tested function in
rustfs/src/storage/storage_api.rs; tier-expiration cleanup removes data
and stays on the namespace path.

No persisted format, protocol, admin API or configuration changes.

This is phase 1 of docs/architecture/scanner-per-bucket-convergence.md,
which this change adds together with its index entry.

Refs #8375, #8494, rustfs/backlog#2816.

* fix(scanner): refresh replication statistics during maintenance

Refresh compacted prefixes and completed bucket caches during replication maintenance. Bind set execution reuse to the observed replication statistics so a same-cycle retry cannot restore older counters.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* test(scanner): avoid expanding the legacy S3 DTO surface

Decode the existing replication configuration type from a JSON fixture instead of adding direct S3 DTO references to a new file. Preserve the compacted-prefix and same-cycle assertions and the footprint ratchet.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* feat(scanner): attribute activity guardrail vetoes per field

The post-walk cluster activity proof compared the whole activity snapshot,
so any namespace or dirty-usage generation movement vetoed publication.
Veto only on the fields that change which heap the walk described
(peer set, instance id, protocol version, topology, maintenance
generation, data movement, movement generation, publication blocked) and
name the field and host in the log and in a new usage-freshness counter.
Generation movement is a shade signal consumed per bucket. A remote peer
whose dirty-usage state moved keeps the whole-cycle outcome until scoped
acknowledgements can name its buckets.

* feat(data-usage): carry pending-bucket certification in DataUsageInfo

Add the additive fields a per-bucket certified scanner snapshot needs:
the sorted list and count of buckets whose dirty-usage generation moved
during the walk, and an explicit observational marker for snapshots whose
authoritative publication was vetoed. All three default when absent and
are skipped when empty, so older readers see the existing snapshot shape.

* feat(scanner): certify dirty usage per bucket and complete cycles with pending buckets

A structurally complete walk that the guardrail did not veto is now
`Complete` even when the dirty-usage generation moved during the walk.
Certification keeps the compare-and-remove semantics of
clear_dirty_usage_buckets: buckets whose generation still equals the start
snapshot are cleared, buckets written during the walk stay in the dirty map
and are published as the pending set, so usage_snapshot_converged becomes
`pending == 0`. The authoritative object is written for such cycles; the
observed route is selected by the explicit observational mark that vetoed
cycles carry instead of by the convergence flag, and the root publication
proof is read for every authoritative save. The post-lease activity recheck
compares the guardrail fields, with the peer dirty-usage fallback, instead
of the full snapshot digest.

* feat(scanner): accept unconverged complete publications as the cache baseline

complete_scanner_cache_baseline_plan_digest required the authoritative
snapshot to be converged, so a site whose cycles always left a bucket
pending could never scope a rescan and every cycle was a full walk. The
authoritative object is now a structural baseline whenever it carries a
convergence marker and no observational mark; bucket-cache reuse is then
governed by certification, because pending buckets stay in the dirty map
and are selected for the rescan while certified buckets reuse their set
cache. Observed candidates keep requiring the unconverged marker, and
segment reuse evidence keeps requiring a converged baseline.

Add the single-disk cycle test: a bucket written during the walk is the
only pending bucket, the others are certified, the publication is
authoritative and unconverged, and the next cycle is scoped to that
bucket; a held PUT tail still supersedes onto the observed route.

* feat(admin): expose scanner guardrail vetoes in usage freshness

Carry the attributed guardrail veto counter, timestamp and <field>@<host>
reason from the scanner metrics report into the madmin scanner status so
an operator can tell a rebalance or a PUT tail from a broken probe. The
fields are additive with serde defaults and merge like the deferral
fields.

* docs(scanner): record per-bucket certification in the design and changelog

Amend the convergence design where the implementation deviates: pending
buckets carry their walked value, maintenance_generation stays a veto,
guardrail localisation and the remaining phase-2 items ship separately,
last_cycle_result keeps its success label, and the admin surface gains the
guardrail veto fields. Update the publication contract for the post-lease
guardrail recheck and the explicit observational routing mark.

* refactor(scanner): drop the unreachable failed-bucket input from the pending set

A failed bucket never reaches a Complete cycle, so the pending set only
needs the start snapshot. Also remove a redundant clone in the data-usage
round-trip test.

* test(scanner): name the activity field mutation table type

* test(scanner): fill the post-walk gate in merged replication fixtures

The replication maintenance fixture merged from main builds a
ScannerCycleRequest literal and predates the test-only post_walk_gate
field; give it the default so the test target compiles on the rebased
branch.

* ci(nextest): run the admin target-repair fixtures on the ecstore base stack

repair_transaction_rejects_a_bucket_recreated_after_target_validation
overflowed the 2 MiB libtest default twice in a row on Linux CI for this
branch. Measured by sweeping RUST_MIN_STACK with the ci profile on macOS
arm64: main (65f30bd5c) passes at 2032 KiB and overflows at 2016 KiB,
this branch passes at 2040 KiB and overflows at 2032 KiB; on both trees
the overflow is in the test's delete_bucket step on the real store, and
the futures on that path are small (the peak is nested poll depth, not
one large future). The module was given this stack in #7272 and removed
in #7280 to check that boxing (#7277) sufficed; with the default now
exhausted on main itself, give the fixtures the same 4 MiB base stack as
the other real-storage rustfs fixtures, in both profiles.

* test(scanner): fill the post-walk gate in the walkers-per-disk fixture

The merge from main brought the walkers_per_disk cycle fixture (#8539),
whose ScannerCycleRequest initializer predates the test-only
post_walk_gate field this branch adds; the scanner test target did not
compile. Fill the default so the merged head builds.

---------

Co-authored-by: Hauser <housemecn@gmail.com>
Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-18 00:25:02 +08:00
Hauser aeae6f9980 fix(ecstore): reclaim rollback backups after object heal (#8641)
* fix(ecstore): reclaim rollback backups after object heal

Retire only the backup returned by a successful, fenced heal publication.
Keep child keys, live data directories, and delete recovery evidence safe.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(ecstore): retain heal cleanup ownership through cancellation

Forward publication and repair key owners to the existing owned delete
executor so local unlink cannot outlive its exact-key serialization.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-18 00:03:00 +08:00
Hauser f64cb816db fix(scanner): preserve checkpoint progress across object failures (#8642)
Separate completed traversal from persisted object health debt so leader
handoffs reuse healthy subtrees and retry failures without losing progress.
Fence old readers with a new receipt digest domain and preserve exact
accounting under diagnostic eviction, compaction and punctuation ordering.

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-17 23:33:47 +08:00
Hauser 816e18f2c8 fix(filemeta): read large metacache records incrementally (#8640)
* fix(filemeta): read large metacache records incrementally

Replace the aggregate 16 MiB guard with fallible chunked growth so
versioned xl.meta records remain listable without trusting length prefixes.
Discard skipped payloads with a bounded buffer and distinguish truncated
records from compatible empty or between-entry EOF.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* perf(filemeta): remove redundant metacache reader copies

Track the returned entry with a boolean instead of cloning its metadata,
decode object names directly from the borrowed slice, and derive the read
position from buffer length. Preserve scratch reuse, exact output capacity,
wire framing, and sticky corruption errors.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-17 23:25:21 +08:00
Chris e98992bb87 docs(release): add post-release advisory reconciliation (#8639)
* docs(release): add post-release advisory reconciliation

* docs(release): remove hard wraps from advisory guidance
2026-10-17 22:01:08 +08:00
cxymds ce0c532740 fix(lifecycle): reject expiry from stale policy snapshots (#8637)
* fix(lifecycle): reject expiry from stale policy snapshots

* fix(lifecycle): keep expiry snapshots behind domain boundary

* test(ecstore): fix lifecycle fixtures without test-util
2026-10-17 21:45:30 +08:00
Chris 3defc19d5e fix(ci): refresh E2E selection and isolate disk lock probes (#8638)
* fix(ci): include the baseline writer test in full E2E selection

* fix(ci): reserve capacity for short-deadline disk lock probes
2026-10-17 21:04:04 +08:00
cxymds b1f3ebb184 fix(ecstore): converge partial pool identity writes (#8636)
* fix(ecstore): converge partial pool identity writes

* fix(ecstore): bound pool metadata CAS future state
2026-10-17 20:45:49 +08:00
唐小鸭 b40f1e5fab fix(utils): harden Win32 volume probes and flag Windows network drives (#8631)
* fix(utils): NUL-terminate and extend paths passed to Win32 volume APIs

`get_volume_name` returned the volume root truncated at its NUL and the
result was then passed to `GetVolumeInformationW` as a PCWSTR, an
out-of-bounds read. The disk-space and volume APIs also received the
caller's raw path, so they failed beyond MAX_PATH, and a UNC share root
lacked the trailing backslash these APIs require.

- Wrap every Win32 path in a NUL-terminated `WidePath` that rejects
  interior NULs; volume roots returned by `GetVolumePathNameW` keep
  their terminator.
- Convert inputs to the extended-length `\\?\` / `\\?\UNC\` form after
  `GetFullPathNameW` normalization, and give directory queries a
  trailing backslash.
- Query `GetDiskFreeSpaceW` and `GetVolumeInformationW` on the volume
  root as documented; size buffers to the documented MAX_PATH + 1 or
  the input length.
- Report Win32 error codes instead of raw HRESULTs so `ErrorKind`
  matches `std::fs`.
- Keep volume ids in their conventional `C:\` / `\\server\share\` form.

Add Windows-only `os::` tests for long directories, UNC share roots and
non-empty fstype, and run them in the Windows filesystem workflow.

* fix(utils): report Windows network volumes to the fs guard

Windows names the server's filesystem (usually NTFS) for SMB shares, so
the startup unsupported-filesystem guard, which matches fstype names
such as cifs or smb2, never fired for UNC paths or mapped drives.

- Add `DiskInfo::remote`. On Windows it is set when the directory handle
  answers `FileRemoteProtocolInfo` (only network redirectors do, and the
  handle follows symlinks and junctions to their target) or the volume
  root's `GetDriveTypeW` is `DRIVE_REMOTE`. Unix keeps it false because
  the client fstype already names network mounts.
- Treat remote volumes as unsupported in the startup guard and label
  them "<fstype> on network share".
- Cover UNC shares, paths beyond MAX_PATH below a share, a directory
  symlink to a share and a `net use` mapped drive with Windows-only
  `os::` tests that skip when `\\localhost\<drive>$` is unreachable;
  the Windows filesystem workflow maps the drive before running them.

* chore(deps): replace yanked inout 0.2.3 with 0.2.4

cargo-deny's advisories check rejects the yanked inout 0.2.3 that main's lockfile pins, failing every open PR. Lockfile-only patch bump.

* fix(utils): detect network targets from the final NT path

On the Windows runner, a directory symlink to \\localhost\C$ was reported as local: FileRemoteProtocolInfo did not identify it, and the volume root of the link is a fixed drive.

Also treat a handle whose final NT path (GetFinalPathNameByHandleW with VOLUME_NAME_NT) lies under \Device\Mup\ as remote; the Multiple UNC Provider fronts every UNC and mapped-drive target. The tests now assert the handle probe on its own for local and share paths and report both signals when it disagrees.

---------

Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-10-17 19:10:09 +08:00
唐小鸭 276cb24e26 fix(ecstore): share cache and lock identity across Windows key aliases (#8635)
* fix(ecstore): share metadata cache identity across Windows key aliases

Windows disks treat `\` as a path separator and `path_join_buf` drops the
empty segments that leaves, so `a\b`, `a\\b`, `\a\b` and `a\b\` all
address the xl.meta of `a/b`. The GetObject metadata cache keyed and
hashed the raw spelling, so DELETE `a\b` retired nothing cached for
`a/b`: GET/HEAD `a/b` kept returning the deleted object for up to the
2s cache TTL.

Fold the object name into the same segment form the disk resolves to
before computing the cache hash, key and lookup key. Off Windows the
name stays verbatim and borrowed, so `\` remains an ordinary key byte.

* fix(ecstore): serialize Windows backslash key aliases on one namespace lock

Windows disks treat `\` as a path separator and drop the empty segments
it leaves, so `a\b`, `a\\b`, `\a\b` and `a\b\` all address the xl.meta
of `a/b`. Namespace locks keyed the raw spelling, so writes, deletes and
heals through different spellings ran concurrently on one xl.meta and a
read-modify-write in rename_data could drop a version.

Fold the object name into its disk identity (rustfs_utils::path::
object_name_disk_identity, a no-op off Windows) wherever a namespace
lock key is formed:
- SetDisks::new_ns_lock, which every object lock path goes through;
- the batch delete lock request and its dedup/lookups, which build
  ObjectKeys directly.

Two consumers compared lock identities by spelling and follow along:
- WriteCommitGuard records the folded key from the lock resource, so
  matching a borrowed guard now folds the target name too;
- CopyObject holds the source read guard until EOF on streaming reads.
  `a\b\` -> `a/b` is not a same-object copy by path_join_buf, but shares
  one lock, so it now takes the self-copy lock instead of waiting on
  itself until the lock timeout.

Distributed lock keys are folded by the requesting node only; lock
servers compare them verbatim.

* refactor(ecstore): key the metadata cache by the shared disk identity

The metadata cache fix carried its own copy of the Windows backslash
folding rule. Use rustfs_utils::path::object_name_disk_identity, which
namespace locks already use, so cache and lock identities cannot drift.

* ci(windows): run filesystem tests when key identity rules change

The Windows backslash folding for object names lives in utils path.rs
and is consumed by the namespace lock and write commit guard; run the
windows_ tests when any of them changes.
2026-10-17 18:06:29 +08:00
cxymds 508caab281 fix(lifecycle): skip empty delete replication tasks (#8628)
* fix(lifecycle): skip empty delete replication tasks

* test(lifecycle): route replication hooks through the sink

* test(e2e): make upgrade write readiness checks deterministic

* fix(deps): replace yanked inout 0.2.3 with 0.2.4
2026-10-17 17:15:37 +08:00
唐小鸭 776cdc517b fix(ecstore): keep Windows disk roots out of verbatim form (#8633)
* fix(ecstore): keep Windows disk roots out of verbatim form

The disk root comes from GetFinalPathNameByHandleW, which returns a
verbatim path. `simplified` only strips `\\?\C:` below MAX_PATH, so a
data directory past 260 characters (or one reached through a UNC share)
kept the prefix. Disk IO extends the root by joining `/`-separated
relative paths as strings (`read_raw` builds `<object>/xl.meta` with
`path_join`), and inside a verbatim path `/` is not a separator: the
first metadata read, pool.bin.identity, failed with ERROR_INVALID_NAME
until startup gave up.

Add `rustfs_utils::non_verbatim`, which strips the disk and UNC verbatim
prefixes without a length limit while still keeping paths whose
components Win32 would rewrite, and use it for the publication root.
std re-adds the extended-length prefix for long paths.

Cover a root past MAX_PATH with a disk-level read/write test and run all
`dunce::` tests in the Windows filesystem workflow.

* fix(ecstore): query Win32 volume APIs with extended long disk roots

A disk root past the legacy limit used to reach `rustfs_utils::os` in
verbatim form, which the Win32 volume APIs accept. Now that the root
stays non-verbatim, GetDiskFreeSpaceExW rejected it with
ERROR_PATH_NOT_FOUND and LocalDisk::new failed in the Windows filesystem
workflow. Pass long roots to `get_info` and `get_physical_device_ids` in
extended-length form; shorter roots stay unchanged so reported volume
identities keep their `C:\` form.
2026-10-17 17:04:21 +08:00
cxymds 8dd399cd58 fix(lifecycle): compact inline versions once per deletion batch (#8634) 2026-10-17 16:16:30 +08:00
唐小鸭 353e67bbf3 fix(ecstore): resolve Windows drive paths in URL endpoints (#8629)
On Windows, URL endpoints such as http://host:9000/C:/data{1...4} (the only
form multi-node and pool-expansion layouts accept) produced the disk path
`/C:/data1`: `try_from` stripped the synthetic slash, but `Url::set_path`
put it back, and `get_file_path` only removed it for `file` endpoints.
Windows resolves `/C:/data1` as an invalid `C:` component under the current
drive, so disk validation and `resolve_local_disk_root` failed.

- `get_file_path` now restores `/X:/...` to `X:/...` for every scheme on
  Windows; URL paths without a drive (`/export1`, `/C:foo`) are unchanged.
- UNC shares (`\\nas\share\d1`, `//nas/share/d1`, `\\?\UNC\...`) are rejected
  with an explicit error instead of becoming a file URL whose host is the
  share server and whose path drops the share name.
- A drive root (`http://host:9000/D:/`) is accepted in URL form, matching
  the path-style `D:\` endpoint that was already accepted.
- The "distinct physical disks" error explains on Windows that directories
  on one volume share a device identity, alongside the existing
  RUSTFS_UNSAFE_BYPASS_DISK_CHECK hint.
- The Windows filesystem lane now runs on layout changes and runs the
  layout::endpoint:: tests; new Windows tests carry the windows_ prefix.
- Document Windows endpoint forms, the same-volume check, and the SNSD
  expansion limit in cluster-lifecycle-operations.md.
2026-10-17 16:06:37 +08:00
唐小鸭 78ea655d87 fix(startup): drive startup on an explicitly sized thread (#8630)
`run_process` called `block_on(async_main())` on the native main thread,
so the whole startup chain ran on whatever stack the platform hands the
main thread: 8 MiB from `ulimit -s` on Linux and macOS, but the 1 MiB PE
default on Windows. A Windows debug build overflowed before it listened.
`RUST_MIN_STACK` does not reach the native main thread.

Run the runtime on a `rustfs-main` thread with an explicit 8 MiB stack,
keeping the budget Linux and macOS already had and making it independent
of the linker, toolchain (msvc/gnu) and `ulimit`. Panics are re-raised
on the main thread so the exit status is unchanged.

Measured on macOS arm64 by lowering the main-thread stack with
`ulimit -s` before this change: debug startup overflows at 1792 KiB and
reaches /health/ready at 2048 KiB; release overflows at 512 KiB and is
ready at 768 KiB. After it, debug starts with a 256 KiB main thread.

Add a Windows `Startup Smoke` job that builds the debug server, runs
`--version`, and waits for /health/ready on a single volume.
2026-10-17 16:06:19 +08:00
唐小鸭 5efae5baa6 fix(ecstore): unify object key admission for Windows hosts (#8632)
* fix(ecstore): admit CopyObject destinations like PutObject

Object-key platform checks were split across call sites with gaps:
CopyObject only ran check_bucket_and_object_names on its destination and
then reached the pools' put_object directly, so it admitted keys PutObject
rejects (over 1024 bytes, leading slash, and on Windows reserved characters
or unaddressable segments, the "writes but cannot be read" case of #3449).

- rustfs-utils: expose check_windows_file_name as the single per-segment
  Win32 rule set; dunce's simplified() reuses it. Reserved device names now
  include COM/LPT with superscript digits and CONIN$/CONOUT$.
- ecstore: replace the duplicated reserved-name table with that function;
  Windows also rejects control characters 0x01-0x1F (NUL keeps its
  platform-independent ObjectNameInvalid answer).
- ecstore: add check_new_object_args as the shared admission entry for
  PutObject, cross-key CopyObject destinations and CreateMultipartUpload.
  In-place copies (metadata replace, restore) keep the copy-only check so
  existing keys stay rewritable. CreateMultipartUpload now also rejects
  over-NAME_MAX segments up front instead of failing at completion.
- CI: run bucket::utils and dunce tests in the Windows filesystem job.

* fix(ecstore): reject new backslash keys on Windows hosts

A Windows disk treats '\' as a path separator, so a new key `a\b` lands on
the same xl.meta as `a/b` and the two keys overwrite each other.

Reject '\' only where a key enters the namespace (check_new_object_args:
PutObject, cross-key CopyObject destinations, CreateMultipartUpload) and
only on Windows. Reads, deletes, in-flight multipart operations, in-place
copies, data movement and internal meta buckets keep accepting it, so keys
already stored with '\' stay readable, deletable and movable.

Pending owner ruling: whether new '\' keys should be rejected at all, and
whether reads/deletes should keep accepting them.

* ci(windows): quote filtered test runs and drop duplicate path
2026-10-17 15:53:16 +08:00
Henry Guo 8110ab2893 fix(admin): preserve dirty usage over observed snapshots (#8627)
fix(data-usage): retain dirty counts over observations

Preserve unreconciled local mutations when an observed snapshot completes later. Add focused selection-to-overlay regressions for writes, deletes and authority boundaries.

Validation checkpoint: the pre-fix regression failed as expected. Post-fix tests, formatting and runtime verification were not run before the user stopped the task. This local commit is not ready for publication.
2026-10-17 15:10:11 +08:00
Ramakrishna Chilaka 59ea8bf8ce fix(s3): return 412 for conditional multipart uploads on existing keys (#8625) 2026-10-17 14:09:04 +08:00
Henry Guo 749a1686aa feat(table-catalog): plan maintenance on durable backing (#8567)
* feat(table-catalog): plan maintenance on durable backing

* test(table-catalog): bootstrap strong planning handler fixture
2026-10-17 12:06:02 +08:00
Chris 6db3158740 fix: resolve clippy warnings from Rust 1.99 lints (#8619)
- Combine identical if-block arms in runtime_sources.rs (clippy::if_same_then_else)
- Allow redundant_locals for cfg-gated closure captures in metadata_read.rs and read.rs
- Remove needless borrow in upgrade_compatibility_test.rs (clippy::needless_borrows_for_generic_args)
2026-10-17 11:46:33 +08:00
Henry Guo c07a5d292c fix(connect): retain bounded object cleanup diagnostics (#8624) 2026-10-17 10:57:39 +08:00
Chris 00d1f6ead2 chore(release): align installation references for 1.0.2 (#8617) 2026-10-16 23:19:34 +08:00
Henry Guo adfe60ec7d fix(connect): prepare profile fixtures before timed IPC (#8616) 2026-10-16 23:16:19 +08:00
Henry Guo a5c6f6195e fix(scanner): retain partial progress without a usage update (#8612) 2026-10-16 21:05:33 +08:00
Ramakrishna Chilaka ec0208480e fix(ecstore): publish delete rollback files via verbatim-safe rename (#8607) 2026-10-16 20:04:44 +08:00
唐小鸭 62b6b6149c fix(ecstore): publish delete rollback backups past Windows MAX_PATH (#8606)
tempfile's Windows `persist` passes the raw paths to MoveFileExW and
SetFileAttributesW, bypassing std's extended-length handling. Once
`<object>/<rollback>/xl.meta.bkp` reached 260 UTF-16 units every delete
of that key failed with 500, while PUT and GET kept working.

Add `rustfs_utils::extended_length_path`, which normalizes a path with
GetFullPathNameW and prefixes it with `\\?\` (or `\\?\UNC\`), and use it
for the staging directory and destination handed to tempfile. Cover the
single and batch delete paths with a deep-key regression test and run the
helper's Windows assertions in the Windows filesystem workflow.

Fixes #8545
2026-10-16 16:16:32 +08:00
唐小鸭 741de6f81c fix(replication): locate encrypted replicas without comparing ETags (#8602)
Since 1.0.2 an encrypted object's ETag is a keyed fingerprint, not the
plaintext MD5. A target that computes its own ETag (AWS S3, Wasabi) never
reports it, so every "find the existing replica by exact ETag" step missed
encrypted objects on targets that mint their own version ids:

- a heal / MRF re-drive after a lost PUT response wrote a second version;
- an existing-object resync re-PUT a replica its ledger already named
  (the ledger-located HEAD was confirmed by ETag only), and on the AWS
  400 shape the ledger was not consulted at all;
- resync verification counted such replicas as failed;
- a ledger-less version purge reported the replica as already gone.

Encrypted sources now recognise their replica by shape: the byte count the
replication PUT sent (plaintext for managed SSE, ciphertext for SSE-C) and
a target write time no earlier than the source version. An ETag match
still wins (RustFS/MinIO targets). The ledger is consulted first on both
the 404 and the AWS 400 shape. Without a ledger, only a unique shape match
among the unclaimed ListObjectVersions candidates is adopted and recorded;
several shape matches mean re-PUT for convergence and a refused purge. The
AWS current-version fallback stays ETag-only, and a listing the
target refuses on the 400/403 shape keeps that fallback (re-PUT) rather
than stalling. An unversioned target's `null` version is never matched by
shape: it holds whichever generation was written last. Plaintext sources keep exact
ETag identity.

Builds on the ETag-computing fake target mode and the ComputesOwnEtags
matrix row from rustfs#8583. Adds an SSE-S3 variant of the mint-own-ids
re-drive test (red before this change: the heal left two target versions),
and both variants now also assert the purge.
2026-10-16 13:44:33 +08:00
Chris f5ff8783f4 fix(ecstore): box large enum variant in CurrentDeleteGeneration (#8576) 2026-10-16 13:11:49 +08:00
cxymds 12ae143bee fix(quota): keep writes available with a degraded fleet (#8604) 2026-10-16 13:05:20 +08:00
cxymds 628663f468 fix(heal): recover replacements after pool removal (#8600)
* fix(heal): recover replacements after pool removal

* test(e2e): gate upgrade probes on writable baseline nodes

* test(e2e): register baseline readiness regression in smoke digest
2026-10-16 12:08:29 +08:00
唐小鸭 d457378c02 feat(scanner): decouple bucket walkers from disk count (#8539)
* fix(scanner): treat replication status writebacks as status-class hits

A replication status writeback (persist_replication_state_if_current in
Update mode, including the heals the scanner queues during its own walk)
marked the bucket dirty on the namespace path: it advanced the dirty-usage
generation, woke the scanner, reached the durable dirty journal and
entered the activity snapshot. Any replicated bucket therefore made every
scanner cycle Superseded, the authoritative usage baseline was never
certified and usage freshness stayed stale for as long as replication ran
(#8375).

The writeback is now status class: record_status_class_mutation records
only the process-local producer coverage bit and nothing else. There is
no rescan queue for it, because a deployment with an active replication
rule already walks every bucket on every cycle
(ScannerMaintenanceFeatures::needs_regular_cycle) and that walk refreshes
replication_stats. The routing lives in a named, unit-tested function in
rustfs/src/storage/storage_api.rs; tier-expiration cleanup removes data
and stays on the namespace path.

No persisted format, protocol, admin API or configuration changes.

This is phase 1 of docs/architecture/scanner-per-bucket-convergence.md,
which this change adds together with its index entry.

Refs #8375, #8494, rustfs/backlog#2816.

* fix(scanner): refresh replication statistics during maintenance

Refresh compacted prefixes and completed bucket caches during replication maintenance. Bind set execution reuse to the observed replication statistics so a same-cycle retry cannot restore older counters.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* test(scanner): avoid expanding the legacy S3 DTO surface

Decode the existing replication configuration type from a JSON fixture instead of adding direct S3 DTO references to a new file. Preserve the compacted-prefix and same-cycle assertions and the footprint ratchet.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* perf(scanner): keep raw enumeration bookkeeping linear per entry

The page writer inserted into its building page with a push-and-sort on
every entry and committed each page through a clone. Insert in sorted
position and move the committed page into the index instead.

Add a regression test that records 20,000 entries in one prefix and pins
the cost to one hash per entry and well under a second, so the quadratic
re-normalization removed by #8114 (rustfs#8113) cannot return once pacing
builds on this walker.

* feat(scanner): decouple bucket walkers from disk count

Add the scanner key walkers_per_disk (RUSTFS_SCANNER_WALKERS_PER_DISK,
1 through 16, default 1). Each local coordinator disk of a set spawns that
many bucket walkers; remote disks keep one. The walker count is what
max_concurrent_disk_scans caps and the disk count stays the floor.

The walkers of one disk share its scanner admission through
LocalDiskAdmissionShare: the first walker to start a bucket takes the
per-disk permit and the last to finish releases it, so a peer's remote
request still sees one scan session per physical disk. Without the share
the second walker was rejected as busy and retired.

The disk_concurrency_budget log line reports walkers_per_disk, walkers and
the reason for the effective limit. The default stays 1 because no local
disk reports rotational media today; the design keeps a higher flash
default as the intent.

* docs(scanner): document walkers_per_disk in the Helm chart and configuration manuals

RUSTFS_SCANNER_WALKERS_PER_DISK was only described in the scanner runtime
controls table. Add it to the Helm chart (values, configmap rendering,
README table, template test), the configuration crate's environment
reference with operator guidance, and a short section in the operations
manual on when to raise it and how to confirm it took effect.

---------

Co-authored-by: Hauser <housemecn@gmail.com>
Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-16 11:15:45 +08:00
cxymds e76cb67e93 fix(lifecycle): read exact object versions after writes (#8601)
* fix(lifecycle): read exact object versions after writes

* test(lifecycle): respect ECStore dependency boundaries
2026-10-16 11:09:55 +08:00
Hauser 8c8c74cbc3 fix(ecstore): unify bounded delete recovery across upgrades (#8594)
* fix(ecstore): complete delete recovery across upgrades and restarts

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* test(ecstore): expect typed quorum error after delete rollback

Assert the exact InsufficientWriteQuorum bucket and object instead of the removed Io wrapper, retaining the rollback and cleanup ownership checks.

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-16 11:09:50 +08:00
Hauser 9d85d21ee6 chore(deps): refresh workspace dependencies (#8597)
* chore(deps): refresh workspace dependencies

Run cargo update and cargo upgrade twice from origin/main.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(deps): pin Pulsar 6.9.0 until recovery is bounded

Keep Pulsar on 6.9.0 after reproducing background recovery and lookup activity after delivery timeout and target destruction in 6.9.1. Use an exact requirement to prevent a later cargo update from selecting the affected release.

Co-Authored-By: heihutu <heihutu@gmail.com>
Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-16 09:54:58 +08:00
Hauser 4e9b3818eb test(s3-select): fix schema reference field ordering (#8599)
Enable preserve_order for the Arrow JSON schema reference in tests and verify first-seen field ordering across nested records.

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-10-16 09:12:37 +08:00