diff --git a/.cargo/config.toml b/.cargo/config.toml index 02550b267..c97dc5701 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,6 +3,8 @@ linker = "x86_64-unknown-redox-gcc" [env] PROJECT_NAME_FOR_VERSION_STRING = "uutils coreutils" +# See feat_external_libstdbuf in src/uu/stdbuf/Cargo.toml +LIBSTDBUF_DIR = "/usr/lib" # libstdbuf must be a shared library, so musl libc can't be linked statically # https://github.com/rust-lang/rust/issues/82193 diff --git a/Cargo.toml b/Cargo.toml index cd280f3a6..ba17d4c43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,9 @@ test_risky_names = [] # * only build `uudoc` when `--feature uudoc` is activated uudoc = ["zip", "dep:uuhelp_parser"] ## features +## Optional feature for stdbuf +# "feat_external_libstdbuf" == use an external libstdbuf.so for stdbuf instead of embedding it +feat_external_libstdbuf = ["stdbuf/feat_external_libstdbuf"] # "feat_acl" == enable support for ACLs (access control lists; by using`--features feat_acl`) # NOTE: # * On linux, the posix-acl/acl-sys crate requires `libacl` headers and shared library to be accessible in the C toolchain at compile time. diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index bcfc9fb94..0da391f49 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -1,3 +1,4 @@ +# spell-checker:ignore dpkg [package] name = "uu_stdbuf" description = "stdbuf ~ (uutils) run COMMAND with modified standard stream buffering" @@ -23,6 +24,25 @@ libstdbuf = { package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } tempfile = { workspace = true } uucore = { workspace = true, features = ["parser"] } +# "feat_external_libstdbuf": use an external libstdbuf.so for stdbuf instead of embedding it into +# the stdbuf binary. +# There are 2 use-cases: +# 1. Installation of uutils-coreutils using cargo install (e.g. from crates.io +# which supports only "cargo install" as installation method). In this case, +# installing libstdbuf.so is impossible, because "cargo install" installs +# only binary programs (no cdylib), thus libstdbuf.so must be embedded into +# stdbuf and written to /tmp at runtime. This is a hack, and may not work +# on some platforms, e.g. because the SELinux permissions may not allow +# stdbuf to write to /tmp, /tmp may be read-only, libstdbuf.so may not work +# at all without SELinux labels, etc. +# +# 2. Installation of uutils-coreutils using an external tool, e.g. dpkg/apt on +# debian. In this case, libstdbuf.so should be installed separately to its +# correct location and the environment variable LIBSTDBUF_DIR configures the +# installation directory during the build. E.g. LIBSTDBUF_DIR="/usr/lib" +[features] +feat_external_libstdbuf = [] + [[bin]] name = "stdbuf" path = "src/main.rs" diff --git a/src/uu/stdbuf/build.rs b/src/uu/stdbuf/build.rs index 52202ed33..a9ef81db1 100644 --- a/src/uu/stdbuf/build.rs +++ b/src/uu/stdbuf/build.rs @@ -29,6 +29,26 @@ fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=src/libstdbuf/src/libstdbuf.rs"); + // Check for external stdbuf feature requirements + #[cfg(feature = "feat_external_libstdbuf")] + { + if env::var("LIBSTDBUF_DIR").is_err() { + eprintln!( + "\n\x1b[31mError:\x1b[0m The 'feat_external_libstdbuf' feature requires the LIBSTDBUF_DIR environment variable to be set." + ); + eprintln!( + "\x1b[33mUsage:\x1b[0m LIBSTDBUF_DIR=/path/to/lib/directory cargo build --features feat_external_libstdbuf" + ); + eprintln!( + "\x1b[33mExample:\x1b[0m LIBSTDBUF_DIR=/usr/lib cargo build --features feat_external_libstdbuf" + ); + eprintln!( + "\nThis directory should point to where libstdbuf.so / libstdbuf.dylib will be installed on the target system." + ); + std::process::exit(1); + } + } + let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set"); let target = env::var("TARGET").unwrap_or_else(|_| "unknown".to_string()); diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 9c2698a97..8f565e796 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -6,8 +6,6 @@ // spell-checker:ignore (ToDO) tempdir dyld dylib optgrps libstdbuf use clap::{Arg, ArgAction, ArgMatches, Command}; -use std::fs::File; -use std::io::Write; use std::os::unix::process::ExitStatusExt; use std::path::PathBuf; use std::process; @@ -29,16 +27,19 @@ mod options { pub const COMMAND: &str = "command"; } -#[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - target_os = "dragonfly" +#[cfg(all( + not(feature = "feat_external_libstdbuf"), + any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly" + ) ))] const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.so")); -#[cfg(target_vendor = "apple")] +#[cfg(all(not(feature = "feat_external_libstdbuf"), target_vendor = "apple"))] const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.dylib")); enum BufferType { @@ -137,7 +138,11 @@ fn set_command_env(command: &mut process::Command, buffer_name: &str, buffer_typ } } +#[cfg(not(feature = "feat_external_libstdbuf"))] fn get_preload_env(tmp_dir: &TempDir) -> UResult<(String, PathBuf)> { + use std::fs::File; + use std::io::Write; + let (preload, extension) = preload_strings()?; let inject_path = tmp_dir.path().join("libstdbuf").with_extension(extension); @@ -147,6 +152,29 @@ fn get_preload_env(tmp_dir: &TempDir) -> UResult<(String, PathBuf)> { Ok((preload.to_owned(), inject_path)) } +#[cfg(feature = "feat_external_libstdbuf")] +fn get_preload_env(_tmp_dir: &TempDir) -> UResult<(String, PathBuf)> { + let (preload, extension) = preload_strings()?; + + // Use the directory provided at compile time via LIBSTDBUF_DIR environment variable + // This will fail to compile if LIBSTDBUF_DIR is not set, which is the desired behavior + const LIBSTDBUF_DIR: &str = env!("LIBSTDBUF_DIR"); + let path_buf = PathBuf::from(LIBSTDBUF_DIR) + .join("libstdbuf") + .with_extension(extension); + if path_buf.exists() { + return Ok((preload.to_owned(), path_buf)); + } + + Err(USimpleError::new( + 1, + format!( + "External libstdbuf not found at configured path: {}", + path_buf.display() + ), + )) +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?;