From 32259aadda4d9460445e768008f73d239280e14e Mon Sep 17 00:00:00 2001 From: dokaptur Date: Tue, 9 Dec 2014 12:02:22 +0100 Subject: [PATCH] basic version 1 --- Makefile | 1 + src/stdbuf/libstdbuf.c | 7 ++ src/stdbuf/libstdbuf.h | 6 ++ src/stdbuf/libstdbuf.rs | 48 +++++++++ src/stdbuf/prepare_libs.sh | 9 ++ src/stdbuf/stdbuf.rs | 215 +++++++++++++++++++++++++++++++++++++ 6 files changed, 286 insertions(+) create mode 100644 src/stdbuf/libstdbuf.c create mode 100644 src/stdbuf/libstdbuf.h create mode 100644 src/stdbuf/libstdbuf.rs create mode 100755 src/stdbuf/prepare_libs.sh create mode 100644 src/stdbuf/stdbuf.rs diff --git a/Makefile b/Makefile index 158ff8f61..cc24773ba 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,7 @@ PROGS := \ seq \ shuf \ sort \ + stdbuf \ sum \ sync \ tac \ 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..3187ef55c --- /dev/null +++ b/src/stdbuf/libstdbuf.rs @@ -0,0 +1,48 @@ +#![crate_type = "dylib"] + +extern crate libc; +use libc::{c_int, size_t, c_char, FILE, _IOFBF, _IONBF, _IOLBF, setvbuf}; +use std::ptr; +use std::os; + +extern { + static stdin: *mut FILE; + static stdout: *mut FILE; + static stderr: *mut FILE; +} + +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: uint = match from_str(input) { + Some(num) => num, + None => panic!("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 { + panic!("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/prepare_libs.sh b/src/stdbuf/prepare_libs.sh new file mode 100755 index 000000000..bc0869123 --- /dev/null +++ b/src/stdbuf/prepare_libs.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +cd $(dirname $0) +rustc libstdbuf.rs +gcc -c -Wall -Werror -fpic libstdbuf.c -L. -llibstdbuf.so +gcc -shared -o libstdbuf.so libstdbuf.o +mv *.so ../../build/ +rm *.o +export LD_LIBRARY_PATH="$LD_LIBRARY_PATH":"$PWD"/../../build diff --git a/src/stdbuf/stdbuf.rs b/src/stdbuf/stdbuf.rs new file mode 100644 index 000000000..548e23351 --- /dev/null +++ b/src/stdbuf/stdbuf.rs @@ -0,0 +1,215 @@ +#![crate_name = "stdbuf"] + +/* +* 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. +*/ +#![feature(macro_rules)] + +extern crate getopts; +extern crate libc; +use getopts::{optopt, optflag, getopts, usage, Matches, OptGroup}; +use std::io::process::{Command, StdioContainer}; +use std::iter::range_inclusive; +use std::num::Int; + +#[path = "../common/util.rs"] +mod util; + +static NAME: &'static str = "stdbuf"; +static VERSION: &'static str = "1.0.0"; +static LIBSTDBUF_PATH: &'static str = "liblibstdbuf.so libstdbuf.so"; + +#[deriving(Show)] +enum BufferType { + Default, + Line, + Size(u64) +} + +#[deriving(Show)] +struct ProgramOptions { + stdin: BufferType, + stdout: BufferType, + stderr: BufferType, +} + +enum ErrMsg { + Retry, + Fatal +} + +enum OkMsg { + Buffering, + Help, + Version +} + +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_chars(|c: char| c.is_digit(10)); + let num = size.trim_right_chars(|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 from_str(num) { + Some(m) => m, + None => return None, + }; + let (power, base): (uint, 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 => {}, + } +} + + +pub fn uumain(args: Vec) -> int { + 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.slice(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); + command.args(args.slice_from(command_idx+1)).env("LD_PRELOAD", LIBSTDBUF_PATH); + 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); + match command.spawn() { + Ok(_) => {}, + Err(e) => crash!(1, "failed to execute process: {}", e), + }; + 0 +} +