Conversation
On macOS `install_inner` moved the installed app into a `TempDir` backup and then renamed the new bundle into place. Neither of the two steps after that move restored the app on failure, and the `TempDir` deleted the backup on every exit path, so a failed final rename left the user with no app at the install path and no way back. The privileged fallback had the same shape: `rm -rf` the app, then `mv` the new one in. Both moves are now `replace_bundle`, which renames the current app to the backup, renames the staged bundle into place, and renames the backup back if that second step fails. The privileged script moves the current app aside, moves the new one in, and restores on failure; it deletes the previous bundle only after the new one is in place. The temp and install locations are compared with `st_dev` before anything moves, returning the existing `TempDirNotOnSameMountPoint` rather than failing after the app has already been moved, and the staged bundle root is set to 0755 because `tempfile` creates it 0700 and that mode followed it into `/Applications`. Two unit tests cover `replace_bundle`: the staged bundle ends up in place with the previous one in the backup, and a failed second rename leaves the current bundle where it was and returns that error. Closes tauri-apps#3505, closes tauri-apps#3506.
alii13
marked this pull request as ready for review
September 10, 2026 15:27
Even with the restore in place there was a moment between the two renames where the install path held nothing, and a process killed in that moment left the previous app in the temp dir rather than where it belonged. `swap_bundle` calls `renamex_np` with `RENAME_SWAP`, which exchanges the two directories in one step so the install path always holds one of the two. The previous app ends up in the extraction temp dir and is removed with it. Where the file system does not support the swap (`ENOTSUP`, or `EINVAL` on older systems) the two-rename path with the restore is used as before, and a `PermissionDenied` from either still goes to the privileged fallback. `libc` is added for the macOS target only; it was already in the lockfile through tauri. One macOS test checks the exchange and skips on a file system that reports `ENOTSUP`.
Author
|
@Legend-Master can you please review this PR? Thank you! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3505, fixes #3506.
I hit this on a Mac where the update kept "failing" and afterwards the app was simply gone from /Applications. The cause is in the macOS
install_inner: the current .app gets renamed into aTempDirbefore the new bundle is renamed into place, and if that second rename fails the function just returns the error. Nothing puts the old bundle back, and theTempDirdrop deletes it on the way out. The admin fallback has the same problem in shell form, it doesrm -rfon the app first andmvthe new one in second.While in there I also fixed the 0700 bundle root from #3506, since it comes from the same temp dir.
What changed:
renamex_np(RENAME_SWAP)(what Sparkle does), so there is no moment where the install path is empty, not even if the process dies mid-updateENOTSUP), the two renames go throughreplace_bundle(), which renames the backup back if the second rename failsmvfails it moves the old one backst_devof the app and the temp dir before touching anything and return the existingTempDirNotOnSameMountPoint(same as the AppImage path already does), instead of failing after the app has been movedThe only dependency change is
libcfor the macOS target, which was already in the lockfile through tauri.Testing: three unit tests,
swap_bundleexchanging two directories (macOS only, skips on a file system that reportsENOTSUP), andreplace_bundleon the normal path and on the restore when the second rename fails.cargo test,clippy -D warningsandfmt --checkwith the flags CI uses are clean on macOS. Side note, the two doctests inlib.rsalready fail onv2before this change;--all-targetsskips doctests so CI never sees them.I also ran it for real in an app pointed at this branch through
[patch.crates-io], serving a signed update from localhost. A normal update swaps the bundle in a few seconds and it ends up 0755 with nothing left in the temp dir. Killing the app withkill -9two seconds in (mid download) leaves the installed bundle untouched and it updates fine on the next launch; killing it a bit later just lands after the swap. In none of the runs did the install path end up empty, which was the whole problem.