1use std::ffi::{CString, OsStr, OsString};
6use std::fs::File;
7use std::io::Write;
8use std::os::fd::{AsFd, BorrowedFd, IntoRawFd, OwnedFd};
9use std::os::unix::prelude::{AsRawFd, OpenOptionsExt, OsStrExt};
10use std::path::Path;
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::sync::Arc;
13use std::{env, io};
14
15use nix::errno::Errno;
16use nix::libc::{login_tty, O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ, VEOF};
17use nix::pty::{openpty, Winsize};
18use nix::sys::select::{select, FdSet};
19use nix::sys::signal::{killpg, Signal};
20use nix::sys::stat::Mode;
21use nix::sys::termios::{
22 cfmakeraw, tcgetattr, tcsetattr, LocalFlags, OutputFlags, SetArg, Termios,
23};
24use nix::sys::time::TimeVal;
25use nix::sys::wait::{waitpid, WaitStatus};
26use nix::unistd::{dup2, execvp, fork, isatty, mkfifo, read, tcgetpgrp, write, ForkResult, Pid};
27use signal_hook::consts::SIGWINCH;
28
29pub struct TtySpawn {
31 options: Option<SpawnOptions>,
32}
33
34impl TtySpawn {
35 pub fn new<S: AsRef<OsStr>>(cmd: S) -> TtySpawn {
37 TtySpawn {
38 options: Some(SpawnOptions {
39 command: vec![cmd.as_ref().to_os_string()],
40 stdin_file: None,
41 stdout_file: None,
42 script_mode: false,
43 no_flush: false,
44 no_echo: false,
45 no_pager: false,
46 no_raw: false,
47 }),
48 }
49 }
50
51 pub fn new_cmdline<S: AsRef<OsStr>, I: Iterator<Item = S>>(mut cmdline: I) -> Self {
60 let mut rv = TtySpawn::new(cmdline.next().expect("empty cmdline"));
61 rv.args(cmdline);
62 rv
63 }
64
65 pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
67 self.options_mut().command.push(arg.as_ref().to_os_string());
68 self
69 }
70
71 pub fn args<S: AsRef<OsStr>, I: Iterator<Item = S>>(&mut self, args: I) -> &mut Self {
73 for arg in args {
74 self.arg(arg);
75 }
76 self
77 }
78
79 pub fn stdin_file(&mut self, f: File) -> &mut Self {
94 self.options_mut().stdin_file = Some(f);
95 self
96 }
97
98 pub fn stdin_path<P: AsRef<Path>>(&mut self, path: P) -> Result<&mut Self, io::Error> {
100 let path = path.as_ref();
101 mkfifo_atomic(path)?;
102 Ok(self.stdin_file(
105 File::options()
106 .read(true)
107 .write(true)
108 .custom_flags(O_NONBLOCK)
109 .open(path)?,
110 ))
111 }
112
113 pub fn stdout_file(&mut self, f: File) -> &mut Self {
115 self.options_mut().stdout_file = Some(f);
116 self
117 }
118
119 pub fn stdout_path<P: AsRef<Path>>(
124 &mut self,
125 path: P,
126 truncate: bool,
127 ) -> Result<&mut Self, io::Error> {
128 Ok(self.stdout_file(if !truncate {
129 File::options().append(true).create(true).open(path)?
130 } else {
131 File::options()
132 .create(true)
133 .truncate(true)
134 .write(true)
135 .open(path)?
136 }))
137 }
138
139 pub fn script_mode(&mut self, yes: bool) -> &mut Self {
146 self.options_mut().script_mode = yes;
147 self
148 }
149
150 pub fn flush(&mut self, yes: bool) -> &mut Self {
154 self.options_mut().no_flush = !yes;
155 self
156 }
157
158 pub fn echo(&mut self, yes: bool) -> &mut Self {
162 self.options_mut().no_echo = !yes;
163 self
164 }
165
166 pub fn pager(&mut self, yes: bool) -> &mut Self {
172 self.options_mut().no_pager = !yes;
173 self
174 }
175
176 pub fn raw(&mut self, yes: bool) -> &mut Self {
182 self.options_mut().no_raw = !yes;
183 self
184 }
185
186 pub fn spawn(&mut self) -> Result<i32, io::Error> {
188 Ok(spawn(
189 self.options.take().expect("builder only works once"),
190 )?)
191 }
192
193 fn options_mut(&mut self) -> &mut SpawnOptions {
194 self.options.as_mut().expect("builder only works once")
195 }
196}
197
198struct SpawnOptions {
199 command: Vec<OsString>,
200 stdin_file: Option<File>,
201 stdout_file: Option<File>,
202 script_mode: bool,
203 no_flush: bool,
204 no_echo: bool,
205 no_pager: bool,
206 no_raw: bool,
207}
208
209fn spawn(mut opts: SpawnOptions) -> Result<i32, Errno> {
216 let term_attrs = tcgetattr(io::stdin()).ok();
220 let winsize = term_attrs
221 .as_ref()
222 .and_then(|_| get_winsize(io::stdin().as_fd()));
223
224 let pty = openpty(&winsize, &term_attrs)?;
226
227 let (_restore_term, stderr_pty) = if opts.script_mode {
231 let term_attrs = tcgetattr(io::stderr()).ok();
232 let winsize = term_attrs
233 .as_ref()
234 .and_then(|_| get_winsize(io::stderr().as_fd()));
235 let stderr_pty = openpty(&winsize, &term_attrs)?;
236 (None, Some(stderr_pty))
237
238 } else if !opts.no_raw {
244 (
245 term_attrs.as_ref().map(|term_attrs| {
246 let mut raw_attrs = term_attrs.clone();
247 cfmakeraw(&mut raw_attrs);
248 raw_attrs.local_flags.remove(LocalFlags::ECHO);
249 tcsetattr(io::stdin(), SetArg::TCSAFLUSH, &raw_attrs).ok();
250 RestoreTerm(term_attrs.clone())
251 }),
252 None,
253 )
254
255 } else {
257 (None, None)
258 };
259
260 if let Ok(mut term_attrs) = tcgetattr(&pty.master) {
265 if opts.script_mode {
266 term_attrs.output_flags.remove(OutputFlags::OPOST);
267 }
268 if opts.no_echo || (opts.script_mode && !isatty(io::stdin().as_raw_fd()).unwrap_or(false)) {
269 term_attrs.local_flags.remove(LocalFlags::ECHO);
270 }
271 tcsetattr(&pty.master, SetArg::TCSAFLUSH, &term_attrs).ok();
272 }
273
274 if let ForkResult::Parent { child } = unsafe { fork()? } {
278 drop(pty.slave);
279 let stderr_pty = if let Some(stderr_pty) = stderr_pty {
280 drop(stderr_pty.slave);
281 Some(stderr_pty.master)
282 } else {
283 None
284 };
285 return communication_loop(
286 pty.master,
287 child,
288 term_attrs.is_some(),
289 opts.stdout_file.as_mut(),
290 opts.stdin_file.as_mut(),
291 stderr_pty,
292 !opts.no_flush,
293 );
294 }
295
296 if opts.no_pager || opts.script_mode {
298 unsafe {
299 env::set_var("PAGER", "cat");
300 }
301 }
302
303 let args = opts
307 .command
308 .iter()
309 .filter_map(|x| CString::new(x.as_bytes()).ok())
310 .collect::<Vec<_>>();
311
312 drop(pty.master);
313 unsafe {
314 login_tty(pty.slave.into_raw_fd());
315 if let Some(stderr_pty) = stderr_pty {
316 dup2(stderr_pty.slave.into_raw_fd(), io::stderr().as_raw_fd())?;
317 }
318 }
319
320 match execvp(&args[0], &args)? {}
323}
324
325fn communication_loop(
326 master: OwnedFd,
327 child: Pid,
328 is_tty: bool,
329 mut out_file: Option<&mut File>,
330 in_file: Option<&mut File>,
331 stderr: Option<OwnedFd>,
332 flush: bool,
333) -> Result<i32, Errno> {
334 let mut buf = [0; 4096];
335 let mut read_stdin = true;
336 let mut done = false;
337 let stdin = io::stdin();
338
339 let got_winch = Arc::new(AtomicBool::new(false));
340 if is_tty {
341 signal_hook::flag::register(SIGWINCH, Arc::clone(&got_winch)).ok();
342 }
343
344 while !done {
345 if got_winch.load(Ordering::Relaxed) {
346 forward_winsize(master.as_fd(), stderr.as_ref().map(|x| x.as_fd()))?;
347 got_winch.store(false, Ordering::Relaxed);
348 }
349
350 let mut read_fds = FdSet::new();
351 let mut timeout = TimeVal::new(1, 0);
352 read_fds.insert(master.as_fd());
353 if !read_stdin && is_tty {
354 read_stdin = true;
355 }
356 if read_stdin {
357 read_fds.insert(stdin.as_fd());
358 }
359 if let Some(ref f) = in_file {
360 read_fds.insert(f.as_fd());
361 }
362 if let Some(ref fd) = stderr {
363 read_fds.insert(fd.as_fd());
364 }
365 match select(None, Some(&mut read_fds), None, None, Some(&mut timeout)) {
366 Ok(0) | Err(Errno::EINTR | Errno::EAGAIN) => continue,
367 Ok(_) => {}
368 Err(err) => return Err(err),
369 }
370
371 if read_fds.contains(stdin.as_fd()) {
372 match read(stdin.as_raw_fd(), &mut buf) {
373 Ok(0) => {
374 send_eof_sequence(master.as_fd());
375 read_stdin = false;
376 }
377 Ok(n) => {
378 write_all(master.as_fd(), &buf[..n])?;
379 }
380 Err(Errno::EINTR | Errno::EAGAIN) => {}
381 Err(Errno::EIO) => {
383 done = true;
384 }
385 Err(err) => return Err(err),
386 };
387 }
388 if let Some(ref f) = in_file {
389 if read_fds.contains(f.as_fd()) {
390 match read(f.as_raw_fd(), &mut buf) {
394 Ok(0) | Err(Errno::EAGAIN | Errno::EINTR) => {}
395 Err(err) => return Err(err),
396 Ok(n) => {
397 write_all(master.as_fd(), &buf[..n])?;
398 }
399 }
400 }
401 }
402 if let Some(ref fd) = stderr {
403 if read_fds.contains(fd.as_fd()) {
404 match read(fd.as_raw_fd(), &mut buf) {
405 Ok(0) | Err(_) => {}
406 Ok(n) => {
407 forward_and_log(io::stderr().as_fd(), &mut out_file, &buf[..n], flush)?;
408 }
409 }
410 }
411 }
412 if read_fds.contains(master.as_fd()) {
413 match read(master.as_raw_fd(), &mut buf) {
414 Ok(0) | Err(Errno::EIO) => {
416 done = true;
417 }
418 Ok(n) => forward_and_log(io::stdout().as_fd(), &mut out_file, &buf[..n], flush)?,
419 Err(Errno::EAGAIN | Errno::EINTR) => {}
420 Err(err) => return Err(err),
421 };
422 }
423 }
424
425 Ok(match waitpid(child, None)? {
426 WaitStatus::Exited(_, status) => status,
427 WaitStatus::Signaled(_, signal, _) => 128 + signal as i32,
428 _ => 1,
429 })
430}
431
432fn forward_and_log(
433 fd: BorrowedFd,
434 out_file: &mut Option<&mut File>,
435 buf: &[u8],
436 flush: bool,
437) -> Result<(), Errno> {
438 if let Some(logfile) = out_file {
439 logfile.write_all(buf).map_err(|x| match x.raw_os_error() {
440 Some(errno) => Errno::from_raw(errno),
441 None => Errno::EINVAL,
442 })?;
443 if flush {
444 logfile.flush().ok();
445 }
446 }
447 write_all(fd, buf)?;
448 Ok(())
449}
450
451fn forward_winsize(master: BorrowedFd, stderr_master: Option<BorrowedFd>) -> Result<(), Errno> {
453 if let Some(winsize) = get_winsize(io::stdin().as_fd()) {
454 set_winsize(master, winsize).ok();
455 if let Some(second_master) = stderr_master {
456 set_winsize(second_master, winsize).ok();
457 }
458 if let Ok(pgrp) = tcgetpgrp(master) {
459 killpg(pgrp, Signal::SIGWINCH).ok();
460 }
461 }
462 Ok(())
463}
464
465fn get_winsize(fd: BorrowedFd) -> Option<Winsize> {
467 nix::ioctl_read_bad!(_get_window_size, TIOCGWINSZ, Winsize);
468 let mut size: Winsize = unsafe { std::mem::zeroed() };
469 unsafe { _get_window_size(fd.as_raw_fd(), &mut size).ok()? };
470 Some(size)
471}
472
473fn set_winsize(fd: BorrowedFd, winsize: Winsize) -> Result<(), Errno> {
475 nix::ioctl_write_ptr_bad!(_set_window_size, TIOCSWINSZ, Winsize);
476 unsafe { _set_window_size(fd.as_raw_fd(), &winsize) }?;
477 Ok(())
478}
479
480fn send_eof_sequence(fd: BorrowedFd) {
482 if let Ok(attrs) = tcgetattr(fd) {
483 if attrs.local_flags.contains(LocalFlags::ICANON) {
484 write(fd, &[attrs.control_chars[VEOF]]).ok();
485 }
486 }
487}
488
489fn write_all(fd: BorrowedFd, mut buf: &[u8]) -> Result<(), Errno> {
491 while !buf.is_empty() {
492 let n = write(fd, buf)?;
494 buf = &buf[n..];
495 }
496 Ok(())
497}
498
499fn mkfifo_atomic(path: &Path) -> Result<(), Errno> {
501 match mkfifo(path, Mode::S_IRUSR | Mode::S_IWUSR) {
502 Ok(()) | Err(Errno::EEXIST) => Ok(()),
503 Err(err) => Err(err),
504 }
505}
506
507struct RestoreTerm(Termios);
508
509impl Drop for RestoreTerm {
510 fn drop(&mut self) {
511 tcsetattr(io::stdin(), SetArg::TCSAFLUSH, &self.0).ok();
512 }
513}