Avoid unlink/rename when replacing regular files on Android - #245
Conversation
Android's September 2026 security patches [0] changed the MediaProvider behavior so that it revokes app permissions on unlink and rename within its FUSE filesystem, which is used for both internal storage (/sdcard) and external storage (SD cards and USB drives). For rename, permissions for both paths are revoked as it does: revoke destination -> rename -> revoke source. Unfortunately, this means it is now impossible to keep app permissions to files in a shared folder while performing atomic file replacement. This commit works around the problem by performing in-place overwrites when replacing regular files. This essentially forces the use of the fallback path of osutil.RenameOrCopy() when the destination is on a FUSE filesystem on Android. The statfs magic check is sufficient since the only FUSE filesystem openable by path on Android is MediaProvider's. [0] https://android.googlesource.com/platform/packages/providers/MediaProvider/+/91dddac65b6ef48ae54302fa852029c2fcf010aa Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
dd27255 to
ff60304
Compare
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
|
I reported this to upstream Syncthing to see if they have any better ideas for optionally performing in-place file replacements: syncthing/syncthing#10887 |
|
Can this workaround be an option per folder (default off)? |
|
I might consider adding a global option, but I won't be adding a per-folder option because it would introduce an incompatibility in the config file format with the upstream Syncthing project. |
|
And an upstream feature request? I would support that. |
|
Yeah, I already have in syncthing/syncthing#10887 |
|
Please excuse my ignorance but does this problem apply if I've granted |
Yep, the permissions of BasicSync (or any other file sync app) don't matter. With the way Android's new security patch works, if a file is renamed (even temporarily) or deleted as part of the sync process, other apps that previously had access to the file will lose access. |
Thank you for the explanation. I have a question about "the chance of file corruption" due to this workaround, mentioned in your change log for 3.17: I currently have BasicSync set to run only when connected to power. If I update a file on my Mac and I unplug my Android phone while that file is mid-sync (causing BasicSync to stop abruptly), I assume the file ends up corrupted (partially written) on Android. Will BasicSync automatically detect the mismatch and repair the file the next time it runs, or does that require manual intervention? Also, since I don't use external apps (like KeePassDX) that are affected by the permission issue, I'd love to avoid the corruption risk altogether. Would you consider adding a global setting to toggle this workaround off for users who don't need it? Thanks again for your time and for keeping this app running so well! |
Previously/normally, Syncthing overwrites files like this:
The new behavior is:
It's specifically step 3 that could potentially cause an issue, but only if the app were to crash during the middle of it. Normal stopping does not interrupt writes. It would still leave the
Yep, I'll likely add a global toggle for this. |
|
For this important folder, I have added a 14-day TrashCan in File Versioning. |
That's a good idea. And on that topic, BasicSync only avoids renames to work around this issue when overwriting files during a normal sync. For all other operations, like moving files to the |
Thank you so much for the detailed explanation. If the app crashes during step 3, what happens when the app restarts? Will it propagate the partially-written/corrupt file |
No problem! I just gave this a quick test by intentionally making it crash exactly half-way through the writes. I overwrote On the phone:
On the computer:
The temp file does not get propagated. So it looks like the original good data never gets lost. It just annoyingly requires the user to deal with the sync conflict, and the temp file gets left behind on the phone. |
|
I've added a new global toggle in #248. I've decided to revert back to the upstream Syncthing behavior of using safe overwrites by default. Folks who want to prevent permissions from being revoked will need to turn off the new "Overwrite files safely" option. I'll mention this in the changelog too. I'll wait a couple days before releasing a new version so that folks have a chance to translate the new strings. |
Android's September 2026 security patches [0] changed the
MediaProviderbehavior so that it revokes app permissions on unlink and rename within its FUSE filesystem, which is used for both internal storage (/sdcard) and external storage (SD cards and USB drives). For rename, permissions for both paths are revoked as it does: revoke destination -> rename -> revoke source.Unfortunately, this means it is now impossible to keep app permissions to files in a shared folder while performing atomic file replacement.
This commit works around the problem by performing in-place overwrites when replacing regular files. This essentially forces the use of the fallback path of
osutil.RenameOrCopy()when the destination is on a FUSE filesystem on Android. Thestatfsmagic check is sufficient since the only FUSE filesystem openable by path on Android isMediaProvider's.[0] https://android.googlesource.com/platform/packages/providers/MediaProvider/+/91dddac65b6ef48ae54302fa852029c2fcf010aa