fix: preserve executable bits when extracting Java runtimes (#873)

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
This commit is contained in:
Luna 2026-09-28 17:15:41 +02:00 • committed by GitHub
parent d632c47320
commit 7ca7ac04cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 60 additions and 10 deletions

View file

@ -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")]

View file

@ -94,6 +94,9 @@ impl JavaService {
#[tracing::instrument(level = "debug", skip(self))]
async fn revalidate(&self, runtime: JavaRuntime) -> JavaResult<Option<JavaRuntime>> {
#[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));

View file

@ -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(())