Skip to content

fix(storage): unlink dir symlinks without deleting targets - #53447

Open
fxprunayre wants to merge 3 commits into
nextcloud:masterfrom
fxprunayre:fix/symlink-removal
Open

fix(storage): unlink dir symlinks without deleting targets#53447
fxprunayre wants to merge 3 commits into
nextcloud:masterfrom
fxprunayre:fix/symlink-removal

Conversation

@fxprunayre

@fxprunayre fxprunayre commented Jun 12, 2025

Copy link
Copy Markdown

Summary

Nextcloud supports following symbolic links when localstorage.allowsymlinks is enabled. However, deleting a symbolic link to a directory recursively deleted the contents of the target directory before failing to remove the link itself.

This change detects when the path passed to local storage directory removal is a symbolic link and unlinks it directly, preserving the target directory and its contents.

Before

Given:

.
├── afolder
│   └── test.txt
├── alink -> afolder
└── welcome.txt

Created with:

mkdir afolder
touch afolder/test.txt
ln -s afolder alink

Deleting alink recursively removed afolder/test.txt and then reported an error:

{
  "method": "DELETE",
  "url": "/remote.php/dav/files/admin/alink",
  "message": "rmdir(/data/dev/nextcloud/data/admin/files/alink): Not a directory"
}

The resulting structure was:

.
├── afolder
└── welcome.txt

Screenshot from 2025-06-12 08-29-22

After

Deleting alink now removes only the symbolic link. The link target and its contents are preserved:

.
├── afolder
│   └── test.txt
└── welcome.txt

Screenshot from 2025-06-12 08-32-20

Implementation

  • Resolve the local source path once.
  • Clear cached filesystem metadata before checking the path type.
  • Detect a top-level symbolic link before constructing the recursive directory iterator.
  • Unlink symbolic links directly instead of traversing their targets.
  • Preserve the existing recursive removal behavior for real directories.
  • Simplify the control flow in Local::rmdir().

Added parameterized regression coverage in LocalTest.php for deletion through both storage entry points:

  • Local::rmdir()
  • Local::unlink()

The tests verify that:

  • The symbolic link is removed.
  • The target directory remains.
  • Files inside the target remain.
  • The target file contents are unchanged.
  • Environments that cannot create symbolic links skip the test.

Todo

Quite new to the Nextcloud codebase, any pointer to a test related to symlink is welcomed.

Checklist

@fxprunayre
fxprunayre requested a review from a team as a code owner June 12, 2025 07:14
@fxprunayre
fxprunayre requested review from ArtificialOwl, sorbaugh and yemkareems and removed request for a team June 12, 2025 07:14
@fxprunayre
fxprunayre force-pushed the fix/symlink-removal branch from 36ca73a to 9fb276c Compare June 12, 2025 09:41
@github-actions

Copy link
Copy Markdown
Contributor

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

@susnux susnux added the community pull requests from community label Jun 9, 2026
@joshtrichards joshtrichards added feature: filesystem hotspot: filename handling Filenames - invalid, portable, blacklisting, etc. bug 3. to review Waiting for reviews labels Aug 24, 2026
Nextcloud allow following symlink (https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/config_sample_php_parameters.html#localstorage-allowsymlinks) but removal of symlink remove files in the target of the symlink and fail to remove the link returning an error.

Using the following structure:

```
.
├── afolder
│   └── test.txt
├── alink -> afolder
└── welcome.txt
```

created with:

```bash
mkdir afolder
touch afolder/test.txt
ln -s afolder alink
```

After deletion of `alink` symbolic link, the content of `afolder` is removed, the link also and an error is reported.

```json
{
  "method":"DELETE",
  "url":"/remote.php/dav/files/admin/alink",
  "message":"rmdir(/data/dev/nextcloud/data/admin/files/alink): Not a directory at /data/dev/nextcloud/lib/private/Files/Storage/Local.php#128"
}
```

Results is:
```
.
├── afolder
└── welcome.txt
```

If the resource to be deleted is a link, unlink it (and preserve link target content).

```
.
├── afolder
│   └── test.txt
└── welcome.txt
```

Signed-off-by: Francois Prunayre <fx.prunayre@gmail.com>
Resolve the source path once, clear the stat cache before checking whether it is a symlink, and use an early return to avoid nesting the recursive directory removal logic.

Signed-off-by: Josh <josh.t.richards@gmail.com>
Verify that deleting a directory symlink through rmdir or unlink removes only the link while preserving the target directory and its contents.

Assisted-by: Copilot:gpt-5.6-sol

Signed-off-by: Josh <josh.t.richards@gmail.com>
@joshtrichards joshtrichards changed the title fix(storage): Unlink symlink instead of deleting target content and link fix(storage): unlink dir symlinks without deleting targets Aug 26, 2026
@joshtrichards
joshtrichards requested review from Altahrim and icewind1991 and removed request for yemkareems August 26, 2026 02:45

@joshtrichards joshtrichards left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Makes sense. Sorry about taking so long to review!

I took the liberty of:

  • moving the clearstatcache() before the is_link() call
  • refactoring slightly for clarity
  • adding unit tests

@joshtrichards joshtrichards added this to the Nextcloud 36 milestone Aug 26, 2026
@joshtrichards

Copy link
Copy Markdown
Member

/backport to stable35

@joshtrichards

Copy link
Copy Markdown
Member

/backport to stable34

1 similar comment
@joshtrichards

Copy link
Copy Markdown
Member

/backport to stable34

@joshtrichards

Copy link
Copy Markdown
Member

/backport to stable33

@joshtrichards

Copy link
Copy Markdown
Member

/backport to stable32

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

Labels

3. to review Waiting for reviews AI assisted backport-request bug community pull requests from community feature: filesystem feedback-requested hotspot: filename handling Filenames - invalid, portable, blacklisting, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants