update: sync bssh-russh-sftp with upstream russh-sftp 2.4.0 - #272
Conversation
Brings in every upstream change released since the fork's 2.3.0 base. Upstream `src/` is identical between 2.4.0 and current master, so this covers all of it. The behavior fix is `FileAttributes::default()`: it used to return *dummy* attributes (every field `Some(0)`) while `empty()` returned omitted ones, which made `Default` the opposite of what a caller would assume. Upstream now derives `Default` as the omitted form, makes `empty()` an alias for it, and moves the zero-filled placeholder to an explicit `FileAttributes::dummy()`, which `protocol::File::new` now calls so its wire output is unchanged. bssh is unaffected: `src/server/sftp.rs` builds every `FileAttributes` and `protocol::File` with fully specified struct literals and never uses `Default`, verified by inspection and by an SFTP round trip against both servers. Also vendored: `File::close()` plus documentation that dropping a handle closes it without awaiting the reply, and client support for the `expand-path@openssh.com` extension (`ExpandPathExtension`, `features.expand_path`, `RawSftpSession::expand_path`, `SftpSession::expand_path`). Fixes a fork maintenance hazard found while doing the sync. `src/server/mod.rs` carried the request read-ahead intake queue and sequential-write coalescing from issue #227, roughly 750 changed lines, with no patch file. Since `sync-upstream.sh` deletes `src/**/*.rs` before copying upstream over it and then re-applies only `patches/*.patch`, the next sync would have silently deleted that work; `create-patch.sh` would not have noticed because it only ever diffed `client/fs/file.rs`. That change is now captured in `patches/server-readahead-write-coalescing.patch`, `create-patch.sh` regenerates a patch per entry in a `PATCH_TARGETS` list and warns about any other vendored file that drifts from upstream without an entry, and the README documents both the second patch and the deletion hazard. Both patch files were verified to apply cleanly onto a pristine upstream 2.4.0 checkout, and `create-patch.sh` reproduces both byte-identically. Validation: `cargo check --workspace --all-targets`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, and `cargo fmt --all -- --check` clean. Full workspace test suite green (bssh lib 1407 passed, bssh-russh-sftp 8 passed) except the three `integration_test` cases that CI already skips and that fail on a stale localhost entry in the developer's `~/.ssh/known_hosts`. End-to-end SFTP re-verified after the sync: round trip against bssh-server, and 300 KB plus 3 MB round trips against OpenSSH 10.3p1, all with matching SHA-256. Refs #227
Running `./sync-upstream.sh 2.4.0` for real, instead of trusting it, surfaced two defects that between them made the documented sync workflow destructive. Version resolution never worked. russh-sftp publishes no git tags at all, so `git checkout v2.4.0 || git checkout 2.4.0` failed both ways and `set -e` aborted the script; the command in the README could not have run. Upstream marks releases with a `bump to <version>` commit, so both scripts now resolve a version argument to that commit. An unresolvable version is a hard error that lists the available release commits rather than a silent fall back to the default branch, which would vendor unreleased code while stamping Cargo.toml with the requested version. Resolution runs before anything is copied, so a bad version leaves the tree untouched. Patch-state detection was inverted-safe in name only. Apple's bundled `patch` (2.0-12u11) silently auto-corrects direction: with no tty it answers "yes" to `Unreversed (or previously applied) patch detected! Ignore -R?` and exits 0 for a forward patch, a reverse patch, an applied patch and an unapplied one alike. The obsolete-detection probe was `patch -p1 -R --dry-run`, so it succeeded unconditionally and every fork patch was classified "already present in upstream" and skipped. The observable result: a sync wiped both fork changes and the fork's test count silently dropped from 8 to 0, because the tests live inside the patched files. Detection now uses `git apply --check`, which never prompts and distinguishes the three real states (applies, already present, conflicts). The post-sync guard added alongside the 2.4.0 sync was subject to the same flaw, since it skipped anything the broken probe had marked obsolete and used the same `patch -R` probe itself. It now reverse-checks with `git apply` too, and the script additionally runs the fork tests, so a lost change fails the sync instead of passing quietly. Verified end to end rather than by inspection: `./sync-upstream.sh 2.4.0` now applies both patches, reports both present, passes 8 fork tests, and leaves `git diff` on the vendored tree completely empty, so the script reproduces the committed source byte for byte. `./create-patch.sh 2.4.0` resolves to the same commit and regenerates both patches byte-identically. Both scripts exit 1 on an unresolvable version without modifying the tree. Refs #227
|
Pushed b87089d. While checking that this PR left the fork in a maintainable state for the next sync, I ran Version resolution never worked. russh-sftp publishes no git tags at all, so Patch-state detection was broken in a way that silently deleted the fork. Apple's bundled Both fork changes were gone from the working tree, and the fork's test count had dropped from 8 to 0 without failing anything, because the tests live inside the patched files. Detection now uses This also means the post-sync guard added in 840fb64 was useless: it skipped whatever the broken probe had marked obsolete, and used the same Verified by running it, not by reading it: |
Summary
Syncs the vendored
bssh-russh-sftpfork with upstream russh-sftp 2.4.0, bringing in every upstream change released since the fork's 2.3.0 base. Upstreamsrc/is identical between 2.4.0 and current master, so this covers all of it.Upstream changes vendored
The behavior fix:
FileAttributes::default()used to return dummy attributes (every fieldSome(0)) whileempty()returned omitted ones, makingDefaultthe opposite of what a caller would assume (upstream #89). Upstream now derivesDefaultas the omitted form, makesempty()an alias for it, and moves the zero-filled placeholder to an explicitFileAttributes::dummy(), whichprotocol::File::newnow calls so its wire output is unchanged.bssh is unaffected:
src/server/sftp.rsbuilds everyFileAttributesandprotocol::Filewith fully specified struct literals and never relies onDefault(no..Default::default()anywhere in that file). Verified by inspection and by SFTP round trips against both servers.Also vendored:
File::close(), plus documentation that dropping a handle closes it without awaiting the reply (so pending write errors and the close status are discarded).expand-path@openssh.comextension:ExpandPathExtension,features.expand_path,RawSftpSession::expand_path,SftpSession::expand_path.Fork maintenance hazard fixed
Found while doing the sync, and worth flagging on its own.
src/server/mod.rscarried the request read-ahead intake queue and sequential-write coalescing from #227, roughly 750 changed lines, with no patch file.sync-upstream.shdeletessrc/**/*.rsbefore copying upstream over it and then re-applies onlypatches/*.patch, so the next sync would have silently deleted that work.create-patch.shwould not have caught it either, because it only ever diffedclient/fs/file.rs.Fixed three ways:
patches/server-readahead-write-coalescing.patch.create-patch.shnow regenerates one patch per entry in aPATCH_TARGETSlist, and warns about any other vendored file that drifts from upstream without an entry.Both patch files were verified to apply cleanly onto a pristine upstream 2.4.0 checkout, and
create-patch.shreproduces both byte-identically.Merge method
Rather than run the destructive
sync-upstream.sh, the five files with no local drift were copied verbatim from upstream 2.4.0 and the upstreamclient/fs/file.rsdelta was applied as a patch onto the forked copy. The result is upstream 2.4.0 plus exactly two additive fork changes, with no upstream line lost:diffagainst pristine 2.4.0 shows only the fork's own hunks.Validation
cargo check --workspace --all-targets,cargo clippy --workspace --all-targets --all-features -- -D warnings,cargo fmt --all -- --check: clean.--no-fail-fast: bssh lib 1407 passed,bssh-russh-sftp8 passed, all other binaries green. The only failures are the threeintegration_testcases that CI already skips, which fail on a stale localhost entry in the developer's~/.ssh/known_hostsand reproduce onmain.bssh-server, plus 300 KB and 3 MB round trips against OpenSSH 10.3p1, all with matching SHA-256. The 3 MB case exercises both pipelined helpers across many chunks.Refs #227