Windows: files with the read-only attribute can never be deleted from a synced library
Summary
On Windows, the client calls DeleteFileW() directly at every worktree deletion
site. DeleteFileW() returns ERROR_ACCESS_DENIED (5) for any file carrying the
FILE_ATTRIBUTE_READONLY attribute, so such a file can never be removed from a
synced library. The deletion fails permanently and the enclosing directory is
moved to seafile-recycle-bin instead.
Neither SetFileAttributesW nor FILE_ATTRIBUTE_READONLY appears anywhere in
this repository, so the attribute is never cleared on any code path.
Environment
- Seafile client 9.0.20 on Windows 11
- Observed continuously over 11 months on the same client
What happens
[08/10/26 06:30:19] Failed to delete file Windows/PrintAgent/build/.git/objects/e6/848e3b...: 5.
[08/10/26 06:30:19] Moved folder D:/Seafile/.../.git/objects/e6 to Seafile recycle bin D:/Seafile/recycle-bin/e6.
[08/10/26 06:30:23] Repo 'Developpements' sync is finished but with error: A folder that may
contain not-yet-uploaded files is moved to seafile-recycle-bin folderCounts from a single client log:
| Log message | Call site | Occurrences |
|---|---|---|
Failed to delete file %s: %lu |
delete_worktree_dir_recursive_win32() |
6676 |
Failed to remove %s: %s |
seaf_util_unlink() |
1233 |
Moved folder ... to Seafile recycle bin |
fallback path | 829 |
Every one of the 6676 is error 5, and every affected path is a read-only file.
Impact
The last log line is the real concern. The fallback moves the whole directory out of the library, and the client itself warns that it may contain files not yet uploaded. For content whose only copy is the library, that is data loss rather than a failed deletion.
Affected call sites
| File | Function |
|---|---|
lib/utils.c |
seaf_util_unlink() |
daemon/repo-mgr.c |
delete_worktree_dir_recursive_win32() |
daemon/vc-utils.c |
remove_hidden_file() |
Why git repositories trigger this reliably
Git creates every loose object and pack file read-only. The mode is hardcoded in
odb_mkstemp() and is not configurable:
/*
* we let the umask do its job, don't try to be more
* restrictive except to remove write permission.
*/
int mode = 0444;core.sharedRepository cannot relax it either, since calc_shared_perm() masks
out every write bit when the file has no owner write bit to begin with.
So any git repository stored in a library is affected. As soon as one client repacks it, the resulting deletions reach every other Windows client and none of them can apply a single one. Over 11 months this hit 7 different repositories on this one client.
Git handles the same case on its side, in compat/mingw.c:
do {
/* read-only files cannot be removed */
_wchmod(wpathname, 0666);
if (try_delete_file(wpathname, use_legacy_delete))
return 0;Steps to reproduce
Nothing git-specific is required:
- In a library synced by two Windows clients A and B, create a file and run
attrib +Ron it from A. - Let both clients sync.
- Delete the file from B, or from the web interface.
- A logs
Failed to delete file <path>: 5and moves the parent directory intoseafile-recycle-bin.
Proposed fix
PR #3065: https://github.com/haiwen/seafile/pull/3065
It adds a win32_delete_file() helper that clears the attribute and retries,
restoring it if the deletion still fails, and preserving GetLastError() so the
existing warnings keep reporting the true cause. The three call sites above are
routed through it.
Source: haiwen/seafile