Skip to main content

linuxutils_system/
pipesz.rs

1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use std::{
7    fs::File,
8    os::unix::io::{AsRawFd, RawFd},
9    process::ExitCode,
10};
11
12/// Set or examine pipe and FIFO buffer sizes.
13///
14/// Uses fcntl(2) with F_GETPIPE_SZ / F_SETPIPE_SZ to get or set the
15/// internal buffer sizes of pipes and FIFOs.
16#[derive(Parser)]
17#[command(name = "pipesz", about = "Set or examine pipe and FIFO buffer sizes")]
18pub struct Args {
19    /// Report pipe buffer sizes
20    #[arg(short = 'g', long)]
21    get: bool,
22
23    /// Set pipe buffer size in bytes
24    #[arg(short = 's', long)]
25    set: Option<i32>,
26
27    /// Operate on a FIFO/pipe at this path (repeatable)
28    #[arg(short = 'f', long = "file", action = clap::ArgAction::Append)]
29    file: Vec<String>,
30
31    /// Operate on this file descriptor number (repeatable)
32    #[arg(short = 'n', long = "fd", action = clap::ArgAction::Append)]
33    fd: Vec<RawFd>,
34
35    /// Shorthand for --fd 0
36    #[arg(short = 'i', long)]
37    stdin: bool,
38
39    /// Shorthand for --fd 1
40    #[arg(short = 'o', long)]
41    stdout: bool,
42
43    /// Shorthand for --fd 2
44    #[arg(short = 'e', long)]
45    stderr: bool,
46
47    /// Exit immediately on any error
48    #[arg(short = 'c', long)]
49    check: bool,
50
51    /// Suppress non-fatal warnings
52    #[arg(short = 'q', long)]
53    quiet: bool,
54
55    /// Emit headers (get) or print actual sizes (set)
56    #[arg(short = 'v', long)]
57    verbose: bool,
58
59    /// Command to execute after setting pipe sizes
60    #[arg(trailing_var_arg = true)]
61    command: Vec<String>,
62}
63
64fn get_pipe_sz(fd: RawFd) -> Result<i32, std::io::Error> {
65    let ret = unsafe { libc::fcntl(fd, libc::F_GETPIPE_SZ) };
66    if ret < 0 {
67        Err(std::io::Error::last_os_error())
68    } else {
69        Ok(ret)
70    }
71}
72
73fn set_pipe_sz(fd: RawFd, size: i32) -> Result<i32, std::io::Error> {
74    let ret = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, size) };
75    if ret < 0 {
76        Err(std::io::Error::last_os_error())
77    } else {
78        Ok(ret)
79    }
80}
81
82fn get_unread(fd: RawFd) -> i32 {
83    let mut count: libc::c_int = 0;
84    if unsafe { libc::ioctl(fd, libc::FIONREAD, &mut count) } < 0 {
85        0
86    } else {
87        count
88    }
89}
90
91fn fd_name(fd: RawFd) -> String {
92    match fd {
93        0 => "stdin".to_string(),
94        1 => "stdout".to_string(),
95        2 => "stderr".to_string(),
96        n => format!("fd {n}"),
97    }
98}
99
100fn collect_fds(args: &Args) -> Vec<(String, RawFd, Option<File>)> {
101    let mut fds: Vec<(String, RawFd, Option<File>)> = Vec::new();
102
103    if args.stdin {
104        fds.push(("stdin".to_string(), 0, None));
105    }
106    if args.stdout {
107        fds.push(("stdout".to_string(), 1, None));
108    }
109    if args.stderr {
110        fds.push(("stderr".to_string(), 2, None));
111    }
112    for &n in &args.fd {
113        fds.push((fd_name(n), n, None));
114    }
115    for path in &args.file {
116        match File::options().read(true).write(true).open(path) {
117            Ok(f) => {
118                let raw = f.as_raw_fd();
119                fds.push((path.clone(), raw, Some(f)));
120            }
121            Err(e) => {
122                if !args.quiet {
123                    eprintln!("pipesz: {path}: {e}");
124                }
125            }
126        }
127    }
128    fds
129}
130
131pub fn run(args: Args) -> ExitCode {
132    let mut fds = collect_fds(&args);
133
134    if args.get {
135        if fds.is_empty() {
136            fds.push(("stdin".to_string(), 0, None));
137        }
138        if args.verbose {
139            println!("name\tsize\tunread");
140        }
141        let mut failed = false;
142        for (name, fd, _file) in &fds {
143            match get_pipe_sz(*fd) {
144                Ok(sz) => {
145                    let unread = get_unread(*fd);
146                    println!("{name}\t{sz}\t{unread}");
147                }
148                Err(e) => {
149                    eprintln!("pipesz: {name}: {e}");
150                    failed = true;
151                    if args.check {
152                        return ExitCode::FAILURE;
153                    }
154                }
155            }
156        }
157        return if failed {
158            ExitCode::FAILURE
159        } else {
160            ExitCode::SUCCESS
161        };
162    }
163
164    if let Some(size) = args.set {
165        if fds.is_empty() {
166            fds.push(("stdout".to_string(), 1, None));
167        }
168        let mut failed = false;
169        for (name, fd, _file) in &fds {
170            match set_pipe_sz(*fd, size) {
171                Ok(actual) => {
172                    if args.verbose {
173                        println!("{name}\t{actual}");
174                    }
175                }
176                Err(e) => {
177                    eprintln!("pipesz: {name}: {e}");
178                    failed = true;
179                    if args.check {
180                        return ExitCode::FAILURE;
181                    }
182                }
183            }
184        }
185
186        if !args.command.is_empty() {
187            let err = std::os::unix::process::CommandExt::exec(
188                std::process::Command::new(&args.command[0])
189                    .args(&args.command[1..]),
190            );
191            eprintln!("pipesz: failed to exec '{}': {err}", args.command[0]);
192            return ExitCode::FAILURE;
193        }
194
195        return if failed {
196            ExitCode::FAILURE
197        } else {
198            ExitCode::SUCCESS
199        };
200    }
201
202    eprintln!("pipesz: one of --get or --set must be specified");
203    ExitCode::FAILURE
204}