Skip to content

df: replace ASCII control characters with '?' in name cells (#15817) - #16065

Open
huuan26 wants to merge 1 commit into
uutils:mainfrom
huuan26:fix/df-replace-control-chars-15817
Open

huuan26 wants to merge 1 commit into
uutils:mainfrom
huuan26:fix/df-replace-control-chars-15817

Conversation

@huuan26

@huuan26 huuan26 commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #15817

GNU df prints ? for control characters in file and mount names, whereas uutils printed them verbatim, breaking table formatting on newlines or transmitting escape sequences.

Changes

  1. src/uu/df/src/table.rs:

    • Added Cell::from_bytes(&[u8]) which maps ASCII control characters (�.is_ascii_control()) to �'?' and computes column width from the resulting byte slice.
    • Routed Cell::from_string and Cell::from_os_string through Cell::from_bytes.
    • Added unit test est_table_control_chars_are_replaced and table formatting test est_table_mount_control_chars_in_output verifying replacement for embedded newlines, tabs, ANSI escape sequences, and OsString inputs.
  2. src/uucore/src/lib/features/fsext/mod.rs:

    • Updated
      eplace_special_chars to decode standard octal sequences (\ooo, including \012 for \n and \011 for \t, matching kernel escaping in mountinfo/mtab and gnulib's unescape_tab).
    • Decoded dev_name (mount source) from mountinfo/mtab, ensuring control characters in device/source names reach Cell decoded to be replaced by ?.
    • Properly decoding \012 in mount_dir also prevents mounts with newlines from failing statfs and disappearing from the output.
    • Added unit tests for octal unescaping and mount parsing with special characters.
  3. ** ests/by-util/test_df.rs**:

    • Added integration test est_file_column_replaces_control_chars covering control character replacement in --output=file.

Verification

  • cargo test -p uu_df: 46 passed, 0 failed.
  • cargo test -p uucore --features fsext --lib test_replace_special_chars_octal: passed.
  • cargo test --features df --test tests test_df::: 57 passed, 0 failed.
  • cargo clippy -p uu_df -p uucore --features fsext --all-targets -- -D warnings: passed with 0 errors.
  • cargo fmt --check: passed.

@fixfon

fixfon commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

On 1af3184 the mount point gets the ? treatment, but two names never reach Cell with the control byte decoded. I mounted tmpfs inside unshare -rm and compared with GNU 9.12:

mountinfo: /tmp/dft/n\012l s\012x
           /tmp/dft/t\011t src\011tab

$ df --output=source,target | grep dft
this PR:  src\011tab     /tmp/dft/t?t
GNU 9.12: s?x            /tmp/dft/n?l
          src?tab        /tmp/dft/t?t

The source field keeps the \011 escape from mountinfo, so it prints as text instead of ?. A mount whose path has a newline is missing from the list entirely (same on the base commit), so the hello\nworld case from the new unit test never shows up in real output.

@sylvestre
sylvestre force-pushed the fix/df-replace-control-chars-15817 branch from 1af3184 to 5636f4b Compare November 3, 2026 07:18
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

GNU test failed: tests/du/block-size. tests/du/block-size is passing on 'main'. Maybe you have to rebase?

@huuan26
huuan26 force-pushed the fix/df-replace-control-chars-15817 branch from 5636f4b to d1af5f0 Compare November 4, 2026 02:24
@huuan26

huuan26 commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @fixfon for the catch and detailed reproducer!

I've updated the PR and rebased onto upstream/main (which also resolves the unrelated ests/du/block-size failure):

  1. Unescape mount source and \012 (newline) in uucore::fsext:

eplace_special_chars now properly unescapes standard 3-digit octal sequences (including \012 for \n and \011 for \t, matching kernel escaping in mountinfo/mtab and gnulib's unescape_tab).

  • Decoded dev_name (mount source) from mountinfo/mtab as well, so control characters in the source reach Cell decoded and get replaced with ? (e.g. src\011tab -> src?tab, s\012x -> s?x).
  • With \012 unescaped in mount_dir, statfs now finds the actual directory on disk so mounts containing newlines in their paths are no longer dropped.
  1. Added Tests:
    • Unit tests in uucore::fsext verifying octal unescaping (\012, \011, \040, \134, and boundaries).
    • Unit test in uu_df::table verifying table layout formatting for mounts with control characters in both source and arget (src?tab /tmp/dft/t?t and s?x /tmp/dft/n?l).
    • Integration test est_file_column_replaces_control_chars in ests/by-util/test_df.rs.

@montezuma-p montezuma-p left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest push fails cargo fmt --check on src/uucore/src/lib/features/fsext/mod.rs (lines 204, 1062 and 1069 per the Style/format job), which is what fails the Style and Lint jobs. Running cargo fmt should clear them.

This overlaps #16102, which also decodes \012 and replaces control characters in the mount point column; this PR is the one that also decodes the source.

@huuan26
huuan26 force-pushed the fix/df-replace-control-chars-15817 branch 2 times, most recently from 10f8aed to 0056b5a Compare November 5, 2026 04:35
@huuan26

huuan26 commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @montezuma-p! I've run cargo fmt and rebased onto the latest main.

@huuan26
huuan26 force-pushed the fix/df-replace-control-chars-15817 branch from 0056b5a to eccd2f3 Compare November 5, 2026 04:50
@huuan26
huuan26 force-pushed the fix/df-replace-control-chars-15817 branch from eccd2f3 to 3ba7b58 Compare November 5, 2026 04:50
@codspeed

codspeed Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning, 1 note

⚠️ 117 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

ℹ️ 1 benchmark has no flamegraph

The flamegraph wasn't generated: most of the time was spent in runtime internals, which would leave nothing useful to analyze.

See affected benchmarks

✅ 401 untouched benchmarks
⏩ 70 skipped benchmarks1


Comparing huuan26:fix/df-replace-control-chars-15817 (3ba7b58) with main (0181732)2

Open in CodSpeed

Footnotes

  1. 70 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

  2. No successful run was found on main (a6e622e) during the generation of this report, so 0181732 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

df: prints raw control characters in file and mount names, unlike GNU

3 participants