mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-10-18 23:07:27 +02:00
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
This commit is contained in:
1 parent
21ebe1f531
commit
24e6451b11
2 files changed
+100
-88
No files matched your search
@@ -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.
|
||||
@@ -1237,79 +1237,95 @@ impl Update {
|
||||
return Err(Error::InvalidUpdaterFormat);
|
||||
}
|
||||
|
||||
let tmp_dir_locations = vec![
|
||||
Box::new(|| Some(std::env::temp_dir())) as Box<dyn FnOnce() -> Option<PathBuf>>,
|
||||
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<Box<dyn FnOnce() -> Option<PathBuf> + '_>> {
|
||||
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<dyn FnOnce() -> Option<PathBuf>>,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user