diff --git a/Makefile b/Makefile index 158ff8f61..0d1c44bea 100644 --- a/Makefile +++ b/Makefile @@ -5,11 +5,13 @@ ENABLE_STRIP ?= n # Binaries RUSTC ?= rustc CARGO ?= cargo +CC ?= gcc RM := rm # Install directories PREFIX ?= /usr/local BINDIR ?= /bin +LIBDIR ?= /lib # This won't support any directory with spaces in its name, but you can just # make a symlink without spaces that points to the directory. @@ -105,6 +107,7 @@ UNIX_PROGS := \ mkfifo \ nice \ nohup \ + stdbuf \ timeout \ tty \ uname \ @@ -136,6 +139,22 @@ INSTALL ?= $(EXES) INSTALLEES := \ $(filter $(INSTALL),$(filter-out $(DONT_INSTALL),$(EXES) uutils)) +# Shared library extension +SYSTEM := $(shell uname) +DYLIB_EXT := +ifeq ($(SYSTEM),Linux) + DYLIB_EXT := .so +endif +ifeq ($(SYSTEM),Darwin) + DYLIB_EXT := .dylib +endif + +# Libaries to install +LIBS := +ifneq (,$(findstring stdbuf, $(INSTALLEES))) +LIBS += libstdbuf$(DYLIB_EXT) +endif + # Programs with usable tests TEST_PROGS := \ cat \ @@ -224,6 +243,16 @@ $(BUILDDIR)/uutils: $(SRCDIR)/uutils/uutils.rs $(BUILDDIR)/mkuutils $(RLIB_PATHS $(BUILDDIR)/mkuutils $(BUILDDIR)/gen/uutils.rs $(EXES) $(RUSTC) $(RUSTCBINFLAGS) --extern test=$(BUILDDIR)/libtest.rlib --emit link,dep-info $(BUILDDIR)/gen/uutils.rs --out-dir $(BUILDDIR) $(if $(ENABLE_STRIP),strip $@) + +# Library for stdbuf +$(BUILDIR)/libstdbuf.$(DYLIB_EXT): $(SRCDIR)/stdbuf/libstdbuf.rs $(SRCDIR)/stdbuf/libstdbuf.c $(SRCDIR)/stdbuf/libstdbuf.h | $(BUILDDIR) + cd $(SRCDIR)/stdbuf && \ + $(RUSTC) libstdbuf.rs && \ + $(CC) -c -Wall -Werror -fpic libstdbuf.c -L. -llibstdbuf.a && \ + $(CC) -shared -o libstdbuf.$(DYLIB_EXT) -Wl,--whole-archive liblibstdbuf.a -Wl,--no-whole-archive libstdbuf.o -lpthread && \ + mv *.so $(BUILDDIR) && $(RM) *.o && $(RM) *.a + +$(BUILDDIR)/stdbuf: $(BUILDIR)/libstdbuf.$(DYLIB_EXT) # Dependencies $(BUILDDIR)/.rust-crypto: | $(BUILDDIR) @@ -277,6 +306,10 @@ install: $(addprefix $(BUILDDIR)/,$(INSTALLEES)) for prog in $(INSTALLEES); do \ install $(BUILDDIR)/$$prog $(DESTDIR)$(PREFIX)$(BINDIR)/$(PROG_PREFIX)$$prog; \ done + mkdir -p $(DESTDIR)$(PREFIX)$(LIBDIR) + for lib in $(LIBS); do \ + install $(BUILDDIR)/$$lib $(DESTDIR)$(PREFIX)$(LIBDIR)/$$lib; \ + done # TODO: figure out if there is way for prefixes to work with the symlinks install-multicall: $(BUILDDIR)/uutils @@ -286,12 +319,18 @@ install-multicall: $(BUILDDIR)/uutils for prog in $(INSTALLEES); do \ ln -s $(PROG_PREFIX)uutils $$prog; \ done + mkdir -p $(DESTDIR)$(PREFIX)$(LIBDIR) + for lib in $(LIBS); do \ + install $(BUILDDIR)/$$lib $(DESTDIR)$(PREFIX)$(LIBDIR)/$$lib; \ + done uninstall: rm -f $(addprefix $(DESTDIR)$(PREFIX)$(BINDIR)/$(PROG_PREFIX),$(PROGS)) + rm -f $(addprefix $(DESTDIR)$(PREFIX)$(LIBDIR)/,$(LIBS)) uninstall-multicall: rm -f $(addprefix $(DESTDIR)$(PREFIX)$(BINDIR)/,$(PROGS) $(PROG_PREFIX)uutils) + rm -f $(addprefix $(DESTDIR)$(PREFIX)$(LIBDIR)/,$(LIBS)) # Test under the busybox testsuite $(BUILDDIR)/busybox: $(BUILDDIR)/uutils diff --git a/src/stdbuf/libstdbuf.c b/src/stdbuf/libstdbuf.c new file mode 100644 index 000000000..b7ee1370f --- /dev/null +++ b/src/stdbuf/libstdbuf.c @@ -0,0 +1,7 @@ +#include "libstdbuf.h" + +void __attribute ((constructor)) +stdbuf_init (void) +{ + stdbuf(); +} diff --git a/src/stdbuf/libstdbuf.h b/src/stdbuf/libstdbuf.h new file mode 100644 index 000000000..d8ae3c70a --- /dev/null +++ b/src/stdbuf/libstdbuf.h @@ -0,0 +1,6 @@ +#ifndef UUTILS_LIBSTDBUF_H +#define UUTILS_LIBSTDBUF_H + +void stdbuf(void); + +#endif diff --git a/src/stdbuf/libstdbuf.rs b/src/stdbuf/libstdbuf.rs new file mode 100644 index 000000000..4c14fab0f --- /dev/null +++ b/src/stdbuf/libstdbuf.rs @@ -0,0 +1,56 @@ +#![crate_name = "libstdbuf"] +#![crate_type = "staticlib"] +#![allow(unstable)] + +extern crate libc; +use libc::{c_int, size_t, c_char, FILE, _IOFBF, _IONBF, _IOLBF, setvbuf}; +use std::ptr; +use std::os; + +#[path = "../common/util.rs"] +#[macro_use] +mod util; + +extern { + static stdin: *mut FILE; + static stdout: *mut FILE; + static stderr: *mut FILE; +} + +static NAME: &'static str = "libstdbuf"; + +fn set_buffer(stream: *mut FILE, value: &str) { + let (mode, size): (c_int, size_t) = match value { + "0" => (_IONBF, 0 as size_t), + "L" => (_IOLBF, 0 as size_t), + input => { + let buff_size: usize = match input.parse() { + Some(num) => num, + None => crash!(1, "incorrect size of buffer!") + }; + (_IOFBF, buff_size as size_t) + } + }; + let mut res: c_int; + unsafe { + let buffer: *mut c_char = ptr::null_mut(); + assert!(buffer.is_null()); + res = libc::setvbuf(stream, buffer, mode, size); + } + if res != 0 { + crash!(res, "error while calling setvbuf!"); + } +} + +#[no_mangle] +pub extern fn stdbuf() { + if let Some(val) = os::getenv("_STDBUF_E") { + set_buffer(stderr, val.as_slice()); + } + if let Some(val) = os::getenv("_STDBUF_I") { + set_buffer(stdin, val.as_slice()); + } + if let Some(val) = os::getenv("_STDBUF_O") { + set_buffer(stdout, val.as_slice()); + } +} diff --git a/src/stdbuf/stdbuf.rs b/src/stdbuf/stdbuf.rs new file mode 100644 index 000000000..100cc2641 --- /dev/null +++ b/src/stdbuf/stdbuf.rs @@ -0,0 +1,251 @@ +#![crate_name = "stdbuf"] +#![allow(unstable)] + +/* +* This file is part of the uutils coreutils package. +* +* (c) Dorota Kapturkiewicz +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +extern crate getopts; +extern crate libc; +use getopts::{optopt, optflag, getopts, usage, Matches, OptGroup}; +use std::io::process::{Command, StdioContainer}; +use std::io::fs::PathExtensions; +use std::iter::range_inclusive; +use std::num::Int; +use std::os; + +#[path = "../common/util.rs"] +#[macro_use] +mod util; + +static NAME: &'static str = "stdbuf"; +static VERSION: &'static str = "1.0.0"; +static LIBSTDBUF: &'static str = "libstdbuf"; + +enum BufferType { + Default, + Line, + Size(u64) +} + +struct ProgramOptions { + stdin: BufferType, + stdout: BufferType, + stderr: BufferType, +} + +enum ErrMsg { + Retry, + Fatal +} + +enum OkMsg { + Buffering, + Help, + Version +} + +#[cfg(target_os = "linux")] +fn preload_strings() -> (&'static str, &'static str) { + ("LD_PRELOAD", ".so") +} + +#[cfg(target_os = "macos")] +fn preload_strings() -> (&'static str, &'static str) { + ("DYLD_INSERT_LIBRARIES", ".dylib") +} + +#[cfg(not(any(target_os = "linux", target_os = "macos")))] +fn preload_strings() -> (&'static str, &'static str) { + crash!(1, "Command not supported for this operating system!") +} + + +fn print_version() { + println!("{} version {}", NAME, VERSION); +} + +fn print_usage(opts: &[OptGroup]) { + let brief = + "Usage: stdbuf OPTION... COMMAND\n \ + Run COMMAND, with modified buffering operations for its standard streams\n \ + Mandatory arguments to long options are mandatory for short options too."; + let explanation = + "If MODE is 'L' the corresponding stream will be line buffered.\n \ + This option is invalid with standard input.\n\n \ + If MODE is '0' the corresponding stream will be unbuffered.\n\n \ + Otherwise MODE is a number which may be followed by one of the following:\n\n \ + KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n \ + In this case the corresponding stream will be fully buffered with the buffer size set to MODE bytes.\n\n \ + NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for e.g.) then that will override \ + corresponding settings changed by 'stdbuf'.\n \ + Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O, \ + and are thus unaffected by 'stdbuf' settings.\n"; + println!("{}\n{}", getopts::usage(brief, opts), explanation); +} + +fn parse_size(size: &str) -> Option { + let ext = size.trim_left_matches(|&: c: char| c.is_digit(10)); + let num = size.trim_right_matches(|&: c: char| c.is_alphabetic()); + let mut recovered = num.to_string(); + recovered.push_str(ext); + if recovered.as_slice() != size { + return None; + } + let buf_size: u64 = match num.parse() { + Some(m) => m, + None => return None, + }; + let (power, base): (usize, u64) = match ext { + "" => (0, 0), + "KB" => (1, 1024), + "K" => (1, 1000), + "MB" => (2, 1024), + "M" => (2, 1000), + "GB" => (3, 1024), + "G" => (3, 1000), + "TB" => (4, 1024), + "T" => (4, 1000), + "PB" => (5, 1024), + "P" => (5, 1000), + "EB" => (6, 1024), + "E" => (6, 1000), + "ZB" => (7, 1024), + "Z" => (7, 1000), + "YB" => (8, 1024), + "Y" => (8, 1000), + _ => return None, + }; + Some(buf_size * base.pow(power)) +} + +fn check_option(matches: &Matches, name: &str, modified: &mut bool) -> Option { + match matches.opt_str(name) { + Some(value) => { + *modified = true; + match value.as_slice() { + "L" => { + if name == "input" { + show_info!("stdbuf: line buffering stdin is meaningless"); + None + } else { + Some(BufferType::Line) + } + }, + x => { + let size = match parse_size(x) { + Some(m) => m, + None => { show_error!("Invalid mode {}", x); return None } + }; + Some(BufferType::Size(size)) + }, + } + }, + None => Some(BufferType::Default), + } +} + +fn parse_options(args: &[String], options: &mut ProgramOptions, optgrps: &[OptGroup]) -> Result { + let matches = match getopts(args, optgrps) { + Ok(m) => m, + Err(_) => return Err(ErrMsg::Retry) + }; + if matches.opt_present("help") { + return Ok(OkMsg::Help); + } + if matches.opt_present("version") { + return Ok(OkMsg::Version); + } + let mut modified = false; + options.stdin = try!(check_option(&matches, "input", &mut modified).ok_or(ErrMsg::Fatal)); + options.stdout = try!(check_option(&matches, "output", &mut modified).ok_or(ErrMsg::Fatal)); + options.stderr = try!(check_option(&matches, "error", &mut modified).ok_or(ErrMsg::Fatal)); + + if matches.free.len() != 1 { + return Err(ErrMsg::Retry); + } + if !modified { + show_error!("stdbuf: you must specify a buffering mode option"); + return Err(ErrMsg::Fatal); + } + Ok(OkMsg::Buffering) +} + +fn set_command_env(command: &mut Command, buffer_name: &str, buffer_type: BufferType) { + match buffer_type { + BufferType::Size(m) => { command.env(buffer_name, m.to_string()); }, + BufferType::Line => { command.env(buffer_name, "L"); }, + BufferType::Default => {}, + } +} + +fn get_preload_env() -> (String, String) { + let (preload, extension) = preload_strings(); + let mut libstdbuf = LIBSTDBUF.to_string(); + libstdbuf.push_str(extension); + // First search for library in directory of executable. + let mut path = match os::self_exe_path() { + Some(exe_path) => exe_path, + None => crash!(1, "Impossible to fetch the path of this executable.") + }; + path.push(libstdbuf.as_slice()); + if path.exists() { + match path.as_str() { + Some(s) => { return (preload.to_string(), s.to_string()); }, + None => crash!(1, "Error while converting path.") + }; + } + // We assume library is in LD_LIBRARY_PATH/ DYLD_LIBRARY_PATH. + (preload.to_string(), libstdbuf) +} + + +pub fn uumain(args: Vec) -> isize { + let optgrps = [ + optopt("i", "input", "adjust standard input stream buffering", "MODE"), + optopt("o", "output", "adjust standard output stream buffering", "MODE"), + optopt("e", "error", "adjust standard error stream buffering", "MODE"), + optflag("", "help", "display this help and exit"), + optflag("", "version", "output version information and exit"), + ]; + let mut options = ProgramOptions {stdin: BufferType::Default, stdout: BufferType::Default, stderr: BufferType::Default}; + let mut command_idx = -1; + for i in range_inclusive(1, args.len()) { + match parse_options(&args[1 .. i], &mut options, &optgrps) { + Ok(OkMsg::Buffering) => { + command_idx = i - 1; + break; + }, + Ok(OkMsg::Help) => { + print_usage(&optgrps); + return 0; + }, + Ok(OkMsg::Version) => { + print_version(); + return 0; + }, + Err(ErrMsg::Fatal) => break, + Err(ErrMsg::Retry) => continue, + } + }; + if command_idx == -1 { + crash!(125, "Invalid options\nTry 'stdbuf --help' for more information."); + } + let ref command_name = args[command_idx]; + let mut command = Command::new(command_name); + let (preload_env, libstdbuf) = get_preload_env(); + command.args(&args[command_idx + 1 ..]).env(preload_env.as_slice(), libstdbuf.as_slice()); + command.stdin(StdioContainer::InheritFd(0)).stdout(StdioContainer::InheritFd(1)).stderr(StdioContainer::InheritFd(2)); + set_command_env(&mut command, "_STDBUF_I", options.stdin); + set_command_env(&mut command, "_STDBUF_O", options.stdout); + set_command_env(&mut command, "_STDBUF_E", options.stderr); + if let Err(e) = command.spawn() { + crash!(1, "failed to execute process: {}", e); + } + 0 +}