From 24e6451b11fbd3378eccf4824552053d8862c66b Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Sun, 11 Oct 2026 16:14:50 -0300 Subject: [PATCH] fix(updater): fall back to the next temp location for AppImage updates (#3693) - `tempdir_in(..)?` and `metadata()?` returned early on the first location that failed (e.g. a non-writable `/tmp`), so the cache dir and the directory next to the AppImage were never tried. Failures now move on to the next location, as the deb/rpm path already did. - The candidate list was duplicated between the AppImage and deb/rpm paths; both now use `tmp_dir_locations`, which also no longer unwraps the parent of the app path (`extract_path.parent().unwrap()`). Compile-checked with clippy in a Linux container (default features). plugins/updater/src/updater.rs --- .changes/updater-appimage-tmp-fallback.md | 6 + plugins/updater/src/updater.rs | 182 +++++++++++----------- 2 files changed, 100 insertions(+), 88 deletions(-) create mode 100644 .changes/updater-appimage-tmp-fallback.md diff --git a/.changes/updater-appimage-tmp-fallback.md b/.changes/updater-appimage-tmp-fallback.md new file mode 100644 index 000000000..be5cbcadd --- /dev/null +++ b/.changes/updater-appimage-tmp-fallback.md @@ -0,0 +1,6 @@ +--- +updater: patch +updater-js: patch +--- + +On Linux, try the next temporary directory location (the user cache directory, then the directory holding the AppImage) when a temporary directory cannot be created in the previous one, instead of failing the AppImage update right away. Also avoids a panic when the app path has no parent directory. diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index bf427acc2..ddb47fc5e 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -1237,79 +1237,95 @@ impl Update { return Err(Error::InvalidUpdaterFormat); } - let tmp_dir_locations = vec![ - Box::new(|| Some(std::env::temp_dir())) as Box Option>, - Box::new(dirs::cache_dir), - Box::new(|| Some(self.extract_path.parent().unwrap().to_path_buf())), - ]; - - for tmp_dir_location in tmp_dir_locations { - if let Some(tmp_dir_location) = tmp_dir_location() { - let tmp_dir = tempfile::Builder::new() - .prefix("tauri_current_app") - .tempdir_in(tmp_dir_location)?; - let tmp_dir_metadata = tmp_dir.path().metadata()?; - - if extract_path_metadata.dev() == tmp_dir_metadata.dev() { - let mut perms = tmp_dir_metadata.permissions(); - perms.set_mode(0o700); - std::fs::set_permissions(tmp_dir.path(), perms)?; - - let tmp_app_image = &tmp_dir.path().join("current_app.AppImage"); - - let permissions = std::fs::metadata(&self.extract_path)?.permissions(); - - // create a backup of our current app image - match std::fs::rename(&self.extract_path, tmp_app_image) { - Ok(()) => {} - Err(err) if err.kind() == std::io::ErrorKind::CrossesDevices => continue, - Err(err) => return Err(err.into()), - } - - #[cfg(feature = "zip")] - if is_gz { - log::debug!("extracting AppImage"); - // extract the buffer to the tmp_dir - // we extract our signed archive into our final directory without any temp file - let archive = std::io::Cursor::new(bytes); - let decoder = flate2::read::GzDecoder::new(archive); - let mut archive = tar::Archive::new(decoder); - for mut entry in archive.entries()?.flatten() { - if let Ok(path) = entry.path() - && path.extension() == Some(OsStr::new("AppImage")) - { - // if something went wrong during the extraction, we should restore previous app - if let Err(err) = entry.unpack(&self.extract_path) { - std::fs::rename(tmp_app_image, &self.extract_path)?; - return Err(err.into()); - } - // early finish we have everything we need here - return Ok(()); - } - } - // if we have not returned early we should restore the backup - std::fs::rename(tmp_app_image, &self.extract_path)?; - return Err(Error::BinaryNotFoundInArchive); - } - - log::debug!("rewriting AppImage"); - return match std::fs::write(&self.extract_path, bytes) - .and_then(|_| std::fs::set_permissions(&self.extract_path, permissions)) - { - Err(err) => { - // if something went wrong during the extraction, we should restore previous app - std::fs::rename(tmp_app_image, &self.extract_path)?; - Err(err.into()) - } - Ok(_) => Ok(()), - }; + for tmp_dir_location in self.tmp_dir_locations() { + let Some(tmp_dir_location) = tmp_dir_location() else { + continue; + }; + // a location that cannot be used moves on to the next one + let tmp_dir = match tempfile::Builder::new() + .prefix("tauri_current_app") + .tempdir_in(&tmp_dir_location) + { + Ok(tmp_dir) => tmp_dir, + Err(err) => { + log::debug!( + "failed to create a temporary directory in {}: {err}", + tmp_dir_location.display() + ); + continue; } + }; + if let Ok(tmp_dir_metadata) = tmp_dir.path().metadata() + && extract_path_metadata.dev() == tmp_dir_metadata.dev() + { + let mut perms = tmp_dir_metadata.permissions(); + perms.set_mode(0o700); + std::fs::set_permissions(tmp_dir.path(), perms)?; + + let tmp_app_image = &tmp_dir.path().join("current_app.AppImage"); + + let permissions = std::fs::metadata(&self.extract_path)?.permissions(); + + // create a backup of our current app image + match std::fs::rename(&self.extract_path, tmp_app_image) { + Ok(()) => {} + Err(err) if err.kind() == std::io::ErrorKind::CrossesDevices => continue, + Err(err) => return Err(err.into()), + } + + #[cfg(feature = "zip")] + if is_gz { + log::debug!("extracting AppImage"); + // extract the buffer to the tmp_dir + // we extract our signed archive into our final directory without any temp file + let archive = std::io::Cursor::new(bytes); + let decoder = flate2::read::GzDecoder::new(archive); + let mut archive = tar::Archive::new(decoder); + for mut entry in archive.entries()?.flatten() { + if let Ok(path) = entry.path() + && path.extension() == Some(OsStr::new("AppImage")) + { + // if something went wrong during the extraction, we should restore previous app + if let Err(err) = entry.unpack(&self.extract_path) { + std::fs::rename(tmp_app_image, &self.extract_path)?; + return Err(err.into()); + } + // early finish we have everything we need here + return Ok(()); + } + } + // if we have not returned early we should restore the backup + std::fs::rename(tmp_app_image, &self.extract_path)?; + return Err(Error::BinaryNotFoundInArchive); + } + + log::debug!("rewriting AppImage"); + return match std::fs::write(&self.extract_path, bytes) + .and_then(|_| std::fs::set_permissions(&self.extract_path, permissions)) + { + Err(err) => { + // if something went wrong during the extraction, we should restore previous app + std::fs::rename(tmp_app_image, &self.extract_path)?; + Err(err.into()) + } + Ok(_) => Ok(()), + }; } } Err(Error::TempDirNotOnSameMountPoint) } + /// Directories to try, in order, for the temporary files of an install: the system temporary + /// directory, the user cache directory and the directory holding the app. + fn tmp_dir_locations(&self) -> Vec Option + '_>> { + vec![ + Box::new(|| Some(std::env::temp_dir())), + Box::new(dirs::cache_dir), + Box::new(|| self.extract_path.parent().map(Path::to_path_buf)), + ] + } + fn install_deb(&self, bytes: &[u8]) -> Result<()> { // First verify the bytes are actually a .deb package if !infer::archive::is_deb(bytes) { @@ -1335,31 +1351,21 @@ impl Update { install_arg: &str, package_extension: &str, ) -> Result<()> { - // Try different temp directories - let tmp_dir_locations = vec![ - Box::new(|| Some(std::env::temp_dir())) as Box Option>, - Box::new(dirs::cache_dir), - Box::new(|| Some(self.extract_path.parent().unwrap().to_path_buf())), - ]; - // Try writing to multiple temp locations until one succeeds - for tmp_dir_location in tmp_dir_locations { - if let Some(path) = tmp_dir_location() { - let prefix = format!("tauri_{package_extension}_update"); - if let Ok(tmp_dir) = tempfile::Builder::new().prefix(&prefix).tempdir_in(path) { - let pkg_path = tmp_dir.path().join(format!("package.{package_extension}")); + for tmp_dir_location in self.tmp_dir_locations() { + let Some(path) = tmp_dir_location() else { + continue; + }; + let prefix = format!("tauri_{package_extension}_update"); + if let Ok(tmp_dir) = tempfile::Builder::new().prefix(&prefix).tempdir_in(path) { + let pkg_path = tmp_dir.path().join(format!("package.{package_extension}")); - // Try writing the .deb / .rpm file - if std::fs::write(&pkg_path, bytes).is_ok() { - // If write succeeds, proceed with installation - return self.try_install_with_privileges( - &pkg_path, - install_cmd, - install_arg, - ); - } - // If write fails, continue to next temp location + // Try writing the .deb / .rpm file + if std::fs::write(&pkg_path, bytes).is_ok() { + // If write succeeds, proceed with installation + return self.try_install_with_privileges(&pkg_path, install_cmd, install_arg); } + // If write fails, continue to next temp location } }