Skip to content

fix(metadata): reject --chmod copy-syntax clauses like upstream#6561

Merged
oferchen merged 1 commit into
masterfrom
fix/312-chmod-copy-syntax-reject
Jul 13, 2026
Merged

fix(metadata): reject --chmod copy-syntax clauses like upstream#6561
oferchen merged 1 commit into
masterfrom
fix/312-chmod-copy-syntax-reject

Conversation

@oferchen

Copy link
Copy Markdown
Owner

Summary

rsync's --chmod grammar does not implement chmod(1)'s copy-from-category
form. A category letter (u/g/o) in the permission half of a clause (e.g.
g=u, o=g, u+g) is rejected by upstream. oc-rsync was silently accepting it.

Empirical divergence (upstream rsync 3.4.4 binary)

rsync -a --chmod=g=u src/ dst/:

  • upstream 3.4.4: prints rsync: Invalid argument passed to --chmod (g=u)
    then rsync error: syntax or usage error (code 1) and exits 1.
  • oc (before): exits 0 — accepted the clause and applied a fabricated
    copy-from-category transform.

Root cause

Upstream chmod.c:parse_chmod() STATE_2ND_HALF (chmod.c:159-185) only accepts
r/w/X/x/s/t; every other byte — including u/g/o — hits default: and
sets STATE_ERROR, so parse_chmod() returns NULL. The caller in
options.c:1762-1766 then emits Invalid argument passed to --chmod (%s) and
exit_cleanup(RERR_SYNTAX). Upstream's struct chmod_mode_struct has only
ModeAND, ModeOR, flags — there are no ModeCOPY_* fields.

A prior change added a copybits/ModeCOPY_* copy-from-category path to the
parser and evaluator with fabricated upstream citations. That path over-accepted
specs upstream rejects. This PR removes it and restores upstream's exact
rejection condition.

Changes

  • crates/metadata/src/chmod/parse.rs: STATE_2ND_HALF now matches only
    r/w/X/x/s/t; u/g/o fall through to the error path exactly as upstream.
    Removed the copybits accumulator and the mode_special_bits() helper.
  • crates/metadata/src/chmod/spec.rs: dropped the dead copy_src, copy_dst,
    copy_and, is_sub fields from Clause.
  • crates/metadata/src/chmod/apply.rs: removed the dead mode_copy_bits()
    helper and the copy distribution in tweak_mode().
  • crates/cli/.../metadata/compute.rs: the surfaced diagnostic now matches
    upstream byte-for-byte: Invalid argument passed to --chmod (<spec>).

Rejected vs still accepted

  • Now rejected (matching upstream): g=u, o=g, u+g, a=u, g-o, o=u,
    g=ur, g=us — any category letter in the permission half.
  • Still accepted (legitimate upstream forms, unchanged): octal (750,
    4755), D/F selectors (Dg-w, F+x), symbolic perms (u=rwX, a+rX),
    set-id/sticky (u+s, g+s, +t), and an empty permission half (g=,
    o=, Dg=) which clears the class.

Reconciliation with the earlier copy-spec change

The earlier work conflated two sub-cases. The empty permission half (Dg=,
o=) is a genuine upstream clause (the operator was seen, so it is not an empty
clause) and is preserved. The copy-from-category form (g=u) is not a real
rsync grammar and is now rejected, matching upstream. The X selector, D/F
prefixes, and set-id/sticky handling are untouched.

Dead code removed

Clause::{copy_src, copy_dst, copy_and, is_sub}, parse.rs:mode_special_bits(),
apply.rs:mode_copy_bits(), and the copy branch in tweak_mode().

Tests

  • metadata unit tests updated to assert rejection:
    parse::tests::copy_from_category_forms_rejected,
    chmod::tests::who_letter_copy_forms_are_rejected,
    chmod::comprehensive_tests::...::who_letter_copy_source_in_rhs_rejected.
    Legitimate forms (o=, Dg=, u=rwX, set-id) retain passing assertions.
  • CLI integration test run_reports_invalid_chmod_specification now asserts
    exit 1 and the exact Invalid argument passed to --chmod (<spec>) string for
    a+q, g=u, o=g, u+g.

cargo fmt --all clean, cargo clippy -p metadata -p cli --all-targets --all-features --no-deps -- -D warnings clean, cargo test -p metadata --lib chmod 37 passed.

@github-actions github-actions Bot added the bug Something isn't working label Jul 13, 2026
rsync's --chmod grammar does not implement chmod(1)'s copy-from-category
form. A category letter (u/g/o) in the permission half of a clause (e.g.
g=u, o=g, u+g) is rejected by upstream chmod.c parse_chmod STATE_2ND_HALF
(default: -> STATE_ERROR); the caller in options.c emits
'Invalid argument passed to --chmod (%s)' and exits RERR_SYNTAX. oc was
silently accepting these via a fabricated ModeCOPY path and exiting 0.

Remove the copybits/mode_copy_bits/mode_special_bits copy-from-category
machinery and the dead Clause copy fields, restoring upstream's exact
rejection. Surface the diagnostic byte-for-byte. Legitimate forms (empty
permission half, D/F selectors, X, set-id/sticky, octal) are preserved.
@oferchen oferchen force-pushed the fix/312-chmod-copy-syntax-reject branch from 287df80 to 4993879 Compare July 13, 2026 12:52
@oferchen oferchen merged commit fa1eb1d into master Jul 13, 2026
62 checks passed
@oferchen oferchen deleted the fix/312-chmod-copy-syntax-reject branch July 13, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant