1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-08-01 13:37:48 +00:00

Merge pull request #469 from dokaptur/stdbuf

stdbuf - basic version
This commit is contained in:
Alex Lyon 2015-01-25 00:02:43 -08:00
commit bfc6d2e288
5 changed files with 359 additions and 0 deletions

View file

@ -5,11 +5,13 @@ ENABLE_STRIP ?= n
# Binaries # Binaries
RUSTC ?= rustc RUSTC ?= rustc
CARGO ?= cargo CARGO ?= cargo
CC ?= gcc
RM := rm RM := rm
# Install directories # Install directories
PREFIX ?= /usr/local PREFIX ?= /usr/local
BINDIR ?= /bin BINDIR ?= /bin
LIBDIR ?= /lib
# This won't support any directory with spaces in its name, but you can just # 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. # make a symlink without spaces that points to the directory.
@ -105,6 +107,7 @@ UNIX_PROGS := \
mkfifo \ mkfifo \
nice \ nice \
nohup \ nohup \
stdbuf \
timeout \ timeout \
tty \ tty \
uname \ uname \
@ -136,6 +139,22 @@ INSTALL ?= $(EXES)
INSTALLEES := \ INSTALLEES := \
$(filter $(INSTALL),$(filter-out $(DONT_INSTALL),$(EXES) uutils)) $(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 # Programs with usable tests
TEST_PROGS := \ TEST_PROGS := \
cat \ cat \
@ -224,6 +243,16 @@ $(BUILDDIR)/uutils: $(SRCDIR)/uutils/uutils.rs $(BUILDDIR)/mkuutils $(RLIB_PATHS
$(BUILDDIR)/mkuutils $(BUILDDIR)/gen/uutils.rs $(EXES) $(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) $(RUSTC) $(RUSTCBINFLAGS) --extern test=$(BUILDDIR)/libtest.rlib --emit link,dep-info $(BUILDDIR)/gen/uutils.rs --out-dir $(BUILDDIR)
$(if $(ENABLE_STRIP),strip $@) $(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 # Dependencies
$(BUILDDIR)/.rust-crypto: | $(BUILDDIR) $(BUILDDIR)/.rust-crypto: | $(BUILDDIR)
@ -277,6 +306,10 @@ install: $(addprefix $(BUILDDIR)/,$(INSTALLEES))
for prog in $(INSTALLEES); do \ for prog in $(INSTALLEES); do \
install $(BUILDDIR)/$$prog $(DESTDIR)$(PREFIX)$(BINDIR)/$(PROG_PREFIX)$$prog; \ install $(BUILDDIR)/$$prog $(DESTDIR)$(PREFIX)$(BINDIR)/$(PROG_PREFIX)$$prog; \
done 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 # TODO: figure out if there is way for prefixes to work with the symlinks
install-multicall: $(BUILDDIR)/uutils install-multicall: $(BUILDDIR)/uutils
@ -286,12 +319,18 @@ install-multicall: $(BUILDDIR)/uutils
for prog in $(INSTALLEES); do \ for prog in $(INSTALLEES); do \
ln -s $(PROG_PREFIX)uutils $$prog; \ ln -s $(PROG_PREFIX)uutils $$prog; \
done done
mkdir -p $(DESTDIR)$(PREFIX)$(LIBDIR)
for lib in $(LIBS); do \
install $(BUILDDIR)/$$lib $(DESTDIR)$(PREFIX)$(LIBDIR)/$$lib; \
done
uninstall: uninstall:
rm -f $(addprefix $(DESTDIR)$(PREFIX)$(BINDIR)/$(PROG_PREFIX),$(PROGS)) rm -f $(addprefix $(DESTDIR)$(PREFIX)$(BINDIR)/$(PROG_PREFIX),$(PROGS))
rm -f $(addprefix $(DESTDIR)$(PREFIX)$(LIBDIR)/,$(LIBS))
uninstall-multicall: uninstall-multicall:
rm -f $(addprefix $(DESTDIR)$(PREFIX)$(BINDIR)/,$(PROGS) $(PROG_PREFIX)uutils) rm -f $(addprefix $(DESTDIR)$(PREFIX)$(BINDIR)/,$(PROGS) $(PROG_PREFIX)uutils)
rm -f $(addprefix $(DESTDIR)$(PREFIX)$(LIBDIR)/,$(LIBS))
# Test under the busybox testsuite # Test under the busybox testsuite
$(BUILDDIR)/busybox: $(BUILDDIR)/uutils $(BUILDDIR)/busybox: $(BUILDDIR)/uutils

7
src/stdbuf/libstdbuf.c Normal file
View file

@ -0,0 +1,7 @@
#include "libstdbuf.h"
void __attribute ((constructor))
stdbuf_init (void)
{
stdbuf();
}

6
src/stdbuf/libstdbuf.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef UUTILS_LIBSTDBUF_H
#define UUTILS_LIBSTDBUF_H
void stdbuf(void);
#endif

56
src/stdbuf/libstdbuf.rs Normal file
View file

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

251
src/stdbuf/stdbuf.rs Normal file
View file

@ -0,0 +1,251 @@
#![crate_name = "stdbuf"]
#![allow(unstable)]
/*
* This file is part of the uutils coreutils package.
*
* (c) Dorota Kapturkiewicz <dokaptur@gmail.com>
*
* 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<u64> {
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<BufferType> {
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<OkMsg, ErrMsg> {
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<String>) -> 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
}