Merge 'uucore' repository source code back into 'coreutils'

This commit is contained in:
Roy Ivy III 2020-11-08 19:58:03 -06:00
commit e15df35e87
24 changed files with 2762 additions and 0 deletions

60
src/uucore/Cargo.toml Normal file
View file

@ -0,0 +1,60 @@
[package]
name = "uucore"
version = "0.0.4"
authors = ["uutils developers"]
license = "MIT"
description = "uutils ~ 'core' uutils code library (cross-platform)"
homepage = "https://github.com/uutils/uucore"
repository = "https://github.com/uutils/uucore"
readme = "README.md"
keywords = ["cross-platform", "uutils", "core", "library"]
categories = ["os"]
edition = "2018"
[badges]
appveyor = { repository = "uutils/uucore" }
travis-ci = { repository = "uutils/uucore" }
[lib]
path="src/lib/lib.rs"
[workspace]
members=["src/uucore_procs"]
[dependencies]
dunce = "1.0.0"
getopts = "<= 0.2.21"
wild = "2.0.4"
## optional
failure = { version = "<= 0.1.1", optional = true }
failure_derive = { version = "<= 0.1.1", optional = true }
lazy_static = { version = "1.3", optional = true }
nix = { version = "<= 0.13", optional = true }
platform-info = { version = "<= 0.0.1", optional = true }
time = { version = "<= 0.1.42", optional = true }
## "problem" dependencies
# * backtrace: transitive dependency via 'failure'; pin to <= v0.3.30 to avoid increasing MinSRV to v1.33.0
backtrace = ">= 0.3.3, <= 0.3.30"
# * data-encoding: require v2.1; but v2.2.0 breaks the build for MinSRV v1.31.0
data-encoding = { version = "~2.1", optional = true }
# * libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0
libc = { version = "0.2.15, <= 0.2.66", optional = true }
[target.'cfg(target_os = "redox")'.dependencies]
termion = "1.5"
[features]
default = []
## non-default features
encoding = ["data-encoding", "failure", "failure_derive"]
entries = ["libc"]
fs = ["libc"]
mode = ["libc"]
parse_time = []
process = ["libc"]
signals = []
utf8 = []
utmpx = ["time", "libc"]
wide = []
zero-copy = ["nix", "libc", "lazy_static", "platform-info"]

View file

@ -0,0 +1,32 @@
// features ~ feature-gated modules (core/bundler file)
#[cfg(feature = "encoding")]
pub mod encoding;
#[cfg(feature = "fs")]
pub mod fs;
#[cfg(feature = "parse_time")]
pub mod parse_time;
#[cfg(feature = "zero-copy")]
pub mod zero_copy;
// * (platform-specific) feature-gated modules
// ** non-windows
#[cfg(all(not(windows), feature = "mode"))]
pub mod mode;
// ** unix-only
#[cfg(all(unix, feature = "entries"))]
pub mod entries;
#[cfg(all(unix, feature = "process"))]
pub mod process;
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
pub mod signals;
#[cfg(all(
unix,
not(target_os = "fuchsia"),
not(target_env = "musl"),
feature = "utmpx"
))]
pub mod utmpx;
// ** windows-only
#[cfg(all(windows, feature = "wide"))]
pub mod wide;

View file

@ -0,0 +1,132 @@
// This file is part of the uutils coreutils package.
//
// (c) Jian Zeng <anonymousknight96@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (strings) ABCDEFGHIJKLMNOPQRSTUVWXYZ
extern crate data_encoding;
extern crate failure;
use self::data_encoding::{DecodeError, BASE32, BASE64};
use failure::Fail;
use std::io::{self, Read, Write};
#[derive(Fail, Debug)]
pub enum EncodingError {
#[fail(display = "{}", _0)]
Decode(#[cause] DecodeError),
#[fail(display = "{}", _0)]
Io(#[cause] io::Error),
}
impl From<io::Error> for EncodingError {
fn from(err: io::Error) -> EncodingError {
EncodingError::Io(err)
}
}
impl From<DecodeError> for EncodingError {
fn from(err: DecodeError) -> EncodingError {
EncodingError::Decode(err)
}
}
pub type DecodeResult = Result<Vec<u8>, EncodingError>;
#[derive(Clone, Copy)]
pub enum Format {
Base32,
Base64,
}
use self::Format::*;
pub fn encode(f: Format, input: &[u8]) -> String {
match f {
Base32 => BASE32.encode(input),
Base64 => BASE64.encode(input),
}
}
pub fn decode(f: Format, input: &[u8]) -> DecodeResult {
Ok(match f {
Base32 => BASE32.decode(input)?,
Base64 => BASE64.decode(input)?,
})
}
pub struct Data<R: Read> {
line_wrap: usize,
ignore_garbage: bool,
input: R,
format: Format,
alphabet: &'static [u8],
}
impl<R: Read> Data<R> {
pub fn new(input: R, format: Format) -> Self {
Data {
line_wrap: 76,
ignore_garbage: false,
input,
format,
alphabet: match format {
Base32 => b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
Base64 => b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=+/",
},
}
}
pub fn line_wrap(mut self, wrap: usize) -> Self {
self.line_wrap = wrap;
self
}
pub fn ignore_garbage(mut self, ignore: bool) -> Self {
self.ignore_garbage = ignore;
self
}
pub fn decode(&mut self) -> DecodeResult {
let mut buf = vec![];
self.input.read_to_end(&mut buf)?;
if self.ignore_garbage {
buf.retain(|c| self.alphabet.contains(c));
} else {
buf.retain(|&c| c != b'\r' && c != b'\n');
};
decode(self.format, &buf)
}
pub fn encode(&mut self) -> String {
let mut buf: Vec<u8> = vec![];
self.input.read_to_end(&mut buf).unwrap();
encode(self.format, buf.as_slice())
}
}
// NOTE: this will likely be phased out at some point
pub fn wrap_print<R: Read>(data: &Data<R>, res: String) {
let stdout = io::stdout();
wrap_write(stdout.lock(), data.line_wrap, res).unwrap();
}
pub fn wrap_write<W: Write>(mut writer: W, line_wrap: usize, res: String) -> io::Result<()> {
use std::cmp::min;
if line_wrap == 0 {
return write!(writer, "{}", res);
}
let mut start = 0;
while start < res.len() {
let end = min(start + line_wrap, res.len());
writeln!(writer, "{}", &res[start..end])?;
start = end;
}
Ok(())
}

View file

@ -0,0 +1,270 @@
// This file is part of the uutils coreutils package.
//
// (c) Jian Zeng <anonymousknight96@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (vars) Passwd cstr fnam gecos ngroups
//! Get password/group file entry
//!
//! # Examples:
//!
//! ```
//! use uucore::entries::{self, Locate};
//!
//! let root_group = if cfg!(any(target_os = "linux", target_os = "android")) {
//! "root"
//! } else {
//! "wheel"
//! };
//!
//! assert_eq!("root", entries::uid2usr(0).unwrap());
//! assert_eq!(0, entries::usr2uid("root").unwrap());
//! assert!(entries::gid2grp(0).is_ok());
//! assert!(entries::grp2gid(root_group).is_ok());
//!
//! assert!(entries::Passwd::locate(0).is_ok());
//! assert!(entries::Passwd::locate("0").is_ok());
//! assert!(entries::Passwd::locate("root").is_ok());
//!
//! assert!(entries::Group::locate(0).is_ok());
//! assert!(entries::Group::locate("0").is_ok());
//! assert!(entries::Group::locate(root_group).is_ok());
//! ```
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
use libc::time_t;
use libc::{c_char, c_int, gid_t, uid_t};
use libc::{getgrgid, getgrnam, getgroups, getpwnam, getpwuid, group, passwd};
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::io::Error as IOError;
use std::io::ErrorKind;
use std::io::Result as IOResult;
use std::ptr;
extern "C" {
fn getgrouplist(
name: *const c_char,
gid: gid_t,
groups: *mut gid_t,
ngroups: *mut c_int,
) -> c_int;
}
pub fn get_groups() -> IOResult<Vec<gid_t>> {
let ngroups = unsafe { getgroups(0, ptr::null_mut()) };
if ngroups == -1 {
return Err(IOError::last_os_error());
}
let mut groups = Vec::with_capacity(ngroups as usize);
let ngroups = unsafe { getgroups(ngroups, groups.as_mut_ptr()) };
if ngroups == -1 {
Err(IOError::last_os_error())
} else {
unsafe {
groups.set_len(ngroups as usize);
}
Ok(groups)
}
}
pub struct Passwd {
inner: passwd,
}
macro_rules! cstr2cow {
($v:expr) => {
unsafe { CStr::from_ptr($v).to_string_lossy() }
};
}
impl Passwd {
/// AKA passwd.pw_name
pub fn name(&self) -> Cow<str> {
cstr2cow!(self.inner.pw_name)
}
/// AKA passwd.pw_uid
pub fn uid(&self) -> uid_t {
self.inner.pw_uid
}
/// AKA passwd.pw_gid
pub fn gid(&self) -> gid_t {
self.inner.pw_gid
}
/// AKA passwd.pw_gecos
pub fn user_info(&self) -> Cow<str> {
cstr2cow!(self.inner.pw_gecos)
}
/// AKA passwd.pw_shell
pub fn user_shell(&self) -> Cow<str> {
cstr2cow!(self.inner.pw_shell)
}
/// AKA passwd.pw_dir
pub fn user_dir(&self) -> Cow<str> {
cstr2cow!(self.inner.pw_dir)
}
/// AKA passwd.pw_passwd
pub fn user_passwd(&self) -> Cow<str> {
cstr2cow!(self.inner.pw_passwd)
}
/// AKA passwd.pw_class
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
pub fn user_access_class(&self) -> Cow<str> {
cstr2cow!(self.inner.pw_class)
}
/// AKA passwd.pw_change
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
pub fn passwd_change_time(&self) -> time_t {
self.inner.pw_change
}
/// AKA passwd.pw_expire
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
pub fn expiration(&self) -> time_t {
self.inner.pw_expire
}
pub fn as_inner(&self) -> &passwd {
&self.inner
}
pub fn into_inner(self) -> passwd {
self.inner
}
pub fn belongs_to(&self) -> Vec<gid_t> {
let mut ngroups: c_int = 8;
let mut groups = Vec::with_capacity(ngroups as usize);
let gid = self.inner.pw_gid;
let name = self.inner.pw_name;
unsafe {
if getgrouplist(name, gid, groups.as_mut_ptr(), &mut ngroups) == -1 {
groups.resize(ngroups as usize, 0);
getgrouplist(name, gid, groups.as_mut_ptr(), &mut ngroups);
}
groups.set_len(ngroups as usize);
}
groups.truncate(ngroups as usize);
groups
}
}
pub struct Group {
inner: group,
}
impl Group {
/// AKA group.gr_name
pub fn name(&self) -> Cow<str> {
cstr2cow!(self.inner.gr_name)
}
/// AKA group.gr_gid
pub fn gid(&self) -> gid_t {
self.inner.gr_gid
}
pub fn as_inner(&self) -> &group {
&self.inner
}
pub fn into_inner(self) -> group {
self.inner
}
}
/// Fetch desired entry.
pub trait Locate<K> {
fn locate(key: K) -> IOResult<Self>
where
Self: ::std::marker::Sized;
}
macro_rules! f {
($fnam:ident, $fid:ident, $t:ident, $st:ident) => {
impl Locate<$t> for $st {
fn locate(k: $t) -> IOResult<Self> {
unsafe {
let data = $fid(k);
if !data.is_null() {
Ok($st {
inner: ptr::read(data as *const _),
})
} else {
Err(IOError::new(
ErrorKind::NotFound,
format!("No such id: {}", k),
))
}
}
}
}
impl<'a> Locate<&'a str> for $st {
fn locate(k: &'a str) -> IOResult<Self> {
if let Ok(id) = k.parse::<$t>() {
let data = unsafe { $fid(id) };
if !data.is_null() {
Ok($st {
inner: unsafe { ptr::read(data as *const _) },
})
} else {
Err(IOError::new(
ErrorKind::NotFound,
format!("No such id: {}", id),
))
}
} else {
unsafe {
let data = $fnam(CString::new(k).unwrap().as_ptr());
if !data.is_null() {
Ok($st {
inner: ptr::read(data as *const _),
})
} else {
Err(IOError::new(
ErrorKind::NotFound,
format!("Not found: {}", k),
))
}
}
}
}
}
};
}
f!(getpwnam, getpwuid, uid_t, Passwd);
f!(getgrnam, getgrgid, gid_t, Group);
#[inline]
pub fn uid2usr(id: uid_t) -> IOResult<String> {
Passwd::locate(id).map(|p| p.name().into_owned())
}
#[inline]
pub fn gid2grp(id: gid_t) -> IOResult<String> {
Group::locate(id).map(|p| p.name().into_owned())
}
#[inline]
pub fn usr2uid(name: &str) -> IOResult<uid_t> {
Passwd::locate(name).map(|p| p.uid())
}
#[inline]
pub fn grp2gid(name: &str) -> IOResult<gid_t> {
Group::locate(name).map(|p| p.gid())
}

View file

@ -0,0 +1,273 @@
// This file is part of the uutils coreutils package.
//
// (c) Joseph Crail <jbcrail@gmail.com>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[cfg(windows)]
extern crate dunce;
#[cfg(target_os = "redox")]
extern crate termion;
#[cfg(unix)]
use libc::{
mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR,
S_IXGRP, S_IXOTH, S_IXUSR,
};
use std::borrow::Cow;
use std::env;
use std::fs;
#[cfg(target_os = "redox")]
use std::io;
use std::io::Result as IOResult;
use std::io::{Error, ErrorKind};
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::MetadataExt;
use std::path::{Component, Path, PathBuf};
#[cfg(unix)]
macro_rules! has {
($mode:expr, $perm:expr) => {
$mode & ($perm as u32) != 0
};
}
pub fn resolve_relative_path(path: &Path) -> Cow<Path> {
if path.components().all(|e| e != Component::ParentDir) {
return path.into();
}
let root = Component::RootDir.as_os_str();
let mut result = env::current_dir().unwrap_or(PathBuf::from(root));
for comp in path.components() {
match comp {
Component::ParentDir => {
if let Ok(p) = result.read_link() {
result = p;
}
result.pop();
}
Component::CurDir => (),
Component::RootDir | Component::Normal(_) | Component::Prefix(_) => {
result.push(comp.as_os_str())
}
}
}
result.into()
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CanonicalizeMode {
None,
Normal,
Existing,
Missing,
}
fn resolve<P: AsRef<Path>>(original: P) -> IOResult<PathBuf> {
const MAX_LINKS_FOLLOWED: u32 = 255;
let mut followed = 0;
let mut result = original.as_ref().to_path_buf();
loop {
if followed == MAX_LINKS_FOLLOWED {
return Err(Error::new(
ErrorKind::InvalidInput,
"maximum links followed",
));
}
match fs::symlink_metadata(&result) {
Err(e) => return Err(e),
Ok(ref m) if !m.file_type().is_symlink() => break,
Ok(..) => {
followed += 1;
match fs::read_link(&result) {
Ok(path) => {
result.pop();
result.push(path);
}
Err(e) => {
return Err(e);
}
}
}
}
}
Ok(result)
}
pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) -> IOResult<PathBuf> {
// Create an absolute path
let original = original.as_ref();
let original = if original.is_absolute() {
original.to_path_buf()
} else {
dunce::canonicalize(env::current_dir().unwrap())
.unwrap()
.join(original)
};
let mut result = PathBuf::new();
let mut parts = vec![];
// Split path by directory separator; add prefix (Windows-only) and root
// directory to final path buffer; add remaining parts to temporary
// vector for canonicalization.
for part in original.components() {
match part {
Component::Prefix(_) | Component::RootDir => {
result.push(part.as_os_str());
}
Component::CurDir => (),
Component::ParentDir => {
parts.pop();
}
Component::Normal(_) => {
parts.push(part.as_os_str());
}
}
}
// Resolve the symlinks where possible
if !parts.is_empty() {
for part in parts[..parts.len() - 1].iter() {
result.push(part);
if can_mode == CanonicalizeMode::None {
continue;
}
match resolve(&result) {
Err(e) => match can_mode {
CanonicalizeMode::Missing => continue,
_ => return Err(e),
},
Ok(path) => {
result.pop();
result.push(path);
}
}
}
result.push(parts.last().unwrap());
match resolve(&result) {
Err(e) => {
if can_mode == CanonicalizeMode::Existing {
return Err(e);
}
}
Ok(path) => {
result.pop();
result.push(path);
}
}
}
Ok(result)
}
#[cfg(unix)]
pub fn is_stdin_interactive() -> bool {
unsafe { libc::isatty(libc::STDIN_FILENO) == 1 }
}
#[cfg(windows)]
pub fn is_stdin_interactive() -> bool {
false
}
#[cfg(target_os = "redox")]
pub fn is_stdin_interactive() -> bool {
termion::is_tty(&io::stdin())
}
#[cfg(unix)]
pub fn is_stdout_interactive() -> bool {
unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 }
}
#[cfg(windows)]
pub fn is_stdout_interactive() -> bool {
false
}
#[cfg(target_os = "redox")]
pub fn is_stdout_interactive() -> bool {
termion::is_tty(&io::stdout())
}
#[cfg(unix)]
pub fn is_stderr_interactive() -> bool {
unsafe { libc::isatty(libc::STDERR_FILENO) == 1 }
}
#[cfg(windows)]
pub fn is_stderr_interactive() -> bool {
false
}
#[cfg(target_os = "redox")]
pub fn is_stderr_interactive() -> bool {
termion::is_tty(&io::stderr())
}
#[cfg(not(unix))]
#[allow(unused_variables)]
pub fn display_permissions(metadata: &fs::Metadata) -> String {
String::from("---------")
}
#[cfg(unix)]
pub fn display_permissions(metadata: &fs::Metadata) -> String {
let mode: mode_t = metadata.mode() as mode_t;
display_permissions_unix(mode as u32)
}
#[cfg(unix)]
pub fn display_permissions_unix(mode: u32) -> String {
let mut result = String::with_capacity(9);
result.push(if has!(mode, S_IRUSR) { 'r' } else { '-' });
result.push(if has!(mode, S_IWUSR) { 'w' } else { '-' });
result.push(if has!(mode, S_ISUID) {
if has!(mode, S_IXUSR) {
's'
} else {
'S'
}
} else if has!(mode, S_IXUSR) {
'x'
} else {
'-'
});
result.push(if has!(mode, S_IRGRP) { 'r' } else { '-' });
result.push(if has!(mode, S_IWGRP) { 'w' } else { '-' });
result.push(if has!(mode, S_ISGID) {
if has!(mode, S_IXGRP) {
's'
} else {
'S'
}
} else if has!(mode, S_IXGRP) {
'x'
} else {
'-'
});
result.push(if has!(mode, S_IROTH) { 'r' } else { '-' });
result.push(if has!(mode, S_IWOTH) { 'w' } else { '-' });
result.push(if has!(mode, S_ISVTX) {
if has!(mode, S_IXOTH) {
't'
} else {
'T'
}
} else if has!(mode, S_IXOTH) {
'x'
} else {
'-'
});
result
}

View file

@ -0,0 +1,131 @@
// This file is part of the uutils coreutils package.
//
// (c) Alex Lyon <arcterus@mail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (vars) fperm srwx
pub fn parse_numeric(fperm: u32, mut mode: &str) -> Result<u32, String> {
let (op, pos) = parse_op(mode, Some('='))?;
mode = mode[pos..].trim_start_matches('0');
if mode.len() > 4 {
Err(format!("mode is too large ({} > 7777)", mode))
} else {
match u32::from_str_radix(mode, 8) {
Ok(change) => Ok(match op {
'+' => fperm | change,
'-' => fperm & !change,
'=' => change,
_ => unreachable!(),
}),
Err(err) => Err(err.to_string()),
}
}
}
pub fn parse_symbolic(
mut fperm: u32,
mut mode: &str,
considering_dir: bool,
) -> Result<u32, String> {
#[cfg(unix)]
use libc::umask;
#[cfg(target_os = "redox")]
unsafe fn umask(_mask: u32) -> u32 {
// XXX Redox does not currently have umask
0
}
let (mask, pos) = parse_levels(mode);
if pos == mode.len() {
return Err(format!("invalid mode ({})", mode));
}
let respect_umask = pos == 0;
let last_umask = unsafe { umask(0) };
mode = &mode[pos..];
while mode.len() > 0 {
let (op, pos) = parse_op(mode, None)?;
mode = &mode[pos..];
let (mut srwx, pos) = parse_change(mode, fperm, considering_dir);
if respect_umask {
srwx &= !(last_umask as u32);
}
mode = &mode[pos..];
match op {
'+' => fperm |= srwx & mask,
'-' => fperm &= !(srwx & mask),
'=' => fperm = (fperm & !mask) | (srwx & mask),
_ => unreachable!(),
}
}
unsafe {
umask(last_umask);
}
Ok(fperm)
}
fn parse_levels(mode: &str) -> (u32, usize) {
let mut mask = 0;
let mut pos = 0;
for ch in mode.chars() {
mask |= match ch {
'u' => 0o7700,
'g' => 0o7070,
'o' => 0o7007,
'a' => 0o7777,
_ => break,
};
pos += 1;
}
if pos == 0 {
mask = 0o7777; // default to 'a'
}
(mask, pos)
}
fn parse_op(mode: &str, default: Option<char>) -> Result<(char, usize), String> {
match mode.chars().next() {
Some(ch) => match ch {
'+' | '-' | '=' => Ok((ch, 1)),
_ => match default {
Some(ch) => Ok((ch, 0)),
None => Err(format!(
"invalid operator (expected +, -, or =, but found {})",
ch
)),
},
},
None => Err("unexpected end of mode".to_owned()),
}
}
fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) {
let mut srwx = fperm & 0o7000;
let mut pos = 0;
for ch in mode.chars() {
match ch {
'r' => srwx |= 0o444,
'w' => srwx |= 0o222,
'x' => srwx |= 0o111,
'X' => {
if considering_dir || (fperm & 0o0111) != 0 {
srwx |= 0o111
}
}
's' => srwx |= 0o4000 | 0o2000,
't' => srwx |= 0o1000,
'u' => srwx = (fperm & 0o700) | ((fperm >> 3) & 0o070) | ((fperm >> 6) & 0o007),
'g' => srwx = ((fperm << 3) & 0o700) | (fperm & 0o070) | ((fperm >> 3) & 0o007),
'o' => srwx = ((fperm << 6) & 0o700) | ((fperm << 3) & 0o070) | (fperm & 0o007),
_ => break,
};
pos += 1;
}
if pos == 0 {
srwx = 0;
}
(srwx, pos)
}

View file

@ -0,0 +1,43 @@
// This file is part of the uutils coreutils package.
//
// (c) Alex Lyon <arcterus@mail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (vars) NANOS numstr
use std::time::Duration;
pub fn from_str(string: &str) -> Result<Duration, String> {
let len = string.len();
if len == 0 {
return Err("empty string".to_owned());
}
let slice = &string[..len - 1];
let (numstr, times) = match string.chars().next_back().unwrap() {
's' | 'S' => (slice, 1),
'm' | 'M' => (slice, 60),
'h' | 'H' => (slice, 60 * 60),
'd' | 'D' => (slice, 60 * 60 * 24),
val => {
if !val.is_alphabetic() {
(string, 1)
} else if string == "inf" || string == "infinity" {
("inf", 1)
} else {
return Err(format!("invalid time interval '{}'", string));
}
}
};
let num = match numstr.parse::<f64>() {
Ok(m) => m,
Err(e) => return Err(format!("invalid time interval '{}': {}", string, e)),
};
const NANOS_PER_SEC: u32 = 1_000_000_000;
let whole_secs = num.trunc();
let nanos = (num.fract() * (NANOS_PER_SEC as f64)).trunc();
let duration = Duration::new(whole_secs as u64, nanos as u32);
Ok(duration * times)
}

View file

@ -0,0 +1,146 @@
// This file is part of the uutils coreutils package.
//
// (c) Maciej Dziardziel <fiedzia@gmail.com>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore (vars) cvar exitstatus
// spell-checker:ignore (sys/unix) WIFSIGNALED
use libc::{c_int, gid_t, pid_t, uid_t};
use std::fmt;
use std::io;
use std::process::Child;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::{Duration, Instant};
pub fn geteuid() -> uid_t {
unsafe { libc::geteuid() }
}
pub fn getegid() -> gid_t {
unsafe { libc::getegid() }
}
pub fn getgid() -> gid_t {
unsafe { libc::getgid() }
}
pub fn getuid() -> uid_t {
unsafe { libc::getuid() }
}
// This is basically sys::unix::process::ExitStatus
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ExitStatus {
Code(i32),
Signal(i32),
}
impl ExitStatus {
fn from_status(status: c_int) -> ExitStatus {
if status & 0x7F != 0 {
// WIFSIGNALED(status) == terminating by/with unhandled signal
ExitStatus::Signal(status & 0x7F)
} else {
ExitStatus::Code(status & 0xFF00 >> 8)
}
}
pub fn success(&self) -> bool {
match *self {
ExitStatus::Code(code) => code == 0,
_ => false,
}
}
pub fn code(&self) -> Option<i32> {
match *self {
ExitStatus::Code(code) => Some(code),
_ => None,
}
}
pub fn signal(&self) -> Option<i32> {
match *self {
ExitStatus::Signal(code) => Some(code),
_ => None,
}
}
}
impl fmt::Display for ExitStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ExitStatus::Code(code) => write!(f, "exit code: {}", code),
ExitStatus::Signal(code) => write!(f, "exit code: {}", code),
}
}
}
/// Missing methods for Child objects
pub trait ChildExt {
/// Send a signal to a Child process.
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
/// Wait for a process to finish or return after the specified duration.
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>>;
}
impl ChildExt for Child {
fn send_signal(&mut self, signal: usize) -> io::Result<()> {
if unsafe { libc::kill(self.id() as pid_t, signal as i32) } != 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>> {
// The result will be written to that Option, protected by a Mutex
// Then the Condvar will be signaled
let state = Arc::new((
Mutex::new(Option::None::<io::Result<ExitStatus>>),
Condvar::new(),
));
// Start the waiting thread
let state_th = state.clone();
let pid_th = self.id();
thread::spawn(move || {
let &(ref lock_th, ref cvar_th) = &*state_th;
// Child::wait() would need a &mut to self, can't use that...
// use waitpid() directly, with our own ExitStatus
let mut status: c_int = 0;
let r = unsafe { libc::waitpid(pid_th as i32, &mut status, 0) };
// Fill the Option and notify on the Condvar
let mut exitstatus_th = lock_th.lock().unwrap();
if r != pid_th as c_int {
*exitstatus_th = Some(Err(io::Error::last_os_error()));
} else {
let s = ExitStatus::from_status(status);
*exitstatus_th = Some(Ok(s));
}
cvar_th.notify_one();
});
// Main thread waits
let &(ref lock, ref cvar) = &*state;
let mut exitstatus = lock.lock().unwrap();
// Condvar::wait_timeout_ms() can wake too soon, in this case wait again
let start = Instant::now();
loop {
if let Some(exitstatus) = exitstatus.take() {
return exitstatus.map(Some);
}
if start.elapsed() >= timeout {
return Ok(None);
}
let cvar_timeout = timeout - start.elapsed();
exitstatus = cvar.wait_timeout(exitstatus, cvar_timeout).unwrap().0;
}
}
}

View file

@ -0,0 +1,390 @@
// This file is part of the uutils coreutils package.
//
// (c) Maciej Dziardziel <fiedzia@gmail.com>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore (vars/api) fcntl setrlimit setitimer
// spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGPIPE SIGPROF SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTERM SIGTRAP SIGTSTP SIGTTIN SIGTTOU SIGURG SIGUSR SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT TSTP TTIN TTOU VTALRM XCPU XFSZ
pub static DEFAULT_SIGNAL: usize = 15;
pub struct Signal<'a> {
pub name: &'a str,
pub value: usize,
}
/*
Linux Programmer's Manual
1 HUP 2 INT 3 QUIT 4 ILL 5 TRAP 6 ABRT 7 BUS
8 FPE 9 KILL 10 USR1 11 SEGV 12 USR2 13 PIPE 14 ALRM
15 TERM 16 STKFLT 17 CHLD 18 CONT 19 STOP 20 TSTP 21 TTIN
22 TTOU 23 URG 24 XCPU 25 XFSZ 26 VTALRM 27 PROF 28 WINCH
29 POLL 30 PWR 31 SYS
*/
#[cfg(target_os = "linux")]
pub static ALL_SIGNALS: [Signal<'static>; 31] = [
Signal {
name: "HUP",
value: 1,
},
Signal {
name: "INT",
value: 2,
},
Signal {
name: "QUIT",
value: 3,
},
Signal {
name: "ILL",
value: 4,
},
Signal {
name: "TRAP",
value: 5,
},
Signal {
name: "ABRT",
value: 6,
},
Signal {
name: "BUS",
value: 7,
},
Signal {
name: "FPE",
value: 8,
},
Signal {
name: "KILL",
value: 9,
},
Signal {
name: "USR1",
value: 10,
},
Signal {
name: "SEGV",
value: 11,
},
Signal {
name: "USR2",
value: 12,
},
Signal {
name: "PIPE",
value: 13,
},
Signal {
name: "ALRM",
value: 14,
},
Signal {
name: "TERM",
value: 15,
},
Signal {
name: "STKFLT",
value: 16,
},
Signal {
name: "CHLD",
value: 17,
},
Signal {
name: "CONT",
value: 18,
},
Signal {
name: "STOP",
value: 19,
},
Signal {
name: "TSTP",
value: 20,
},
Signal {
name: "TTIN",
value: 21,
},
Signal {
name: "TTOU",
value: 22,
},
Signal {
name: "URG",
value: 23,
},
Signal {
name: "XCPU",
value: 24,
},
Signal {
name: "XFSZ",
value: 25,
},
Signal {
name: "VTALRM",
value: 26,
},
Signal {
name: "PROF",
value: 27,
},
Signal {
name: "WINCH",
value: 28,
},
Signal {
name: "POLL",
value: 29,
},
Signal {
name: "PWR",
value: 30,
},
Signal {
name: "SYS",
value: 31,
},
];
/*
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/signal.3.html
No Name Default Action Description
1 SIGHUP terminate process terminal line hangup
2 SIGINT terminate process interrupt program
3 SIGQUIT create core image quit program
4 SIGILL create core image illegal instruction
5 SIGTRAP create core image trace trap
6 SIGABRT create core image abort program (formerly SIGIOT)
7 SIGEMT create core image emulate instruction executed
8 SIGFPE create core image floating-point exception
9 SIGKILL terminate process kill program
10 SIGBUS create core image bus error
11 SIGSEGV create core image segmentation violation
12 SIGSYS create core image non-existent system call invoked
13 SIGPIPE terminate process write on a pipe with no reader
14 SIGALRM terminate process real-time timer expired
15 SIGTERM terminate process software termination signal
16 SIGURG discard signal urgent condition present on socket
17 SIGSTOP stop process stop (cannot be caught or ignored)
18 SIGTSTP stop process stop signal generated from keyboard
19 SIGCONT discard signal continue after stop
20 SIGCHLD discard signal child status has changed
21 SIGTTIN stop process background read attempted from control terminal
22 SIGTTOU stop process background write attempted to control terminal
23 SIGIO discard signal I/O is possible on a descriptor (see fcntl(2))
24 SIGXCPU terminate process cpu time limit exceeded (see setrlimit(2))
25 SIGXFSZ terminate process file size limit exceeded (see setrlimit(2))
26 SIGVTALRM terminate process virtual time alarm (see setitimer(2))
27 SIGPROF terminate process profiling timer alarm (see setitimer(2))
28 SIGWINCH discard signal Window size change
29 SIGINFO discard signal status request from keyboard
30 SIGUSR1 terminate process User defined signal 1
31 SIGUSR2 terminate process User defined signal 2
*/
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
pub static ALL_SIGNALS: [Signal<'static>; 31] = [
Signal {
name: "HUP",
value: 1,
},
Signal {
name: "INT",
value: 2,
},
Signal {
name: "QUIT",
value: 3,
},
Signal {
name: "ILL",
value: 4,
},
Signal {
name: "TRAP",
value: 5,
},
Signal {
name: "ABRT",
value: 6,
},
Signal {
name: "EMT",
value: 7,
},
Signal {
name: "FPE",
value: 8,
},
Signal {
name: "KILL",
value: 9,
},
Signal {
name: "BUS",
value: 10,
},
Signal {
name: "SEGV",
value: 11,
},
Signal {
name: "SYS",
value: 12,
},
Signal {
name: "PIPE",
value: 13,
},
Signal {
name: "ALRM",
value: 14,
},
Signal {
name: "TERM",
value: 15,
},
Signal {
name: "URG",
value: 16,
},
Signal {
name: "STOP",
value: 17,
},
Signal {
name: "TSTP",
value: 18,
},
Signal {
name: "CONT",
value: 19,
},
Signal {
name: "CHLD",
value: 20,
},
Signal {
name: "TTIN",
value: 21,
},
Signal {
name: "TTOU",
value: 22,
},
Signal {
name: "IO",
value: 23,
},
Signal {
name: "XCPU",
value: 24,
},
Signal {
name: "XFSZ",
value: 25,
},
Signal {
name: "VTALRM",
value: 26,
},
Signal {
name: "PROF",
value: 27,
},
Signal {
name: "WINCH",
value: 28,
},
Signal {
name: "INFO",
value: 29,
},
Signal {
name: "USR1",
value: 30,
},
Signal {
name: "USR2",
value: 31,
},
];
pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option<usize> {
if let Ok(value) = signal_name_or_value.parse() {
if is_signal(value) {
return Some(value);
} else {
return None;
}
}
let signal_name = signal_name_or_value.trim_start_matches("SIG");
ALL_SIGNALS
.iter()
.find(|s| s.name == signal_name)
.map(|s| s.value)
}
#[inline(always)]
pub fn is_signal(num: usize) -> bool {
// Named signals start at 1
num <= ALL_SIGNALS.len()
}
#[test]
fn signals_all_contiguous() {
for (i, signal) in ALL_SIGNALS.iter().enumerate() {
assert_eq!(signal.value, i + 1);
}
}
#[test]
fn signals_all_are_signal() {
for signal in &ALL_SIGNALS {
assert!(is_signal(signal.value));
}
}
#[test]
fn signal_by_value() {
assert_eq!(signal_by_name_or_value("0"), Some(0));
for signal in &ALL_SIGNALS {
assert_eq!(
signal_by_name_or_value(&signal.value.to_string()),
Some(signal.value)
);
}
}
#[test]
fn signal_by_short_name() {
for signal in &ALL_SIGNALS {
assert_eq!(signal_by_name_or_value(signal.name), Some(signal.value));
}
}
#[test]
fn signal_by_long_name() {
for signal in &ALL_SIGNALS {
assert_eq!(
signal_by_name_or_value(&format!("SIG{}", signal.name)),
Some(signal.value)
);
}
}

View file

@ -0,0 +1,271 @@
// This file is part of the uutils coreutils package.
//
// (c) Jian Zeng <anonymousknight96@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//
//! Aims to provide platform-independent methods to obtain login records
//!
//! **ONLY** support linux, macos and freebsd for the time being
//!
//! # Examples:
//!
//! ```
//! use uucore::utmpx::Utmpx;
//! for ut in Utmpx::iter_all_records() {
//! if ut.is_user_process() {
//! println!("{}: {}", ut.host(), ut.user())
//! }
//! }
//! ```
//!
//! Specifying the path to login record:
//!
//! ```
//! use uucore::utmpx::Utmpx;
//! for ut in Utmpx::iter_all_records().read_from("/some/where/else") {
//! if ut.is_user_process() {
//! println!("{}: {}", ut.host(), ut.user())
//! }
//! }
//! ```
pub extern crate time;
use self::time::{Timespec, Tm};
use std::ffi::CString;
use std::io::Error as IOError;
use std::io::Result as IOResult;
use std::ptr;
pub use self::ut::*;
use libc::utmpx;
// pub use libc::getutxid;
// pub use libc::getutxline;
// pub use libc::pututxline;
pub use libc::endutxent;
pub use libc::getutxent;
pub use libc::setutxent;
#[cfg(any(target_os = "macos", target_os = "linux"))]
pub use libc::utmpxname;
#[cfg(target_os = "freebsd")]
pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int {
0
}
// In case the c_char array doesn't end with NULL
macro_rules! chars2string {
($arr:expr) => {
$arr.iter()
.take_while(|i| **i > 0)
.map(|&i| i as u8 as char)
.collect::<String>()
};
}
#[cfg(target_os = "linux")]
mod ut {
pub static DEFAULT_FILE: &str = "/var/run/utmp";
pub use libc::__UT_HOSTSIZE as UT_HOSTSIZE;
pub use libc::__UT_LINESIZE as UT_LINESIZE;
pub use libc::__UT_NAMESIZE as UT_NAMESIZE;
pub const UT_IDSIZE: usize = 4;
pub use libc::ACCOUNTING;
pub use libc::BOOT_TIME;
pub use libc::DEAD_PROCESS;
pub use libc::EMPTY;
pub use libc::INIT_PROCESS;
pub use libc::LOGIN_PROCESS;
pub use libc::NEW_TIME;
pub use libc::OLD_TIME;
pub use libc::RUN_LVL;
pub use libc::USER_PROCESS;
}
#[cfg(target_os = "macos")]
mod ut {
pub static DEFAULT_FILE: &str = "/var/run/utmpx";
pub use libc::_UTX_HOSTSIZE as UT_HOSTSIZE;
pub use libc::_UTX_IDSIZE as UT_IDSIZE;
pub use libc::_UTX_LINESIZE as UT_LINESIZE;
pub use libc::_UTX_USERSIZE as UT_NAMESIZE;
pub use libc::ACCOUNTING;
pub use libc::BOOT_TIME;
pub use libc::DEAD_PROCESS;
pub use libc::EMPTY;
pub use libc::INIT_PROCESS;
pub use libc::LOGIN_PROCESS;
pub use libc::NEW_TIME;
pub use libc::OLD_TIME;
pub use libc::RUN_LVL;
pub use libc::SHUTDOWN_TIME;
pub use libc::SIGNATURE;
pub use libc::USER_PROCESS;
}
#[cfg(target_os = "freebsd")]
mod ut {
pub static DEFAULT_FILE: &str = "";
pub const UT_LINESIZE: usize = 16;
pub const UT_NAMESIZE: usize = 32;
pub const UT_IDSIZE: usize = 8;
pub const UT_HOSTSIZE: usize = 128;
pub use libc::BOOT_TIME;
pub use libc::DEAD_PROCESS;
pub use libc::EMPTY;
pub use libc::INIT_PROCESS;
pub use libc::LOGIN_PROCESS;
pub use libc::NEW_TIME;
pub use libc::OLD_TIME;
pub use libc::SHUTDOWN_TIME;
pub use libc::USER_PROCESS;
}
pub struct Utmpx {
inner: utmpx,
}
impl Utmpx {
/// A.K.A. ut.ut_type
pub fn record_type(&self) -> i16 {
self.inner.ut_type as i16
}
/// A.K.A. ut.ut_pid
pub fn pid(&self) -> i32 {
self.inner.ut_pid as i32
}
/// A.K.A. ut.ut_id
pub fn terminal_suffix(&self) -> String {
chars2string!(self.inner.ut_id)
}
/// A.K.A. ut.ut_user
pub fn user(&self) -> String {
chars2string!(self.inner.ut_user)
}
/// A.K.A. ut.ut_host
pub fn host(&self) -> String {
chars2string!(self.inner.ut_host)
}
/// A.K.A. ut.ut_line
pub fn tty_device(&self) -> String {
chars2string!(self.inner.ut_line)
}
/// A.K.A. ut.ut_tv
pub fn login_time(&self) -> Tm {
time::at(Timespec::new(
self.inner.ut_tv.tv_sec as i64,
self.inner.ut_tv.tv_usec as i32,
))
}
/// A.K.A. ut.ut_exit
///
/// Return (e_termination, e_exit)
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn exit_status(&self) -> (i16, i16) {
(self.inner.ut_exit.e_termination, self.inner.ut_exit.e_exit)
}
/// A.K.A. ut.ut_exit
///
/// Return (0, 0) on Non-Linux platform
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn exit_status(&self) -> (i16, i16) {
(0, 0)
}
/// Consumes the `Utmpx`, returning the underlying C struct utmpx
pub fn into_inner(self) -> utmpx {
self.inner
}
pub fn is_user_process(&self) -> bool {
!self.user().is_empty() && self.record_type() == USER_PROCESS
}
/// Canonicalize host name using DNS
pub fn canon_host(&self) -> IOResult<String> {
const AI_CANONNAME: libc::c_int = 0x2;
let host = self.host();
let host = host.split(':').nth(0).unwrap();
let hints = libc::addrinfo {
ai_flags: AI_CANONNAME,
ai_family: 0,
ai_socktype: 0,
ai_protocol: 0,
ai_addrlen: 0,
ai_addr: ptr::null_mut(),
ai_canonname: ptr::null_mut(),
ai_next: ptr::null_mut(),
};
let c_host = CString::new(host).unwrap();
let mut res = ptr::null_mut();
let status = unsafe {
libc::getaddrinfo(
c_host.as_ptr(),
ptr::null(),
&hints as *const _,
&mut res as *mut _,
)
};
if status == 0 {
let info: libc::addrinfo = unsafe { ptr::read(res as *const _) };
// http://lists.gnu.org/archive/html/bug-coreutils/2006-09/msg00300.html
// says Darwin 7.9.0 getaddrinfo returns 0 but sets
// res->ai_canonname to NULL.
let ret = if info.ai_canonname.is_null() {
Ok(String::from(host))
} else {
Ok(unsafe { CString::from_raw(info.ai_canonname).into_string().unwrap() })
};
unsafe {
libc::freeaddrinfo(res);
}
ret
} else {
Err(IOError::last_os_error())
}
}
pub fn iter_all_records() -> UtmpxIter {
UtmpxIter
}
}
/// Iterator of login records
pub struct UtmpxIter;
impl UtmpxIter {
/// Sets the name of the utmpx-format file for the other utmpx functions to access.
///
/// If not set, default record file will be used(file path depends on the target OS)
pub fn read_from(self, f: &str) -> Self {
let res = unsafe { utmpxname(CString::new(f).unwrap().as_ptr()) };
if res != 0 {
println!("Warning: {}", IOError::last_os_error());
}
unsafe {
setutxent();
}
self
}
}
impl Iterator for UtmpxIter {
type Item = Utmpx;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let res = getutxent();
if !res.is_null() {
Some(Utmpx {
inner: ptr::read(res as *const _),
})
} else {
endutxent();
None
}
}
}
}

View file

@ -0,0 +1,39 @@
// This file is part of the uutils coreutils package.
//
// (c) Peter Atashian <retep998@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::ffi::{OsStr, OsString};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
pub trait ToWide {
fn to_wide(&self) -> Vec<u16>;
fn to_wide_null(&self) -> Vec<u16>;
}
impl<T> ToWide for T
where
T: AsRef<OsStr>,
{
fn to_wide(&self) -> Vec<u16> {
self.as_ref().encode_wide().collect()
}
fn to_wide_null(&self) -> Vec<u16> {
self.as_ref().encode_wide().chain(Some(0)).collect()
}
}
pub trait FromWide {
fn from_wide(wide: &[u16]) -> Self;
fn from_wide_null(wide: &[u16]) -> Self;
}
impl FromWide for String {
fn from_wide(wide: &[u16]) -> String {
OsString::from_wide(wide).to_string_lossy().into_owned()
}
fn from_wide_null(wide: &[u16]) -> String {
let len = wide.iter().take_while(|&&c| c != 0).count();
OsString::from_wide(&wide[..len])
.to_string_lossy()
.into_owned()
}
}

View file

@ -0,0 +1,146 @@
use self::platform::*;
use std::io::{self, Write};
mod platform;
pub trait AsRawObject {
fn as_raw_object(&self) -> RawObject;
}
pub trait FromRawObject: Sized {
unsafe fn from_raw_object(obj: RawObject) -> Option<Self>;
}
// TODO: also make a SpliceWriter that takes an input fd and and output fd and uses splice() to
// transfer data
// TODO: make a TeeWriter or something that takes an input fd and two output fds and uses tee() to
// transfer to both output fds
enum InnerZeroCopyWriter<T: Write + Sized> {
Platform(PlatformZeroCopyWriter),
Standard(T),
}
impl<T: Write + Sized> Write for InnerZeroCopyWriter<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
InnerZeroCopyWriter::Platform(ref mut writer) => writer.write(buf),
InnerZeroCopyWriter::Standard(ref mut writer) => writer.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
InnerZeroCopyWriter::Platform(ref mut writer) => writer.flush(),
InnerZeroCopyWriter::Standard(ref mut writer) => writer.flush(),
}
}
}
pub struct ZeroCopyWriter<T: Write + AsRawObject + Sized> {
/// This field is never used, but we need it to drop file descriptors
#[allow(dead_code)]
raw_obj_owner: Option<T>,
inner: InnerZeroCopyWriter<T>,
}
struct TransformContainer<'a, A: Write + AsRawObject + Sized, B: Write + Sized> {
/// This field is never used and probably could be converted into PhantomData, but might be
/// useful for restructuring later (at the moment it's basically left over from an earlier
/// design)
#[allow(dead_code)]
original: Option<&'a mut A>,
transformed: Option<B>,
}
impl<'a, A: Write + AsRawObject + Sized, B: Write + Sized> Write for TransformContainer<'a, A, B> {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.transformed.as_mut().unwrap().write(bytes)
}
fn flush(&mut self) -> io::Result<()> {
self.transformed.as_mut().unwrap().flush()
}
}
impl<'a, A: Write + AsRawObject + Sized, B: Write + Sized> AsRawObject
for TransformContainer<'a, A, B>
{
fn as_raw_object(&self) -> RawObject {
panic!("Test should never be used")
}
}
impl<T: Write + AsRawObject + Sized> ZeroCopyWriter<T> {
pub fn new(writer: T) -> Self {
let raw_obj = writer.as_raw_object();
match unsafe { PlatformZeroCopyWriter::new(raw_obj) } {
Ok(inner) => ZeroCopyWriter {
raw_obj_owner: Some(writer),
inner: InnerZeroCopyWriter::Platform(inner),
},
_ => {
// creating the splice writer failed for whatever reason, so just make a default
// writer
ZeroCopyWriter {
raw_obj_owner: None,
inner: InnerZeroCopyWriter::Standard(writer),
}
}
}
}
pub fn with_default<'a: 'b, 'b, F, W>(
writer: &'a mut T,
func: F,
) -> ZeroCopyWriter<impl Write + AsRawObject + Sized + 'b>
where
F: Fn(&'a mut T) -> W,
W: Write + Sized + 'b,
{
let raw_obj = writer.as_raw_object();
match unsafe { PlatformZeroCopyWriter::new(raw_obj) } {
Ok(inner) => ZeroCopyWriter {
raw_obj_owner: Some(TransformContainer {
original: Some(writer),
transformed: None,
}),
inner: InnerZeroCopyWriter::Platform(inner),
},
_ => {
// XXX: should func actually consume writer and leave it up to the user to save the value?
// maybe provide a default stdin method then? in some cases it would make more sense for the
// value to be consumed
let real_writer = func(writer);
ZeroCopyWriter {
raw_obj_owner: None,
inner: InnerZeroCopyWriter::Standard(TransformContainer {
original: None,
transformed: Some(real_writer),
}),
}
}
}
}
// XXX: unsure how to get something like this working without allocating, so not providing it
/*pub fn stdout() -> ZeroCopyWriter<impl Write + AsRawObject + Sized> {
let mut stdout = io::stdout();
ZeroCopyWriter::with_default(&mut stdout, |stdout| {
stdout.lock()
})
}*/
}
impl<T: Write + AsRawObject + Sized> Write for ZeroCopyWriter<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}

View file

@ -0,0 +1,21 @@
#[cfg(any(target_os = "linux", target_os = "android"))]
pub use self::linux::*;
#[cfg(unix)]
pub use self::unix::*;
#[cfg(windows)]
pub use self::windows::*;
// Add any operating systems we support here
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub use self::default::*;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux;
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
// Add any operating systems we support here
#[cfg(not(any(target_os = "linux", target_os = "android")))]
mod default;

View file

@ -0,0 +1,21 @@
use crate::features::zero_copy::RawObject;
use std::io::{self, Write};
pub struct PlatformZeroCopyWriter;
impl PlatformZeroCopyWriter {
pub unsafe fn new(_obj: RawObject) -> Result<Self, ()> {
Err(())
}
}
impl Write for PlatformZeroCopyWriter {
fn write(&mut self, _bytes: &[u8]) -> io::Result<usize> {
panic!("should never occur")
}
fn flush(&mut self) -> io::Result<()> {
panic!("should never occur")
}
}

View file

@ -0,0 +1,112 @@
use std::io::{self, Write};
use std::os::unix::io::RawFd;
use libc::{O_APPEND, S_IFIFO, S_IFREG};
use nix::errno::Errno;
use nix::fcntl::{fcntl, splice, vmsplice, FcntlArg, SpliceFFlags};
use nix::sys::stat::{fstat, FileStat};
use nix::sys::uio::IoVec;
use nix::unistd::pipe;
use platform_info::{PlatformInfo, Uname};
use crate::features::zero_copy::{FromRawObject, RawObject};
lazy_static::lazy_static! {
static ref IN_WSL: bool = {
let info = PlatformInfo::new().unwrap();
info.release().contains("Microsoft")
};
}
pub struct PlatformZeroCopyWriter {
raw_obj: RawObject,
read_pipe: RawFd,
write_pipe: RawFd,
write_fn: fn(&mut PlatformZeroCopyWriter, &[IoVec<&[u8]>], usize) -> io::Result<usize>,
}
impl PlatformZeroCopyWriter {
pub unsafe fn new(raw_obj: RawObject) -> nix::Result<Self> {
if *IN_WSL {
// apparently WSL hasn't implemented vmsplice(), causing writes to fail
// thus, we will just say zero-copy doesn't work there rather than working
// around it
return Err(nix::Error::from(Errno::EOPNOTSUPP));
}
let stat_info: FileStat = fstat(raw_obj)?;
let access_mode: libc::c_int = fcntl(raw_obj, FcntlArg::F_GETFL)?;
let is_regular = (stat_info.st_mode & S_IFREG) != 0;
let is_append = (access_mode & O_APPEND) != 0;
let is_fifo = (stat_info.st_mode & S_IFIFO) != 0;
if is_regular && !is_append {
let (read_pipe, write_pipe) = pipe()?;
Ok(PlatformZeroCopyWriter {
raw_obj,
read_pipe,
write_pipe,
write_fn: write_regular,
})
} else if is_fifo {
Ok(PlatformZeroCopyWriter {
raw_obj,
read_pipe: Default::default(),
write_pipe: Default::default(),
write_fn: write_fifo,
})
} else {
// FIXME: how to error?
Err(nix::Error::from(Errno::UnknownErrno))
}
}
}
impl FromRawObject for PlatformZeroCopyWriter {
unsafe fn from_raw_object(obj: RawObject) -> Option<Self> {
PlatformZeroCopyWriter::new(obj).ok()
}
}
impl Write for PlatformZeroCopyWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let iovec = &[IoVec::from_slice(buf)];
let func = self.write_fn;
func(self, iovec, buf.len())
}
fn flush(&mut self) -> io::Result<()> {
// XXX: not sure if we need anything else
Ok(())
}
}
fn write_regular(
writer: &mut PlatformZeroCopyWriter,
iovec: &[IoVec<&[u8]>],
len: usize,
) -> io::Result<usize> {
vmsplice(writer.write_pipe, iovec, SpliceFFlags::empty())
.and_then(|_| {
splice(
writer.read_pipe,
None,
writer.raw_obj,
None,
len,
SpliceFFlags::empty(),
)
})
.map_err(|_| io::Error::last_os_error())
}
fn write_fifo(
writer: &mut PlatformZeroCopyWriter,
iovec: &[IoVec<&[u8]>],
_len: usize,
) -> io::Result<usize> {
vmsplice(writer.raw_obj, iovec, SpliceFFlags::empty()).map_err(|_| io::Error::last_os_error())
}

View file

@ -0,0 +1,18 @@
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use crate::features::zero_copy::{AsRawObject, FromRawObject};
pub type RawObject = RawFd;
impl<T: AsRawFd> AsRawObject for T {
fn as_raw_object(&self) -> RawObject {
self.as_raw_fd()
}
}
// FIXME: check if this works right
impl<T: FromRawFd> FromRawObject for T {
unsafe fn from_raw_object(obj: RawObject) -> Option<Self> {
Some(T::from_raw_fd(obj))
}
}

View file

@ -0,0 +1,19 @@
use std::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle};
use crate::features::zero_copy::{AsRawObject, FromRawObject};
pub type RawObject = RawHandle;
impl<T: AsRawHandle> AsRawObject for T {
fn as_raw_object(&self) -> RawObject {
self.as_raw_handle()
}
}
impl<T: FromRawHandle> FromRawObject for T {
unsafe fn from_raw_object(obj: RawObject) -> Option<Self> {
Some(T::from_raw_handle(obj))
}
}
// TODO: see if there's some zero-copy stuff in Windows

91
src/uucore/src/lib/lib.rs Normal file
View file

@ -0,0 +1,91 @@
// library ~ (core/bundler file)
// Copyright (C) ~ Alex Lyon <arcterus@mail.com>
// Copyright (C) ~ Roy Ivy III <rivy.dev@gmail.com>; MIT license
//## external crates
extern crate wild;
// * feature-gated external crates
#[cfg(feature = "failure")]
extern crate failure;
#[cfg(feature = "failure_derive")]
extern crate failure_derive;
#[cfg(all(feature = "lazy_static", target_os = "linux"))]
extern crate lazy_static;
#[cfg(feature = "nix")]
extern crate nix;
#[cfg(feature = "platform-info")]
extern crate platform_info;
// * feature-gated external crates (re-shared as public internal modules)
#[cfg(feature = "libc")]
pub extern crate libc;
#[cfg(feature = "winapi")]
pub extern crate winapi;
//## internal modules
mod macros; // crate macros (macro_rules-type; exported to `crate::...`)
mod features; // feature-gated code modules
mod mods; // core cross-platform modules
// * cross-platform modules
pub use crate::mods::coreopts;
pub use crate::mods::panic;
// * feature-gated modules
#[cfg(feature = "encoding")]
pub use crate::features::encoding;
#[cfg(feature = "fs")]
pub use crate::features::fs;
#[cfg(feature = "parse_time")]
pub use crate::features::parse_time;
#[cfg(feature = "zero-copy")]
pub use crate::features::zero_copy;
// * (platform-specific) feature-gated modules
// ** non-windows
#[cfg(all(not(windows), feature = "mode"))]
pub use crate::features::mode;
// ** unix-only
#[cfg(all(unix, feature = "entries"))]
pub use crate::features::entries;
#[cfg(all(unix, feature = "process"))]
pub use crate::features::process;
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
pub use crate::features::signals;
#[cfg(all(
unix,
not(target_os = "fuchsia"),
not(target_env = "musl"),
feature = "utmpx"
))]
pub use crate::features::utmpx;
// ** windows-only
#[cfg(all(windows, feature = "wide"))]
pub use crate::features::wide;
//## core functions
use std::ffi::OsString;
pub trait Args: Iterator<Item = OsString> + Sized {
fn collect_str(self) -> Vec<String> {
// FIXME: avoid unwrap()
self.map(|s| s.into_string().unwrap()).collect()
}
}
impl<T: Iterator<Item = OsString> + Sized> Args for T {}
// args() ...
pub fn args() -> impl Iterator<Item = String> {
wild::args()
}
pub fn args_os() -> impl Iterator<Item = OsString> {
wild::args_os()
}

View file

@ -0,0 +1,271 @@
// This file is part of the uutils coreutils package.
//
// (c) Alex Lyon <arcterus@mail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[macro_export]
macro_rules! executable(
() => ({
let module = module_path!();
let module = module.split("::").next().unwrap_or(module);
if &module[0..3] == "uu_" {
&module[3..]
} else {
module
}
})
);
#[macro_export]
macro_rules! show_error(
($($args:tt)+) => ({
eprint!("{}: error: ", executable!());
eprintln!($($args)+);
})
);
#[macro_export]
macro_rules! show_warning(
($($args:tt)+) => ({
eprint!("{}: warning: ", executable!());
eprintln!($($args)+);
})
);
#[macro_export]
macro_rules! show_info(
($($args:tt)+) => ({
eprint!("{}: ", executable!());
eprintln!($($args)+);
})
);
#[macro_export]
macro_rules! show_usage_error(
($($args:tt)+) => ({
eprint!("{}: ", executable!());
eprintln!($($args)+);
eprintln!("Try '{} --help' for more information.", executable!());
})
);
#[macro_export]
macro_rules! crash(
($exit_code:expr, $($args:tt)+) => ({
show_error!($($args)+);
::std::process::exit($exit_code)
})
);
#[macro_export]
macro_rules! exit(
($exit_code:expr) => ({
::std::process::exit($exit_code)
})
);
#[macro_export]
macro_rules! crash_if_err(
($exit_code:expr, $exp:expr) => (
match $exp {
Ok(m) => m,
Err(f) => crash!($exit_code, "{}", f),
}
)
);
#[macro_export]
macro_rules! return_if_err(
($exit_code:expr, $exp:expr) => (
match $exp {
Ok(m) => m,
Err(f) => {
show_error!("{}", f);
return $exit_code;
}
}
)
);
#[macro_export]
macro_rules! safe_write(
($fd:expr, $($args:tt)+) => (
match write!($fd, $($args)+) {
Ok(_) => {}
Err(f) => panic!(f.to_string())
}
)
);
#[macro_export]
macro_rules! safe_writeln(
($fd:expr, $($args:tt)+) => (
match writeln!($fd, $($args)+) {
Ok(_) => {}
Err(f) => panic!(f.to_string())
}
)
);
#[macro_export]
macro_rules! safe_unwrap(
($exp:expr) => (
match $exp {
Ok(m) => m,
Err(f) => crash!(1, "{}", f.to_string())
}
)
);
//-- message templates
//-- message templates : general
#[macro_export]
macro_rules! snippet_list_join_oxford {
($conjunction:expr, $valOne:expr, $valTwo:expr) => (
format!("{}, {} {}", $valOne, $conjunction, $valTwo)
);
($conjunction:expr, $valOne:expr, $valTwo:expr $(, $remaining_values:expr)*) => (
format!("{}, {}", $valOne, snippet_list_join_inner!($conjunction, $valTwo $(, $remaining_values)*))
);
}
#[macro_export]
macro_rules! snippet_list_join_or {
($valOne:expr, $valTwo:expr) => (
format!("{} or {}", $valOne, $valTwo)
);
($valOne:expr, $valTwo:expr $(, $remaining_values:expr)*) => (
format!("{}, {}", $valOne, snippet_list_join_oxford!("or", $valTwo $(, $remaining_values)*))
);
}
//-- message templates : invalid input
#[macro_export]
macro_rules! msg_invalid_input {
($reason: expr) => {
format!("invalid input: {}", $reason)
};
}
#[macro_export]
macro_rules! snippet_no_file_at_path {
($path:expr) => {
format!("nonexistent path {}", $path)
};
}
// -- message templates : invalid input : flag
#[macro_export]
macro_rules! msg_invalid_opt_use {
($about:expr, $flag:expr) => {
msg_invalid_input!(format!("The '{}' option {}", $flag, $about))
};
($about:expr, $long_flag:expr, $short_flag:expr) => {
msg_invalid_input!(format!(
"The '{}' ('{}') option {}",
$long_flag, $short_flag, $about
))
};
}
#[macro_export]
macro_rules! msg_opt_only_usable_if {
($clause:expr, $flag:expr) => {
msg_invalid_opt_use!(format!("only usable if {}", $clause), $flag)
};
($clause:expr, $long_flag:expr, $short_flag:expr) => {
msg_invalid_opt_use!(
format!("only usable if {}", $clause),
$long_flag,
$short_flag
)
};
}
#[macro_export]
macro_rules! msg_opt_invalid_should_be {
($expects:expr, $received:expr, $flag:expr) => {
msg_invalid_opt_use!(
format!("expects {}, but was provided {}", $expects, $received),
$flag
)
};
($expects:expr, $received:expr, $long_flag:expr, $short_flag:expr) => {
msg_invalid_opt_use!(
format!("expects {}, but was provided {}", $expects, $received),
$long_flag,
$short_flag
)
};
}
// -- message templates : invalid input : args
#[macro_export]
macro_rules! msg_arg_invalid_value {
($expects:expr, $received:expr) => {
msg_invalid_input!(format!(
"expects its argument to be {}, but was provided {}",
$expects, $received
))
};
}
#[macro_export]
macro_rules! msg_args_invalid_value {
($expects:expr, $received:expr) => {
msg_invalid_input!(format!(
"expects its arguments to be {}, but was provided {}",
$expects, $received
))
};
($msg:expr) => {
msg_invalid_input!($msg)
};
}
#[macro_export]
macro_rules! msg_args_nonexistent_file {
($received:expr) => {
msg_args_invalid_value!("paths to files", snippet_no_file_at_path!($received))
};
}
#[macro_export]
macro_rules! msg_wrong_number_of_arguments {
() => {
msg_args_invalid_value!("wrong number of arguments")
};
($min:expr, $max:expr) => {
msg_args_invalid_value!(format!("expects {}-{} arguments", $min, $max))
};
($exact:expr) => {
if $exact == 1 {
msg_args_invalid_value!("expects 1 argument")
} else {
msg_args_invalid_value!(format!("expects {} arguments", $exact))
}
};
}
// -- message templates : invalid input : input combinations
#[macro_export]
macro_rules! msg_expects_one_of {
($valOne:expr $(, $remaining_values:expr)*) => (
msg_invalid_input!(format!("expects one of {}", snippet_list_join_or!($valOne $(, $remaining_values)*)))
);
}
#[macro_export]
macro_rules! msg_expects_no_more_than_one_of {
($valOne:expr $(, $remaining_values:expr)*) => (
msg_invalid_input!(format!("expects no more than one of {}", snippet_list_join_or!($valOne $(, $remaining_values)*))) ;
);
}

View file

@ -0,0 +1,4 @@
// mods ~ cross-platforms modules (core/bundler file)
pub mod coreopts;
pub mod panic;

View file

@ -0,0 +1,143 @@
extern crate getopts;
pub struct HelpText<'a> {
pub name: &'a str,
pub version: &'a str,
pub syntax: &'a str,
pub summary: &'a str,
pub long_help: &'a str,
pub display_usage: bool,
}
pub struct CoreOptions<'a> {
options: getopts::Options,
help_text: HelpText<'a>,
}
impl<'a> CoreOptions<'a> {
pub fn new(help_text: HelpText<'a>) -> Self {
let mut ret = CoreOptions {
options: getopts::Options::new(),
help_text,
};
ret.options
.optflag("", "help", "print usage information")
.optflag("", "version", "print name and version number");
ret
}
pub fn optflagopt(
&mut self,
short_name: &str,
long_name: &str,
desc: &str,
hint: &str,
) -> &mut CoreOptions<'a> {
self.options.optflagopt(short_name, long_name, desc, hint);
self
}
pub fn optflag(
&mut self,
short_name: &str,
long_name: &str,
desc: &str,
) -> &mut CoreOptions<'a> {
self.options.optflag(short_name, long_name, desc);
self
}
pub fn optflagmulti(
&mut self,
short_name: &str,
long_name: &str,
desc: &str,
) -> &mut CoreOptions<'a> {
self.options.optflagmulti(short_name, long_name, desc);
self
}
pub fn optopt(
&mut self,
short_name: &str,
long_name: &str,
desc: &str,
hint: &str,
) -> &mut CoreOptions<'a> {
self.options.optopt(short_name, long_name, desc, hint);
self
}
pub fn optmulti(
&mut self,
short_name: &str,
long_name: &str,
desc: &str,
hint: &str,
) -> &mut CoreOptions<'a> {
self.options.optmulti(short_name, long_name, desc, hint);
self
}
pub fn usage(&self, summary: &str) -> String {
self.options.usage(summary)
}
pub fn parse(&mut self, args: Vec<String>) -> getopts::Matches {
let matches = match self.options.parse(&args[1..]) {
Ok(m) => Some(m),
Err(f) => {
eprint!("{}: error: ", self.help_text.name);
eprintln!("{}", f);
::std::process::exit(1);
}
}
.unwrap();
if matches.opt_present("help") {
let usage_str = if self.help_text.display_usage {
format!(
"\n {}\n\n Reference\n",
self.options.usage(self.help_text.summary)
)
.replace("Options:", " Options:")
} else {
String::new()
};
print!(
"
{0} {1}
{0} {2}
{3}{4}
",
self.help_text.name,
self.help_text.version,
self.help_text.syntax,
usage_str,
self.help_text.long_help
);
crate::exit!(0);
} else if matches.opt_present("version") {
println!("{} {}", self.help_text.name, self.help_text.version);
crate::exit!(0);
}
matches
}
}
#[macro_export]
macro_rules! app {
($syntax: expr, $summary: expr, $long_help: expr) => {
uucore::coreopts::CoreOptions::new(uucore::coreopts::HelpText {
name: executable!(),
version: env!("CARGO_PKG_VERSION"),
syntax: $syntax,
summary: $summary,
long_help: $long_help,
display_usage: true,
})
};
($syntax: expr, $summary: expr, $long_help: expr, $display_usage: expr) => {
uucore::coreopts::CoreOptions::new(uucore::coreopts::HelpText {
name: executable!(),
version: env!("CARGO_PKG_VERSION"),
syntax: $syntax,
summary: $summary,
long_help: $long_help,
display_usage: $display_usage,
})
};
}

View file

@ -0,0 +1,17 @@
use std::panic;
//## SIGPIPE handling background/discussions ...
//* `uutils` ~ <https://github.com/uutils/coreutils/issues/374> , <https://github.com/uutils/coreutils/pull/1106>
//* rust and `rg` ~ <https://github.com/rust-lang/rust/issues/62569> , <https://github.com/BurntSushi/ripgrep/issues/200> , <https://github.com/crev-dev/cargo-crev/issues/287>
pub fn mute_sigpipe_panic() {
let hook = panic::take_hook();
panic::set_hook(Box::new(move |info| {
if let Some(res) = info.payload().downcast_ref::<String>() {
if res.contains("Broken pipe") {
return;
}
}
hook(info)
}));
}

View file

@ -0,0 +1,26 @@
[package]
name = "uucore_procs"
version = "0.0.4"
authors = ["Roy Ivy III <rivy.dev@gmail.com>"]
license = "MIT"
description = "uutils ~ 'uucore' proc-macros"
homepage = "https://github.com/uutils/uucore/uucore_procs"
repository = "https://github.com/uutils/uucore/uucore_procs"
# readme = "README.md"
keywords = ["cross-platform", "proc-macros", "uucore", "uutils"]
# categories = ["os"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version="1.0" }
[features]
default = []
## non-default features
debug = ["syn/extra-traits"] ## add Debug traits to syn structs (for `println!("{:?}", ...)`)

View file

@ -0,0 +1,86 @@
#![allow(dead_code)] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0
#![allow(unused_macros)] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0
// Copyright (C) ~ Roy Ivy III <rivy.dev@gmail.com>; MIT license
extern crate proc_macro;
//## rust proc-macro background info
//* ref: <https://dev.to/naufraghi/procedural-macro-in-rust-101-k3f> @@ <http://archive.is/Vbr5e>
//* ref: [path construction from LitStr](https://oschwald.github.io/maxminddb-rust/syn/struct.LitStr.html) @@ <http://archive.is/8YDua>
//## proc_dbg macro
//* used to help debug the compile-time proc_macro code
#[cfg(feature = "debug")]
macro_rules! proc_dbg {
($x:expr) => {
dbg!($x)
};
}
#[cfg(not(feature = "debug"))]
macro_rules! proc_dbg {
($x:expr) => {};
}
//## main!()
// main!( EXPR )
// generates a `main()` function for utilities within the uutils group
// EXPR == syn::Expr::Lit::String | syn::Expr::Path::Ident ~ EXPR contains the lexical path to the utility `uumain()` function
//* NOTE: EXPR is ultimately expected to be a multi-segment lexical path (eg, `crate::func`); so, if a single segment path is provided, a trailing "::uumain" is automatically added
//* for more generic use (and future use of "eager" macros), EXPR may be in either STRING or IDENT form
struct Tokens {
expr: syn::Expr,
}
impl syn::parse::Parse for Tokens {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Tokens {
expr: input.parse()?,
})
}
}
#[proc_macro]
#[cfg(not(test))] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0
pub fn main(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
let Tokens { expr } = syn::parse_macro_input!(stream as Tokens);
proc_dbg!(&expr);
const ARG_PANIC_TEXT: &str =
"expected ident lexical path (or a literal string version) to 'uumain()' as argument";
// match EXPR as a string literal or an ident path, o/w panic!()
let mut expr = match expr {
syn::Expr::Lit(expr_lit) => match expr_lit.lit {
syn::Lit::Str(ref lit_str) => lit_str.parse::<syn::ExprPath>().unwrap(),
_ => panic!(ARG_PANIC_TEXT),
},
syn::Expr::Path(expr_path) => expr_path,
_ => panic!(ARG_PANIC_TEXT),
};
proc_dbg!(&expr);
// for a single segment ExprPath argument, add trailing '::uumain' segment
if expr.path.segments.len() < 2 {
expr = syn::parse_quote!( #expr::uumain );
};
proc_dbg!(&expr);
let f = quote::quote! { #expr(uucore::args_os()) };
proc_dbg!(&f);
// generate a uutils utility `main()` function, tailored for the calling utility
let result = quote::quote! {
fn main() {
use std::io::Write;
uucore::panic::mute_sigpipe_panic(); // suppress extraneous error output for SIGPIPE failures/panics
let code = #f; // execute utility code
std::io::stdout().flush().expect("could not flush stdout"); // (defensively) flush stdout for utility prior to exit; see <https://github.com/rust-lang/rust/issues/23818>
std::process::exit(code);
}
};
proc_macro::TokenStream::from(result)
}