use linuxutils_common::man::ManContent;
pub const MAN: ManContent = ManContent::empty();
use clap::Parser;
use std::{
fs::File,
os::unix::io::{AsRawFd, RawFd},
process::ExitCode,
};
#[derive(Parser)]
#[command(name = "pipesz", about = "Set or examine pipe and FIFO buffer sizes")]
pub struct Args {
#[arg(short = 'g', long)]
get: bool,
#[arg(short = 's', long)]
set: Option<i32>,
#[arg(short = 'f', long = "file", action = clap::ArgAction::Append)]
file: Vec<String>,
#[arg(short = 'n', long = "fd", action = clap::ArgAction::Append)]
fd: Vec<RawFd>,
#[arg(short = 'i', long)]
stdin: bool,
#[arg(short = 'o', long)]
stdout: bool,
#[arg(short = 'e', long)]
stderr: bool,
#[arg(short = 'c', long)]
check: bool,
#[arg(short = 'q', long)]
quiet: bool,
#[arg(short = 'v', long)]
verbose: bool,
#[arg(trailing_var_arg = true)]
command: Vec<String>,
}
fn get_pipe_sz(fd: RawFd) -> Result<i32, std::io::Error> {
let ret = unsafe { libc::fcntl(fd, libc::F_GETPIPE_SZ) };
if ret < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(ret)
}
}
fn set_pipe_sz(fd: RawFd, size: i32) -> Result<i32, std::io::Error> {
let ret = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, size) };
if ret < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(ret)
}
}
fn get_unread(fd: RawFd) -> i32 {
let mut count: libc::c_int = 0;
if unsafe { libc::ioctl(fd, libc::FIONREAD, &mut count) } < 0 {
0
} else {
count
}
}
fn fd_name(fd: RawFd) -> String {
match fd {
0 => "stdin".to_string(),
1 => "stdout".to_string(),
2 => "stderr".to_string(),
n => format!("fd {n}"),
}
}
fn collect_fds(args: &Args) -> Vec<(String, RawFd, Option<File>)> {
let mut fds: Vec<(String, RawFd, Option<File>)> = Vec::new();
if args.stdin {
fds.push(("stdin".to_string(), 0, None));
}
if args.stdout {
fds.push(("stdout".to_string(), 1, None));
}
if args.stderr {
fds.push(("stderr".to_string(), 2, None));
}
for &n in &args.fd {
fds.push((fd_name(n), n, None));
}
for path in &args.file {
match File::options().read(true).write(true).open(path) {
Ok(f) => {
let raw = f.as_raw_fd();
fds.push((path.clone(), raw, Some(f)));
}
Err(e) => {
if !args.quiet {
eprintln!("pipesz: {path}: {e}");
}
}
}
}
fds
}
pub fn run(args: Args) -> ExitCode {
let mut fds = collect_fds(&args);
if args.get {
if fds.is_empty() {
fds.push(("stdin".to_string(), 0, None));
}
if args.verbose {
println!("name\tsize\tunread");
}
let mut failed = false;
for (name, fd, _file) in &fds {
match get_pipe_sz(*fd) {
Ok(sz) => {
let unread = get_unread(*fd);
println!("{name}\t{sz}\t{unread}");
}
Err(e) => {
eprintln!("pipesz: {name}: {e}");
failed = true;
if args.check {
return ExitCode::FAILURE;
}
}
}
}
return if failed {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
};
}
if let Some(size) = args.set {
if fds.is_empty() {
fds.push(("stdout".to_string(), 1, None));
}
let mut failed = false;
for (name, fd, _file) in &fds {
match set_pipe_sz(*fd, size) {
Ok(actual) => {
if args.verbose {
println!("{name}\t{actual}");
}
}
Err(e) => {
eprintln!("pipesz: {name}: {e}");
failed = true;
if args.check {
return ExitCode::FAILURE;
}
}
}
}
if !args.command.is_empty() {
let err = std::os::unix::process::CommandExt::exec(
std::process::Command::new(&args.command[0])
.args(&args.command[1..]),
);
eprintln!("pipesz: failed to exec '{}': {err}", args.command[0]);
return ExitCode::FAILURE;
}
return if failed {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
};
}
eprintln!("pipesz: one of --get or --set must be specified");
ExitCode::FAILURE
}