From 99aa51a9a9a7d908bac124ae163257a90a810a21 Mon Sep 17 00:00:00 2001 From: Etienne Cordonnier Date: Tue, 27 May 2025 21:06:02 +0200 Subject: [PATCH] stdbuf: add feat_external_stdbuf Fixes https://github.com/uutils/coreutils/issues/6591 "feat_external_stdbuf": 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_PATH configures the installation path during the build. E.g. LIBSTDBUF_PATH="/lib/libstdbuf.so" Signed-off-by: Etienne Cordonnier --- .cargo/config.toml | 2 ++ Cargo.toml | 3 +++ src/uu/stdbuf/Cargo.toml | 20 ++++++++++++++++ src/uu/stdbuf/build.rs | 20 ++++++++++++++++ src/uu/stdbuf/src/stdbuf.rs | 46 +++++++++++++++++++++++++++++-------- 5 files changed, 82 insertions(+), 9 deletions(-) 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 07c6e1238..1d59ffe5e 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)?;