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': Already 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.

Verified on Linux against GNU 9.10: -D messages match over 23 invocations covering symlinks, dangling symlinks, plain files, symlink loops, unreadable, read-only and write-only parents, trailing and doubled slashes. 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:

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
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.
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 17.84%

⚡ 1 improved benchmark
❌ 2 regressed benchmarks
✅ 390 untouched benchmarks
⏩ 54 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ Simulation three_39_bit_primes 346.9 ms 600.7 ms -42.25%
❌ Simulation five_38_bit_primes 1.8 s 2 s -10.24%
⚡ Simulation thirteen_39_bit_primes 9.4 s 8.8 s +6.96%

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 (5867bbe) with main (cacdd8d)

Open in CodSpeed

Footnotes

  1. 54 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.

`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.
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
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 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 lite review requested due to automatic review settings October 1, 2026 06:52

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.

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