Repository navigation
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses a Windows-specific download failure where applying “read-only” ACLs to the temporary download file prevents the subsequent rename/replace operation, causing persistent Error 5 (Access Denied) retry loops for read-only shares (including Talk attachments). It does so by moving the read-only permission application to after the final rename succeeds.
Changes:
- Move
setFileReadOnly*()calls from the temporary file (_tmpFile.fileName()) to the final destination path (filename) afterFileSystem::uncheckedRenameReplace()succeeds. - Ensure the final file is un-hidden (
setFileHidden(filename, false)) before applying the read-only/read-write decision.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (_item->_locked == SyncFileItem::LockStatus::LockedItem && (_item->_lockOwnerType != SyncFileItem::LockOwnerType::UserLock || _item->_lockOwnerId != propagator()->account()->davUser())) { | ||
| qCDebug(lcPropagateDownload()) << filename << "file is locked: making it read only"; | ||
| FileSystem::setFileReadOnly(filename, true); | ||
| } else { | ||
| qCDebug(lcPropagateDownload()) << filename << "file is not locked: making it" | ||
| << ((!_item->_remotePerm.isNull() && !_item->_remotePerm.hasPermission(RemotePermissions::CanWrite)) ? "read only" | ||
| : "read write"); | ||
| FileSystem::setFileReadOnlyWeak(filename, (!_item->_remotePerm.isNull() && !_item->_remotePerm.hasPermission(RemotePermissions::CanWrite))); | ||
| } |
There was a problem hiding this comment.
This condition was moved as-is, so the divergence from updateMetadata() is pre-existing. And updateMetadata() runs right after on the same path with the full UserLock/TokenLock check, so it overwrites this block anyway. Behavior is unchanged. Aligning the checks is a fine cleanup but out of scope here.
| if (_item->_locked == SyncFileItem::LockStatus::LockedItem && (_item->_lockOwnerType != SyncFileItem::LockOwnerType::UserLock || _item->_lockOwnerId != propagator()->account()->davUser())) { | ||
| qCDebug(lcPropagateDownload()) << filename << "file is locked: making it read only"; | ||
| FileSystem::setFileReadOnly(filename, true); | ||
| } else { | ||
| qCDebug(lcPropagateDownload()) << filename << "file is not locked: making it" | ||
| << ((!_item->_remotePerm.isNull() && !_item->_remotePerm.hasPermission(RemotePermissions::CanWrite)) ? "read only" | ||
| : "read write"); | ||
| FileSystem::setFileReadOnlyWeak(filename, (!_item->_remotePerm.isNull() && !_item->_remotePerm.hasPermission(RemotePermissions::CanWrite))); | ||
| } |
There was a problem hiding this comment.
Real behavioral change, good catch. That said, a case-clash conflict file is a local artifact the user has to resolve manually, so dropping read-only may actually be preferable. @ maintainer is read-only intended on these files? If so, the fix belongs in createCaseClashConflict() after its rename, not in downloadFinished().
|
I checked Copilot suggestions, I think it might need some maintainer input though. |
|
Also I have checked the sync code paths and in theory all cases should handle an existing read-only temp file, so this bug should be "self healing" without any additional changes. |
|
It has been two weeks with no comment from anyone. This is ready for review, can anyone take a look at this please @mgallien |
|
Hello there, 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.) |
|
@Lenart12 I am sorry for the delay |
There was a problem hiding this comment.
@Lenart12 sorry again for the delay
since you are modifying syncing behavior, please add some automated tests changes to validate that
let us know if you have questions on that
automated tests changes are usually required for this part of the code base
|
@Lenart12 gentle reminder |
|
Thanks for reminding, I plan to, but not for a few weeks as im away on vacation. |
|
Is there anyone, who can finish this PR? Current Nextcloud Desktop Client is simply unusable for me with read-only-shares. |
|
@AndyFx12 I found a workaround that works for me: #9885 (comment) But I'd also appreciate the proper fix being merged. Happy to help out if there's a lack of dev capacity :) |
|
I think this PR is extremely important and critical, and it should be reviewed as soon as possible. |
Temporary solution for those facing the problemSince I'm dealing with this problem, I found a solution. Perform the standard folder synchronization; you will still encounter file access errors. Once all the files in the folder have been synchronized (even with the errors), run the following commands in Windows PowerShell: OBS: Run the commands below with synchronization paused.After execution, you can re-enable synchronization, and NextCloud will recognize that everything is correct. |
Regarding issue #9885 and the permissions, I tried running Nextcloud as an administrator, and the problems went away. |
|
Independent confirmation that this fix addresses the root cause. Reproduced deterministically on Windows 11 25H2 (Nextcloud Desktop 34.0.4, build 20260916 — the current release, non-VFS) syncing a view-only shared folder, and the same failure applies to regular read-only shares, not only Talk downloads (#9885). Symptom: when a new or changed file appears in the read-only share, the client downloads it to Root cause (matches this PR): Removing that single deny ACE lets the rename succeed immediately. Applying the read-only ACL to the final path after the rename, as this PR does in Client debug log (
Reproduction:
This looks like the same root cause behind #9488 (files never populating a read-only share on Windows), so it may be worth linking there as well. As a side note on the "run as administrator" workaround mentioned above: it only works because an elevated token is not subject to the |
|
@Lenart12 |
|
Added automated tests, which reproduced the issue before the fix and then passed after the fix. More info in commit messages. |
|
@mgallien |
|
Can someone take a look at this please :) It has been a week. |
Add sync tests validating the behavior changed by the read-only permission reorder in PropagateDownloadFile::downloadFinished(): - a read-only shared file downloads and ends up read-only locally - a remote change to an already-read-only local file re-downloads and overwrites it (the temp-file -> rename-over-read-only-destination path that regressed on Windows with Error 5) - a file regaining write permission on the server becomes writable locally again Signed-off-by: Lenart Kos <koslenart@gmail.com>
…ud#9885) The existing read-only download tests pass even with the read-only permissions applied to the temporary file before the rename: the test folder grants "Full control", and FILE_DELETE_CHILD on the parent folder lets Windows rename the temporary file despite its deny-DELETE ACE. Restrict the sync folder to "Modify" permissions for the current user, which does not include FILE_DELETE_CHILD, so the rename depends on the DELETE access of the temporary file itself. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Lenart Kos <koslenart@gmail.com>
setFileReadOnly() on Windows adds an ACCESS_DENIED_ACE for BUILTIN\Users to the file's DACL. Applying this to the temp file before renaming it to its final destination blocks the rename, because Windows requires Delete access on the source path to complete the operation. This caused a persistent Error 5 (Access Denied) retry loop for read-only shares and Talk attachments on Windows. Move the permission block to after uncheckedRenameReplace() succeeds, targeting the final filename instead of _tmpFile.fileName(). Fixes: nextcloud#9885 Signed-off-by: Lenart Kos <koslenart@gmail.com>
…ud#9885) Clients that applied read-only permissions to the temporary download file left it behind fully downloaded after the rename into place failed. The download info in the journal still references it, so the next sync reuses it instead of downloading the file again. Recreate that state in a sync folder without FILE_DELETE_CHILD and check that the next sync renames the temporary file into place. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Lenart Kos <koslenart@gmail.com>
Clients that made the temporary download file read-only before renaming it could leave it behind fully downloaded when the rename failed on Windows. The next sync reuses it and skips straight to renaming it, but the read-only permissions were only cleared on the path that reopens the file to resume the download, so the rename kept failing. Clear them as soon as an existing temporary file is reused. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Lenart Kos <koslenart@gmail.com>
|
Rebased for latest master, still ready for review :) |
setFileReadOnly() on Windows adds an ACCESS_DENIED_ACE for BUILTIN\Users
to the file's DACL. Applying this to the temp file before renaming it to
its final destination blocks the rename, because Windows requires Delete
access on the source path to complete the operation. This caused a
persistent Error 5 (Access Denied) retry loop for read-only shares and
Talk attachments on Windows.
Move the permission block to after uncheckedRenameReplace() succeeds,
targeting the final filename instead of _tmpFile.fileName().
Fixes: #9885