Skip to content

install: name the directory component that could not be created - #14772

Open
abendrothj wants to merge 7 commits into
uutils:mainfrom
abendrothj:fix/install-create-dir-error-component
Open

abendrothj wants to merge 7 commits into
uutils:mainfrom
abendrothj:fix/install-create-dir-error-component

Conversation

@abendrothj

@abendrothj abendrothj commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

install named the whole leading-directory path instead of the component that failed, and with -D gave no reason at all:

install -D f dangling/sub/f   cannot create directory 'dangling/sub'
install -d dangling/sub       cannot create directory 'dangling/sub': File exists
GNU, both cases               cannot create directory 'dangling': File exists

create_dir_all_safe now returns the failing prefix along with its error, and the ancestor search walks up past files, symlink loops and unreadable directories instead of giving up with the whole path. A name that exists but can't be descended into reports EEXIST when it doesn't resolve and ENOTDIR when it resolves to a non-directory, which is what GNU prints. When the parent of the failing component can't be searched at all, that parent is named instead - the difference between r--/x, which GNU reports as r--, and r-x/x, which it reports as r-x/x. The message string gained a place for the errno.

install -d keeps creating directories path-based. I tried routing it through the same fd walk and it broke write-only directories: DirFd::open needs read permission, mkdir only needs write and execute, so install -d wx-dir/sub started failing where GNU succeeds. There's a regression test for that now. -D has had the same limitation since it moved to the fd walk, which is #14778; this PR does not change it.

Checked on Linux against GNU 9.12 built from source: -D messages match in 19 of 24 cases covering symlinks, dangling symlinks, plain files, symlink loops, unreadable, read-only and write-only parents, and doubled slashes. The five that differ also differ on main and are not changed here: trailing-slash targets (-D f regular/sub/, -D f dangling/sub/), an unreadable existing parent (-D f r--/f), and two -D -t cases covered by #14999. No GNU source was read.

Closes #14995

Copilot AI lite review requested due to automatic review settings September 21, 2026 06:23

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@abendrothj

Copy link
Copy Markdown
Contributor Author

Conflicts with #12715 in the two -d hunks. Whichever lands second resolves to with_umask(0, || create_dir_all_safe(&path_to_create, DEFAULT_MODE)) with show!(InstallError::CreateDirFailed(e.path, e.error)).

@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

GNU testsuite comparison:

GNU test failed: tests/id/setgid. tests/id/setgid is passing on 'main'. Maybe you have to rebase?
Skipping an intermittent issue tests/date/resolution (passes in this run but fails in the 'main' branch)

Copilot AI review requested due to automatic review settings September 21, 2026 07:44

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings September 21, 2026 08:32

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI lite review requested due to automatic review settings October 1, 2026 01:16
@abendrothj
abendrothj force-pushed the fix/install-create-dir-error-component branch from e0c8f96 to f088cbd Compare October 1, 2026 01:16

Copilot AI 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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 1 High severity · 3 Medium severity

Open (4)

Comment thread src/uucore/src/lib/features/safe_traversal.rs
Comment thread src/uucore/src/lib/features/safe_traversal.rs
Comment thread src/uucore/src/lib/features/safe_traversal.rs
Comment thread src/uucore/src/lib/features/safe_traversal.rs Outdated
Copilot AI lite review requested due to automatic review settings October 1, 2026 02:02
@codspeed

codspeed Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 2.29%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 8 improved benchmarks
❌ 16 regressed benchmarks
✅ 222 untouched benchmarks
⏩ 207 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ Simulation split_lines 8.5 ms 11.1 ms -23.5%
❌ Simulation split_numeric_suffix 8.7 ms 11.4 ms -23.08%
❌ Simulation false_consecutive_calls 294.7 ns 350.2 ns -15.86%
❌ Simulation check_sorted_utf8_locale 422.9 ms 492.2 ms -14.09%
❌ Simulation sort_numeric_utf8_locale 36.1 ms 40.7 ms -11.41%
❌ Simulation merge_single_file_utf8_locale 119.3 ms 133.4 ms -10.59%
❌ Simulation tsort_complex_dag[50000] 87.6 ms 97.1 ms -9.79%
❌ Simulation tsort_tree_dag[(10, 3)] 36.5 ms 39.4 ms -7.33%
❌ Simulation tsort_wide_dag[100000] 157.6 ms 167 ms -5.65%
❌ Simulation merge_pre_sorted_files 238.7 ms 252.6 ms -5.53%
❌ Simulation merge_pre_sorted_files_utf8_locale 237.9 ms 251.8 ms -5.53%
❌ Simulation tsort_linear_chain[1000000] 1.9 s 2 s -4.75%
❌ Simulation five_38_bit_primes 1.5 s 1.6 s -4.43%
❌ Simulation sort_german_de_locale 282.1 ms 295 ms -4.4%
❌ Simulation sort_case_sensitive[500000] 321 ms 334.9 ms -4.14%
❌ Simulation sort_spill_to_tmp_files_utf8_locale 321 ms 332.1 ms -3.34%
⚡ Simulation cut_fields_custom_delim 65.9 ms 51.9 ms +27%
⚡ Simulation cut_fields_tab 57.4 ms 45.5 ms +26.23%
⚡ Simulation three_39_bit_primes 901.6 ms 730.4 ms +23.44%
⚡ Simulation cut_bytes 18.1 ms 15.5 ms +16.69%
... ... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing abendrothj:fix/install-create-dir-error-component (e24d398) with main (fe26d56)

Open in CodSpeed

Footnotes

  1. 207 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. ↩

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

install -d still reports the full path for nested failures, and permission-sensitive naming cases lack regression coverage.

Review effort: Lite
Findings: 1 High severity · 2 Medium severity

Open (3)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Report the failing path component for nested mkdir errors

src/​uu/​install/​src/​install.rs:517

install -d still passes the entire path_to_create as the error path, so a nested failure such as install -d dangling/sub (or regular/sub where regular is a file) will continue to report dangling/sub/regular/sub instead of the failing component. Keep the path-based mkdir behavior for write-only parents, but track the component that failed (or add a path-based helper that returns it) before constructing CreateDirFailed.

// check runs on the trimmed path so that a trailing slash, which
// makes `exists()` fail with ENOTDIR, is handled the same way.
if b.target_dir.is_some() && to_create.exists() && !to_create.is_dir() {
return Err(InstallError::NotADirectory(to_create_original.to_path_buf()).into());

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.

Wouldn't that cause a TOCTOU?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It only decides which error gets printed. If the target is an existing non-directory, install stops with "Not a directory"; otherwise nothing is done with the result. If the path changes after the check, the directories are still created through create_dir_all_safe, which works through directory descriptors (mkdirat, openat with O_DIRECTORY) and refuses a file or dangling symlink at that name by itself. So a race can only change which message you get, to "cannot create directory". The check isn't new either: main already does the same exists()/is_dir() check on the -t target; this moves it after the trailing-slash trim.

Copilot AI lite review requested due to automatic review settings October 1, 2026 04:04

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

A critical -t target-validation regression and a moderate safe-traversal issue remain unresolved.

Review effort: Lite
Findings: 2 High severity · 2 Medium severity

Open (4)
Previously missed (1)

In code that hasn't changed since last review

Low severity Add integration coverage for unsearchable parent error paths

src/​uucore/​src/​lib/​features/​safe_traversal.rs:630

The new blame branch is what implements the documented r--/x versus r-x/x distinction, but the added tests only cover a regular file, a dangling symlink, and the successful -d write-only case. Add an integration test for an unsearchable parent (and ideally the loop/read-only error cases described by the PR) so this error-path naming behavior cannot regress.

Comment thread src/uu/install/src/install.rs
Copilot AI lite review requested due to automatic review settings October 1, 2026 04:39

Copilot AI 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.

Comment thread tests/by-util/test_install.rs

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

An outstanding documentation nit and mixed readiness signals warrant human review.

Review effort: Lite
Findings: 1 High severity

Open (1)
Resolved since last review (4)
Previously missed (1)

In code that hasn't changed since last review

Low severity Remove GNU test path reference from PR description

tests/​by-util/​test_install.rs:2891

The PR description references the GNU test path tests/install/basic-1. Please remove that path reference and summarize the observed behavior instead; this repository must not include references to GNU source/test paths.

@abendrothj

Copy link
Copy Markdown
Contributor Author

Rechecked against GNU 9.12 built from the release tarball (9.10 behaves the same): this branch matches GNU in 19 of 24 -D cases. The five that differ are trailing-slash targets (-D f regular/sub/, -D f dangling/sub/), an unreadable existing parent (-D f r--/f), and two -D -t cases that #14999 covers. None of them match on main either. I've corrected the description.

@abendrothj

Copy link
Copy Markdown
Contributor Author

Correction: with the current #12715 this is one hunk in directory(), and with_umask must not be nested there, since install already runs under it. Keep #12715's DirBuilder with DEFAULT_MODE and take this PR's error: show!(InstallError::CreateDirFailed(failed_create_dir_prefix(&path_to_create), e)), keeping the #[cfg(unix)]/#[cfg(not(unix))] split for failed.

Nothing converts a CreateDirError into an io::Error, and doing so would
silently drop the path the type exists to carry. Also document why the
access(2) call in blame() is sound.
`install -d` creates its directories by path, so it still reported the
whole path when that failed, e.g. 'dangling/sub' where GNU names
'dangling'. Name the first component that is not an existing directory,
through the same blame logic create_dir_all_safe uses for -D.
The parent-versus-component choice in blame() had no test: cover a parent
that cannot be searched (named) against one that can (the component is
named), and a symlink loop in the leading directories, for -D and -d.
Copilot AI lite review requested due to automatic review settings October 7, 2026 11:28
@abendrothj
abendrothj force-pushed the fix/install-create-dir-error-component branch from 5867bbe to e24d398 Compare October 7, 2026 11:28

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

install: error names the whole leading path instead of the failing component

3 participants