Skip to content

#1738: Fixed SymLink loop#1756

Open
tmukh wants to merge 4 commits intodevonfw:mainfrom
tmukh:1738_FileAccessFix
Open

#1738: Fixed SymLink loop#1756
tmukh wants to merge 4 commits intodevonfw:mainfrom
tmukh:1738_FileAccessFix

Conversation

@tmukh
Copy link
Contributor

@tmukh tmukh commented Mar 17, 2026

This PR fixes #1738

Implemented changes:

  • Updated recursive deletion in IDEasy/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java to stop following links:
  • Added an early guard in deleteRecursive(Path path) for Files.isSymbolicLink(path) || isJunction(path), then delete link and return. * This prevents recursion into link targets (including self-referencing links/junctions)
  • Changed directory detection in deleteRecursive(Path path) to use Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) so only real directories are traversed.
  • Kept Windows junction handling via isJunction(path) so mklink /j cases are covered and no infinite recursion occurs
  • Added test coverage for self-referencing link/junction deletion (no infinite loop, delete completes successfully).

Checklist for this PR

Make sure everything is checked before merging this PR. For further info please also see
our DoD.

  • When running mvn clean test locally all tests pass and build is successful
  • PR title is of the form #«issue-id»: «brief summary» (e.g. #921: fixed setup.bat). If no issue ID exists, title only.
  • PR top-level comment summarizes what has been done and contains link to addressed issue(s)
  • PR and issue(s) have suitable labels
  • Issue is set to In Progress and assigned to you or there is no issue (might happen for very small PRs)
  • You followed all coding conventions
  • You have added the issue implemented by your PR in CHANGELOG.adoc unless issue is labeled
    with internal

@github-project-automation github-project-automation bot moved this to 🆕 New in IDEasy board Mar 17, 2026
@tmukh tmukh added the bugfix PR that fixes a bug issue label Mar 17, 2026
@tmukh tmukh self-assigned this Mar 17, 2026
@tmukh tmukh requested a review from hohwille March 17, 2026 09:12
@tmukh tmukh moved this from 🆕 New to 👀 In review in IDEasy board Mar 17, 2026
@coveralls
Copy link
Collaborator

coveralls commented Mar 17, 2026

Pull Request Test Coverage Report for Build 23194065055

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 181 unchanged lines in 1 file lost coverage.
  • Overall coverage decreased (-0.3%) to 70.34%

Files with Coverage Reduction New Missed Lines %
com/devonfw/tools/ide/io/FileAccessImpl.java 181 63.18%
Totals Coverage Status
Change from base Build 23194041997: -0.3%
Covered Lines: 10727
Relevant Lines: 14644

💛 - Coveralls

@hohwille hohwille added this to the release:2026.04.001 milestone Mar 17, 2026
@hohwille hohwille changed the title 1738: Fixed SymLink loop #1738: Fixed SymLink loop Mar 17, 2026
Copy link
Member

@hohwille hohwille left a comment

Choose a reason for hiding this comment

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

@tmukh thank you for your great PR 👍
You not only fixed #1738 but also two other things regarding link creation.
I left some comments for further improvements....

Comment on lines +1053 to +1054
if (isDirectoryLink(path)) {
deletePath(path);
Copy link
Member

Choose a reason for hiding this comment

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

Fully correct to fix the issue.
Just as a question: Since you correctly also added the LinkOption.NOFOLLOW_LINKS to the isDirectory check in deleteRecursive why do we need this if anymore at all?
We could now always just call deleteRecursive for simplification...

LOG.trace("Creating a Windows {} at {} pointing to {}", type, link, source);
ProcessContext pc = this.context.newProcess().executable("cmd").addArgs("/c", "mklink", type.getMklinkOption());
Path finalSource = source;
Path absoluteSource = source.isAbsolute() ? source : link.getParent().resolve(source).normalize();
Copy link
Member

Choose a reason for hiding this comment

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

nice. This seems exactly the fix we need to resolve foo/../bar properly.
Great job 👍
But shouldn't we already do that in link method before already?
Also for real (sym)links without mklink we should not link to foo/../bar but normalize to bar then...

Comment on lines +503 to +514
if (type == PathLinkType.SYMBOLIC_LINK) {
boolean directoryTarget = Files.isDirectory(absoluteSource, LinkOption.NOFOLLOW_LINKS);
if (directoryTarget && runMklink(finalSource, finalLink, cwd, "/j")) {
return;
}
if (runMklink(finalSource, finalLink, cwd, "/d")) {
return;
}
throw new IllegalStateException("Failed to create Windows link at " + link + " pointing to " + source);
}

if (!runMklink(finalSource, finalLink, cwd, type.getMklinkOption())) {
Copy link
Member

Choose a reason for hiding this comment

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

Also an excellent improvement (actually a fix not related to #1738).
Small suggestion to simplify the code:

Suggested change
if (type == PathLinkType.SYMBOLIC_LINK) {
boolean directoryTarget = Files.isDirectory(absoluteSource, LinkOption.NOFOLLOW_LINKS);
if (directoryTarget && runMklink(finalSource, finalLink, cwd, "/j")) {
return;
}
if (runMklink(finalSource, finalLink, cwd, "/d")) {
return;
}
throw new IllegalStateException("Failed to create Windows link at " + link + " pointing to " + source);
}
if (!runMklink(finalSource, finalLink, cwd, type.getMklinkOption())) {
String options = type.getMklinkOption();
if (type == PathLinkType.SYMBOLIC_LINK) {
boolean directoryTarget = Files.isDirectory(absoluteSource, LinkOption.NOFOLLOW_LINKS);
if (directoryTarget) {
options = "/j";
} else {
options = "/d";
}
}
if (!runMklink(finalSource, finalLink, cwd, type.getMklinkOption())) {

I do not expect that mklink /j ... on a directory fails but then mklink /d ... will succeed. That both variants will be tried in case the first failed is for me rather confusing.
BTW: Do we really want LinkOption.NOFOLLOW_LINKS for targetDirectory?
What if we create a link to a link linking to a directory...?

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

Labels

bugfix PR that fixes a bug issue

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

FileAccess.delete follows links

3 participants