mirror of
https://github.com/uutils/coreutils
synced 2024-11-17 02:08:09 +00:00
Merge 'uucore' repository source code back into 'coreutils'
This commit is contained in:
commit
e15df35e87
24 changed files with 2762 additions and 0 deletions
60
src/uucore/Cargo.toml
Normal file
60
src/uucore/Cargo.toml
Normal 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"]
|
32
src/uucore/src/lib/features.rs
Normal file
32
src/uucore/src/lib/features.rs
Normal 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;
|
132
src/uucore/src/lib/features/encoding.rs
Normal file
132
src/uucore/src/lib/features/encoding.rs
Normal 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(())
|
||||
}
|
270
src/uucore/src/lib/features/entries.rs
Normal file
270
src/uucore/src/lib/features/entries.rs
Normal 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())
|
||||
}
|
273
src/uucore/src/lib/features/fs.rs
Normal file
273
src/uucore/src/lib/features/fs.rs
Normal 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
|
||||
}
|
131
src/uucore/src/lib/features/mode.rs
Normal file
131
src/uucore/src/lib/features/mode.rs
Normal 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)
|
||||
}
|
43
src/uucore/src/lib/features/parse_time.rs
Normal file
43
src/uucore/src/lib/features/parse_time.rs
Normal 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)
|
||||
}
|
146
src/uucore/src/lib/features/process.rs
Normal file
146
src/uucore/src/lib/features/process.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
390
src/uucore/src/lib/features/signals.rs
Normal file
390
src/uucore/src/lib/features/signals.rs
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
271
src/uucore/src/lib/features/utmpx.rs
Normal file
271
src/uucore/src/lib/features/utmpx.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
src/uucore/src/lib/features/wide.rs
Normal file
39
src/uucore/src/lib/features/wide.rs
Normal 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()
|
||||
}
|
||||
}
|
146
src/uucore/src/lib/features/zero_copy.rs
Normal file
146
src/uucore/src/lib/features/zero_copy.rs
Normal 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()
|
||||
}
|
||||
}
|
21
src/uucore/src/lib/features/zero_copy/platform.rs
Normal file
21
src/uucore/src/lib/features/zero_copy/platform.rs
Normal 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;
|
21
src/uucore/src/lib/features/zero_copy/platform/default.rs
Normal file
21
src/uucore/src/lib/features/zero_copy/platform/default.rs
Normal 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")
|
||||
}
|
||||
}
|
112
src/uucore/src/lib/features/zero_copy/platform/linux.rs
Normal file
112
src/uucore/src/lib/features/zero_copy/platform/linux.rs
Normal 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())
|
||||
}
|
18
src/uucore/src/lib/features/zero_copy/platform/unix.rs
Normal file
18
src/uucore/src/lib/features/zero_copy/platform/unix.rs
Normal 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))
|
||||
}
|
||||
}
|
19
src/uucore/src/lib/features/zero_copy/platform/windows.rs
Normal file
19
src/uucore/src/lib/features/zero_copy/platform/windows.rs
Normal 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
91
src/uucore/src/lib/lib.rs
Normal 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()
|
||||
}
|
271
src/uucore/src/lib/macros.rs
Normal file
271
src/uucore/src/lib/macros.rs
Normal 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)*))) ;
|
||||
);
|
||||
}
|
4
src/uucore/src/lib/mods.rs
Normal file
4
src/uucore/src/lib/mods.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
// mods ~ cross-platforms modules (core/bundler file)
|
||||
|
||||
pub mod coreopts;
|
||||
pub mod panic;
|
143
src/uucore/src/lib/mods/coreopts.rs
Normal file
143
src/uucore/src/lib/mods/coreopts.rs
Normal 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,
|
||||
})
|
||||
};
|
||||
}
|
17
src/uucore/src/lib/mods/panic.rs
Normal file
17
src/uucore/src/lib/mods/panic.rs
Normal 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)
|
||||
}));
|
||||
}
|
26
src/uucore/src/uucore_procs/Cargo.toml
Normal file
26
src/uucore/src/uucore_procs/Cargo.toml
Normal 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!("{:?}", ...)`)
|
86
src/uucore/src/uucore_procs/src/lib.rs
Normal file
86
src/uucore/src/uucore_procs/src/lib.rs
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue