From 7ca7ac04cfce8aa467925287cd5bcbda15ef6b98 Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 28 Sep 2026 17:15:41 +0200 Subject: [PATCH] fix: preserve executable bits when extracting Java runtimes (#873) Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com> --- packages/oneclient_java/src/install.rs | 55 +++++++++++++++++++++----- packages/oneclient_java/src/service.rs | 3 ++ packages/polyio/src/archive.rs | 12 +++++- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/packages/oneclient_java/src/install.rs b/packages/oneclient_java/src/install.rs index 5adf9a7e..8f4e35dd 100644 --- a/packages/oneclient_java/src/install.rs +++ b/packages/oneclient_java/src/install.rs @@ -81,15 +81,6 @@ pub async fn install_package( .map_err(|err| polyio::IOError::from(std::io::Error::other(err)))? }; - #[cfg(unix)] - { - let _ = tokio::process::Command::new("chmod") - .arg("755") - .arg(&executable) - .output() - .await; - } - let _ = polyio::remove_file(&archive_path).await; tracing::info!(vendor = %package.vendor, major, "installed Java runtime"); @@ -136,6 +127,52 @@ pub fn is_launcher_managed(executable: &Path) -> bool { managed_install_root(executable).is_ok_and(|root| root.is_some()) } +#[cfg(unix)] +pub(crate) fn restore_executable_bits(executable: &Path) { + use std::os::unix::fs::PermissionsExt; + + let Ok(executable) = polyio::canonicalize(executable) else { + return; + }; + if !is_launcher_managed(&executable) { + return; + } + let Some(home) = executable.parent().and_then(Path::parent) else { + return; + }; + let jre = home.join("jre"); + + let entries = |dir: PathBuf| { + std::fs::read_dir(dir) + .into_iter() + .flatten() + .flatten() + .map(|entry| entry.path()) + }; + let tools = entries(home.join("bin")).chain(entries(jre.join("bin"))); + let helpers = ["jspawnhelper", "jexec"].map(|name| home.join("lib").join(name)); + let jre_helpers = std::iter::once(jre.join("lib")) + .chain(entries(jre.join("lib"))) + .map(|dir| dir.join("jspawnhelper")); + + for path in tools.chain(helpers).chain(jre_helpers) { + let Ok(metadata) = std::fs::metadata(&path) else { + continue; + }; + let mode = metadata.permissions().mode(); + if !metadata.is_file() || mode & 0o111 != 0 { + continue; + } + + match std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode | 0o111)) { + Ok(()) => tracing::info!(path = %path.display(), "restored missing executable bit"), + Err(err) => { + tracing::warn!(path = %path.display(), "could not restore executable bit: {err}"); + } + } + } +} + /// Only ever touches OneClient's own java dir a runtime the user added from /// their own folder is left on disk untouched #[tracing::instrument(level = "debug")] diff --git a/packages/oneclient_java/src/service.rs b/packages/oneclient_java/src/service.rs index 24b854ef..73588cd5 100644 --- a/packages/oneclient_java/src/service.rs +++ b/packages/oneclient_java/src/service.rs @@ -94,6 +94,9 @@ impl JavaService { #[tracing::instrument(level = "debug", skip(self))] async fn revalidate(&self, runtime: JavaRuntime) -> JavaResult> { + #[cfg(unix)] + crate::install::restore_executable_bits(Path::new(&runtime.absolute_path)); + if runtime.probe_version == PROBE_VERSION { if Path::new(&runtime.absolute_path).is_file() { return Ok(Some(runtime)); diff --git a/packages/polyio/src/archive.rs b/packages/polyio/src/archive.rs index 10362c5e..d6eb1311 100644 --- a/packages/polyio/src/archive.rs +++ b/packages/polyio/src/archive.rs @@ -216,6 +216,14 @@ pub async fn extract_zip_filtered( let entry_reader = reader.reader_without_entry(index).await?; futures_lite::io::copy(entry_reader, &mut writer.compat_write()).await?; + + #[cfg(unix)] + if let Some(mode) = entry.unix_permissions().filter(|mode| mode & 0o777 != 0) { + use std::os::unix::fs::PermissionsExt; + + let mode = u32::from(mode & 0o777) | 0o600; + tokio::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode)).await?; + } } } @@ -353,7 +361,9 @@ pub async fn extract_tar_gz( let buf_reader = tokio::io::BufReader::new(file); let gzip_decoder = async_compression::tokio::bufread::GzipDecoder::new(buf_reader); - let mut tar_archive = tokio_tar::Archive::new(gzip_decoder); + let mut tar_archive = tokio_tar::ArchiveBuilder::new(gzip_decoder) + .set_preserve_permissions(true) + .build(); tar_archive.unpack(dest).await?; Ok(())