use std::env;
use std::path::Path;
pub fn setup_env(
onelf_dir: &str,
argv0: &str,
exec_path: &str,
entrypoint_name: &str,
mode: &str,
lib_subpath: &str,
target_path: &str,
) -> String {
let launch_dir = env::current_dir()
.ok()
.and_then(|p| p.to_str().map(String::from))
.unwrap_or_default();
unsafe {
env::set_var("ONELF_DIR", onelf_dir);
env::set_var("ONELF_ARGV0", argv0);
env::set_var("ONELF_EXEC", exec_path);
env::set_var("ONELF_ENTRYPOINT", entrypoint_name);
env::set_var("ONELF_LAUNCH_DIR", &launch_dir);
env::set_var("ONELF_MODE", mode);
}
if onelf_dir.is_empty() {
return String::new();
}
let pkg = Path::new(onelf_dir);
let target_is_elf = is_elf_file(target_path);
let mut lib_path = String::new();
if target_is_elf && !lib_subpath.is_empty() {
let lib_paths: Vec<String> = lib_subpath
.split(':')
.map(|p| pkg.join(p).to_string_lossy().to_string())
.collect();
let lib_str = lib_paths.join(":");
if !lib_str.is_empty() {
let mut parts: Vec<String> = Vec::new();
parts.push(lib_str);
let existing = env::var("LD_LIBRARY_PATH").unwrap_or_default();
if !existing.is_empty() {
parts.push(existing);
}
let host_paths = host_driver_paths();
if !host_paths.is_empty() {
parts.push(host_paths.join(":"));
}
lib_path = parts.join(":");
let dri_paths: Vec<String> = lib_paths
.iter()
.map(|p| Path::new(p).join("dri").to_string_lossy().to_string())
.filter(|p| Path::new(p).is_dir())
.collect();
if !dri_paths.is_empty() {
let joined = dri_paths.join(":");
if env::var("LIBGL_DRIVERS_PATH").is_err() {
unsafe {
env::set_var("LIBGL_DRIVERS_PATH", &joined);
}
}
if env::var("LIBVA_DRIVERS_PATH").is_err() {
unsafe {
env::set_var("LIBVA_DRIVERS_PATH", &joined);
}
}
}
if env::var("GBM_BACKENDS_PATH").is_err() {
let gbm_paths: Vec<String> = lib_paths
.iter()
.map(|p| Path::new(p).join("gbm").to_string_lossy().to_string())
.filter(|p| Path::new(p).is_dir())
.collect();
if !gbm_paths.is_empty() {
unsafe {
env::set_var("GBM_BACKENDS_PATH", gbm_paths.join(":"));
}
}
}
}
}
setup_xdg_data_dirs(pkg);
if env::var("__EGL_VENDOR_LIBRARY_DIRS").is_err() {
let mut egl_dirs: Vec<String> = Vec::new();
let egl_dir = pkg.join("share/glvnd/egl_vendor.d");
if egl_dir.is_dir() {
egl_dirs.push(egl_dir.to_string_lossy().into_owned());
}
for d in &[
"/run/opengl-driver/share/glvnd/egl_vendor.d",
"/etc/glvnd/egl_vendor.d",
"/usr/share/glvnd/egl_vendor.d",
] {
if Path::new(d).is_dir() {
egl_dirs.push((*d).to_string());
}
}
if !egl_dirs.is_empty() {
unsafe {
env::set_var("__EGL_VENDOR_LIBRARY_DIRS", egl_dirs.join(":"));
}
}
}
if env::var("DRIRC_CONFIGDIR").is_err() {
let drirc_dir = pkg.join("share/drirc.d");
if drirc_dir.is_dir() {
unsafe {
env::set_var("DRIRC_CONFIGDIR", drirc_dir.to_string_lossy().as_ref());
}
}
}
if env::var("LIBDRM_IDS_PATH").is_err() {
let libdrm_dir = pkg.join("share/libdrm");
if libdrm_dir.is_dir() {
unsafe {
env::set_var("LIBDRM_IDS_PATH", libdrm_dir.to_string_lossy().as_ref());
}
}
}
if env::var("XKB_CONFIG_ROOT").is_err() {
let xkb_dir = pkg.join("share/X11/xkb");
if xkb_dir.is_dir() {
unsafe {
env::set_var("XKB_CONFIG_ROOT", xkb_dir.to_string_lossy().as_ref());
}
}
}
if env::var("LIBDECOR_PLUGIN_DIR").is_err() {
let libdecor_dir = pkg.join("share/libdecor/plugins-1");
if libdecor_dir.is_dir() {
unsafe {
env::set_var(
"LIBDECOR_PLUGIN_DIR",
libdecor_dir.to_string_lossy().as_ref(),
);
}
}
}
if env::var("VK_DRIVER_FILES").is_err() && env::var("VK_ADD_DRIVER_FILES").is_err() {
let mut icd_dirs: Vec<String> = Vec::new();
let vk_dir = pkg.join("share/vulkan/icd.d");
if vk_dir.is_dir() {
icd_dirs.push(vk_dir.to_string_lossy().into_owned());
}
for d in &[
"/run/opengl-driver/share/vulkan/icd.d",
"/etc/vulkan/icd.d",
"/usr/share/vulkan/icd.d",
] {
if Path::new(d).is_dir() {
icd_dirs.push((*d).to_string());
}
}
if !icd_dirs.is_empty() {
let mut all_files: Vec<String> = Vec::new();
for dir in &icd_dirs {
if let Ok(entries) = std::fs::read_dir(dir) {
for e in entries.filter_map(|e| e.ok()) {
if e.path().extension().map_or(false, |ext| ext == "json") {
all_files.push(e.path().to_string_lossy().into_owned());
}
}
}
}
if !all_files.is_empty() {
unsafe {
env::set_var("VK_DRIVER_FILES", all_files.join(":"));
}
}
}
}
lib_path
}
fn is_elf_file(path: &str) -> bool {
use std::io::Read;
let Ok(mut f) = std::fs::File::open(path) else {
return false;
};
let mut buf = [0u8; 4];
matches!(f.read(&mut buf), Ok(4)) && buf == *b"\x7fELF"
}
fn host_driver_paths() -> Vec<String> {
const CANDIDATES: &[&str] = &[
"/run/opengl-driver/lib",
"/run/opengl-driver-32/lib",
"/usr/lib/x86_64-linux-gnu",
"/usr/lib64",
"/usr/lib",
"/lib/x86_64-linux-gnu",
"/lib64",
];
CANDIDATES
.iter()
.filter(|p| Path::new(p).is_dir())
.map(|s| (*s).to_string())
.collect()
}
fn setup_xdg_data_dirs(pkg: &Path) {
let share = pkg.join("share");
if !share.is_dir() {
return;
}
let pkg_share = share.to_string_lossy();
let existing = env::var("XDG_DATA_DIRS").unwrap_or_default();
let new_val = if existing.is_empty() {
format!("{pkg_share}:/usr/local/share:/usr/share")
} else {
format!("{pkg_share}:{existing}")
};
unsafe {
env::set_var("XDG_DATA_DIRS", new_val);
}
}
fn expand_env_value(val: &str, onelf_dir: &str) -> String {
let mut out = String::with_capacity(val.len());
let b = val.as_bytes();
let mut i = 0;
while i < b.len() {
if b[i] == b'$' && i + 1 < b.len() && b[i + 1] == b'{' {
if let Some(end) = b[i + 2..].iter().position(|&c| c == b'}') {
let token = &val[i + 2..i + 2 + end];
let (name, default) = match token.split_once(":-") {
Some((n, d)) => (n, Some(d)),
None => (token, None),
};
let resolved = if name == "ONELF_DIR" {
Some(onelf_dir.to_string())
} else {
env::var(name).ok()
};
match resolved {
Some(ref v) if !v.is_empty() => out.push_str(v),
_ => out.push_str(default.unwrap_or("")),
}
i += 3 + end;
continue;
}
}
let ch = val[i..].chars().next().unwrap();
out.push(ch);
i += ch.len_utf8();
}
out
}
pub fn apply_custom_env(env_data: &[u8], onelf_dir: &str) {
let Ok(text) = std::str::from_utf8(env_data) else {
return;
};
for line in text.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((key, val)) = line.split_once('=') {
let expanded = expand_env_value(val.trim(), onelf_dir);
unsafe {
env::set_var(key.trim(), expanded);
}
}
}
}
#[cfg(test)]
mod expand_value_tests {
use super::expand_env_value;
#[test]
fn onelf_dir_live_env_and_unset() {
unsafe {
std::env::set_var("ONELF_T_LIVE_9c1", "LV");
std::env::remove_var("ONELF_T_UNSET_9c1");
}
assert_eq!(expand_env_value("${ONELF_DIR}/x", "/root"), "/root/x");
assert_eq!(expand_env_value("a:${ONELF_T_LIVE_9c1}:b", "/r"), "a:LV:b");
assert_eq!(expand_env_value("${ONELF_T_UNSET_9c1}", "/r"), "");
assert_eq!(
expand_env_value("${ONELF_DIR}/bin:${ONELF_T_LIVE_9c1}", "/R"),
"/R/bin:LV"
);
assert_eq!(expand_env_value("a${ONELF_DIR", "/r"), "a${ONELF_DIR");
}
#[test]
fn posix_default_word() {
unsafe {
std::env::set_var("ONELF_T_SET_d2", "S");
std::env::set_var("ONELF_T_EMPTY_d2", "");
std::env::remove_var("ONELF_T_MISSING_d2");
}
assert_eq!(
expand_env_value("${ONELF_T_MISSING_d2:-fallback}", "/r"),
"fallback"
);
assert_eq!(expand_env_value("${ONELF_T_SET_d2:-fb}", "/r"), "S");
assert_eq!(expand_env_value("${ONELF_T_EMPTY_d2:-fb}", "/r"), "fb");
assert_eq!(expand_env_value("${ONELF_DIR:-x}", "/R"), "/R");
unsafe { std::env::set_var("ONELF_T_PE_d2", "") };
assert_eq!(
expand_env_value("${ONELF_DIR}/bin:${ONELF_T_PE_d2:-/usr/bin:/bin}", "/R"),
"/R/bin:/usr/bin:/bin"
);
}
}