tracexec_core/
pty.rs

1// MIT License
2
3// Copyright (c) 2018 Wez Furlong
4// Copyright (c) 2024 Levi Zim
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in all
14// copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23
24//! Modified from https://github.com/wez/wezterm/tree/main/pty
25
26#![allow(unused)]
27
28use color_eyre::eyre::{Error, bail};
29use filedescriptor::FileDescriptor;
30use nix::libc::{self, pid_t, winsize};
31use nix::unistd::{Pid, dup2, execv, fork};
32use std::cell::RefCell;
33use std::ffi::{CStr, CString, OsStr};
34
35use std::fs::File;
36use std::io::{Read, Write};
37use std::os::fd::{AsFd, OwnedFd};
38use std::os::unix::ffi::{OsStrExt, OsStringExt};
39use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
40use std::os::unix::process::CommandExt;
41use std::path::{Path, PathBuf};
42use std::process::Command;
43use std::{io, mem, ptr};
44
45// use downcast_rs::{impl_downcast, Downcast};
46
47use std::io::Result as IoResult;
48
49use crate::cmdbuilder::CommandBuilder;
50
51/// Represents the size of the visible display area in the pty
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct PtySize {
54  /// The number of lines of text
55  pub rows: u16,
56  /// The number of columns of text
57  pub cols: u16,
58  /// The width of a cell in pixels.  Note that some systems never
59  /// fill this value and ignore it.
60  pub pixel_width: u16,
61  /// The height of a cell in pixels.  Note that some systems never
62  /// fill this value and ignore it.
63  pub pixel_height: u16,
64}
65
66impl Default for PtySize {
67  fn default() -> Self {
68    Self {
69      rows: 24,
70      cols: 80,
71      pixel_width: 0,
72      pixel_height: 0,
73    }
74  }
75}
76
77/// Represents the master/control end of the pty
78pub trait MasterPty: Send {
79  /// Inform the kernel and thus the child process that the window resized.
80  /// It will update the winsize information maintained by the kernel,
81  /// and generate a signal for the child to notice and update its state.
82  fn resize(&self, size: PtySize) -> Result<(), Error>;
83  /// Retrieves the size of the pty as known by the kernel
84  fn get_size(&self) -> Result<PtySize, Error>;
85  /// Obtain a readable handle; output from the slave(s) is readable
86  /// via this stream.
87  fn try_clone_reader(&self) -> Result<Box<dyn std::io::Read + Send>, Error>;
88  /// Obtain a writable handle; writing to it will send data to the
89  /// slave end.
90  /// Dropping the writer will send EOF to the slave end.
91  /// It is invalid to take the writer more than once.
92  fn take_writer(&self) -> Result<Box<dyn std::io::Write + Send>, Error>;
93
94  /// If applicable to the type of the tty, return the local process id
95  /// of the process group or session leader
96  #[cfg(unix)]
97  fn process_group_leader(&self) -> Option<libc::pid_t>;
98
99  /// If get_termios() and process_group_leader() are both implemented and
100  /// return Some, then as_raw_fd() should return the same underlying fd
101  /// associated with the stream. This is to enable applications that
102  /// "know things" to query similar information for themselves.
103  #[cfg(unix)]
104  fn as_raw_fd(&self) -> Option<RawFd>;
105
106  #[cfg(unix)]
107  fn tty_name(&self) -> Option<std::path::PathBuf>;
108
109  /// If applicable to the type of the tty, return the termios
110  /// associated with the stream
111  #[cfg(unix)]
112  fn get_termios(&self) -> Option<nix::sys::termios::Termios> {
113    None
114  }
115}
116
117/// Represents a child process spawned into the pty.
118/// This handle can be used to wait for or terminate that child process.
119pub trait Child: std::fmt::Debug + ChildKiller + Send {
120  /// Poll the child to see if it has completed.
121  /// Does not block.
122  /// Returns None if the child has not yet terminated,
123  /// else returns its exit status.
124  fn try_wait(&mut self) -> IoResult<Option<ExitStatus>>;
125  /// Blocks execution until the child process has completed,
126  /// yielding its exit status.
127  fn wait(&mut self) -> IoResult<ExitStatus>;
128  /// Returns the process identifier of the child process,
129  /// if applicable
130  fn process_id(&self) -> Pid;
131}
132
133/// Represents the ability to signal a Child to terminate
134pub trait ChildKiller: std::fmt::Debug + Send {
135  /// Terminate the child process
136  fn kill(&mut self) -> IoResult<()>;
137
138  /// Clone an object that can be split out from the Child in order
139  /// to send it signals independently from a thread that may be
140  /// blocked in `.wait`.
141  fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync>;
142}
143
144/// Represents the exit status of a child process.
145#[derive(Debug, Clone)]
146pub struct ExitStatus {
147  code: u32,
148  signal: Option<String>,
149}
150
151impl ExitStatus {
152  /// Construct an ExitStatus from a process return code
153  pub fn with_exit_code(code: u32) -> Self {
154    Self { code, signal: None }
155  }
156
157  /// Construct an ExitStatus from a signal name
158  pub fn with_signal(signal: &str) -> Self {
159    Self {
160      code: 1,
161      signal: Some(signal.to_string()),
162    }
163  }
164
165  /// Returns true if the status indicates successful completion
166  pub fn success(&self) -> bool {
167    match self.signal {
168      None => self.code == 0,
169      Some(_) => false,
170    }
171  }
172
173  /// Returns the exit code that this ExitStatus was constructed with
174  pub fn exit_code(&self) -> u32 {
175    self.code
176  }
177
178  /// Returns the signal if present that this ExitStatus was constructed with
179  pub fn signal(&self) -> Option<&str> {
180    self.signal.as_deref()
181  }
182}
183
184impl From<std::process::ExitStatus> for ExitStatus {
185  fn from(status: std::process::ExitStatus) -> Self {
186    #[cfg(unix)]
187    {
188      use std::os::unix::process::ExitStatusExt;
189
190      if let Some(signal) = status.signal() {
191        let signame = unsafe { libc::strsignal(signal) };
192        let signal = if signame.is_null() {
193          format!("Signal {signal}")
194        } else {
195          let signame = unsafe { std::ffi::CStr::from_ptr(signame) };
196          signame.to_string_lossy().to_string()
197        };
198
199        return Self {
200          code: status.code().map(|c| c as u32).unwrap_or(1),
201          signal: Some(signal),
202        };
203      }
204    }
205
206    let code = status
207      .code()
208      .map(|c| c as u32)
209      .unwrap_or_else(|| if status.success() { 0 } else { 1 });
210
211    Self { code, signal: None }
212  }
213}
214
215impl std::fmt::Display for ExitStatus {
216  fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
217    if self.success() {
218      write!(fmt, "Success")
219    } else {
220      match &self.signal {
221        Some(sig) => write!(fmt, "Terminated by {sig}"),
222        None => write!(fmt, "Exited with code {}", self.code),
223      }
224    }
225  }
226}
227
228pub struct PtyPair {
229  // slave is listed first so that it is dropped first.
230  // The drop order is stable and specified by rust rfc 1857
231  pub slave: UnixSlavePty,
232  pub master: UnixMasterPty,
233}
234
235/// The `PtySystem` trait allows an application to work with multiple
236/// possible Pty implementations at runtime.  This is important on
237/// Windows systems which have a variety of implementations.
238pub trait PtySystem {
239  /// Create a new Pty instance with the window size set to the specified
240  /// dimensions.  Returns a (master, slave) Pty pair.  The master side
241  /// is used to drive the slave side.
242  fn openpty(&self, size: PtySize) -> color_eyre::Result<PtyPair>;
243}
244
245impl Child for std::process::Child {
246  fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
247    Self::try_wait(self).map(|s| s.map(Into::into))
248  }
249
250  fn wait(&mut self) -> IoResult<ExitStatus> {
251    Self::wait(self).map(Into::into)
252  }
253
254  fn process_id(&self) -> Pid {
255    Pid::from_raw(self.id() as pid_t)
256  }
257}
258
259#[derive(Debug)]
260struct ProcessSignaller {
261  pid: Option<Pid>,
262}
263
264impl ChildKiller for ProcessSignaller {
265  fn kill(&mut self) -> IoResult<()> {
266    if let Some(pid) = self.pid {
267      let result = unsafe { libc::kill(pid.as_raw(), libc::SIGHUP) };
268      if result != 0 {
269        return Err(std::io::Error::last_os_error());
270      }
271    }
272    Ok(())
273  }
274
275  fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
276    Box::new(Self { pid: self.pid })
277  }
278}
279
280impl ChildKiller for std::process::Child {
281  fn kill(&mut self) -> IoResult<()> {
282    #[cfg(unix)]
283    {
284      // On unix, we send the SIGHUP signal instead of trying to kill
285      // the process. The default behavior of a process receiving this
286      // signal is to be killed unless it configured a signal handler.
287      let result = unsafe { libc::kill(self.id() as i32, libc::SIGHUP) };
288      if result != 0 {
289        return Err(std::io::Error::last_os_error());
290      }
291
292      // We successfully delivered SIGHUP, but the semantics of Child::kill
293      // are that on success the process is dead or shortly about to
294      // terminate.  Since SIGUP doesn't guarantee termination, we
295      // give the process a bit of a grace period to shutdown or do whatever
296      // it is doing in its signal handler before we proceed with the
297      // full on kill.
298      for attempt in 0..5 {
299        if attempt > 0 {
300          std::thread::sleep(std::time::Duration::from_millis(50));
301        }
302
303        if let Ok(Some(_)) = self.try_wait() {
304          // It completed, so report success!
305          return Ok(());
306        }
307      }
308
309      // it's still alive after a grace period, so proceed with a kill
310    }
311
312    Self::kill(self)
313  }
314
315  fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
316    Box::new(ProcessSignaller {
317      pid: Some(self.process_id()),
318    })
319  }
320}
321
322pub fn native_pty_system() -> NativePtySystem {
323  NativePtySystem::default()
324}
325
326pub type NativePtySystem = UnixPtySystem;
327
328#[derive(Default)]
329pub struct UnixPtySystem {}
330
331fn openpty(size: PtySize) -> color_eyre::Result<(UnixMasterPty, UnixSlavePty)> {
332  let mut master: RawFd = -1;
333  let mut slave: RawFd = -1;
334
335  let mut size = winsize {
336    ws_row: size.rows,
337    ws_col: size.cols,
338    ws_xpixel: size.pixel_width,
339    ws_ypixel: size.pixel_height,
340  };
341
342  let result = unsafe {
343    libc::openpty(
344      &mut master,
345      &mut slave,
346      ptr::null_mut(),
347      ptr::null_mut(),
348      &size,
349    )
350  };
351
352  if result != 0 {
353    bail!("failed to openpty: {:?}", io::Error::last_os_error());
354  }
355
356  let tty_name = tty_name(slave);
357
358  let master = UnixMasterPty {
359    fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(master) }),
360    took_writer: RefCell::new(false),
361    tty_name,
362  };
363  let slave = UnixSlavePty {
364    fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(slave) }),
365  };
366
367  // Ensure that these descriptors will get closed when we execute
368  // the child process.  This is done after constructing the Pty
369  // instances so that we ensure that the Ptys get drop()'d if
370  // the cloexec() functions fail (unlikely!).
371  cloexec(master.fd.as_raw_fd())?;
372  cloexec(slave.fd.as_raw_fd())?;
373
374  Ok((master, slave))
375}
376
377impl PtySystem for UnixPtySystem {
378  fn openpty(&self, size: PtySize) -> color_eyre::Result<PtyPair> {
379    let (master, slave) = openpty(size)?;
380    Ok(PtyPair { master, slave })
381  }
382}
383
384pub struct PtyFd(pub FileDescriptor);
385impl std::ops::Deref for PtyFd {
386  type Target = FileDescriptor;
387  fn deref(&self) -> &FileDescriptor {
388    &self.0
389  }
390}
391impl std::ops::DerefMut for PtyFd {
392  fn deref_mut(&mut self) -> &mut FileDescriptor {
393    &mut self.0
394  }
395}
396
397impl Read for PtyFd {
398  fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
399    match self.0.read(buf) {
400      Err(ref e) if e.raw_os_error() == Some(libc::EIO) => {
401        // EIO indicates that the slave pty has been closed.
402        // Treat this as EOF so that std::io::Read::read_to_string
403        // and similar functions gracefully terminate when they
404        // encounter this condition
405        Ok(0)
406      }
407      x => x,
408    }
409  }
410}
411
412fn tty_name(fd: RawFd) -> Option<PathBuf> {
413  let mut buf = vec![0 as std::ffi::c_char; 128];
414
415  loop {
416    let res = unsafe { libc::ttyname_r(fd, buf.as_mut_ptr(), buf.len()) };
417
418    if res == libc::ERANGE {
419      if buf.len() > 64 * 1024 {
420        // on macOS, if the buf is "too big", ttyname_r can
421        // return ERANGE, even though that is supposed to
422        // indicate buf is "too small".
423        return None;
424      }
425      buf.resize(buf.len() * 2, 0 as std::ffi::c_char);
426      continue;
427    }
428
429    return if res == 0 {
430      let cstr = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) };
431      let osstr = OsStr::from_bytes(cstr.to_bytes());
432      Some(PathBuf::from(osstr))
433    } else {
434      None
435    };
436  }
437}
438
439/// On Big Sur, Cocoa leaks various file descriptors to child processes,
440/// so we need to make a pass through the open descriptors beyond just the
441/// stdio descriptors and close them all out.
442/// This is approximately equivalent to the darwin `posix_spawnattr_setflags`
443/// option POSIX_SPAWN_CLOEXEC_DEFAULT which is used as a bit of a cheat
444/// on macOS.
445/// On Linux, gnome/mutter leak shell extension fds to wezterm too, so we
446/// also need to make an effort to clean up the mess.
447///
448/// This function enumerates the open filedescriptors in the current process
449/// and then will forcibly call close(2) on each open fd that is numbered
450/// 3 or higher, effectively closing all descriptors except for the stdio
451/// streams.
452///
453/// The implementation of this function relies on `/dev/fd` being available
454/// to provide the list of open fds.  Any errors in enumerating or closing
455/// the fds are silently ignored.
456fn close_random_fds() {
457  // FreeBSD, macOS and presumably other BSDish systems have /dev/fd as
458  // a directory listing the current fd numbers for the process.
459  //
460  // On Linux, /dev/fd is a symlink to /proc/self/fd
461  if let Ok(dir) = std::fs::read_dir("/proc/self/fd").or_else(|_| std::fs::read_dir("/dev/fd")) {
462    let mut fds = vec![];
463    for entry in dir {
464      if let Some(num) = entry
465        .ok()
466        .map(|e| e.file_name())
467        .and_then(|s| s.into_string().ok())
468        .and_then(|n| n.parse::<libc::c_int>().ok())
469        && num > 2
470      {
471        fds.push(num);
472      }
473    }
474    for fd in fds {
475      let _ = nix::unistd::close(fd);
476    }
477  }
478}
479
480impl PtyFd {
481  fn resize(&self, size: PtySize) -> Result<(), Error> {
482    let ws_size = winsize {
483      ws_row: size.rows,
484      ws_col: size.cols,
485      ws_xpixel: size.pixel_width,
486      ws_ypixel: size.pixel_height,
487    };
488
489    if unsafe {
490      libc::ioctl(
491        self.0.as_raw_fd(),
492        libc::TIOCSWINSZ as _,
493        &ws_size as *const _,
494      )
495    } != 0
496    {
497      bail!(
498        "failed to ioctl(TIOCSWINSZ): {:?}",
499        io::Error::last_os_error()
500      );
501    }
502
503    Ok(())
504  }
505
506  fn get_size(&self) -> Result<PtySize, Error> {
507    let mut size: winsize = unsafe { mem::zeroed() };
508    if unsafe {
509      libc::ioctl(
510        self.0.as_raw_fd(),
511        libc::TIOCGWINSZ as _,
512        &mut size as *mut _,
513      )
514    } != 0
515    {
516      bail!(
517        "failed to ioctl(TIOCGWINSZ): {:?}",
518        io::Error::last_os_error()
519      );
520    }
521    Ok(PtySize {
522      rows: size.ws_row,
523      cols: size.ws_col,
524      pixel_width: size.ws_xpixel,
525      pixel_height: size.ws_ypixel,
526    })
527  }
528
529  fn spawn_command(
530    &self,
531    command: CommandBuilder,
532    pre_exec: impl FnOnce(&Path) -> color_eyre::Result<()> + Send + Sync + 'static,
533  ) -> color_eyre::Result<Pid> {
534    spawn_command_from_pty_fd(Some(self), command, pre_exec)
535  }
536}
537
538pub fn spawn_command(
539  pts: Option<&UnixSlavePty>,
540  command: CommandBuilder,
541  pre_exec: impl FnOnce(&Path) -> color_eyre::Result<()> + Send + Sync + 'static,
542) -> color_eyre::Result<Pid> {
543  if let Some(pts) = pts {
544    pts.spawn_command(command, pre_exec)
545  } else {
546    spawn_command_from_pty_fd(None, command, pre_exec)
547  }
548}
549
550fn spawn_command_from_pty_fd(
551  pty: Option<&PtyFd>,
552  command: CommandBuilder,
553  pre_exec: impl FnOnce(&Path) -> color_eyre::Result<()> + Send + Sync + 'static,
554) -> color_eyre::Result<Pid> {
555  let configured_umask = command.umask;
556
557  let mut cmd = command.build()?;
558
559  match unsafe { fork()? } {
560    nix::unistd::ForkResult::Parent { child } => Ok(child),
561    nix::unistd::ForkResult::Child => {
562      if let Some(pty) = pty {
563        let mut stdin = unsafe { OwnedFd::from_raw_fd(0) };
564        let mut stdout = unsafe { OwnedFd::from_raw_fd(1) };
565        let mut stderr = unsafe { OwnedFd::from_raw_fd(2) };
566        dup2(pty.as_fd(), &mut stdin).unwrap();
567        dup2(pty.as_fd(), &mut stdout).unwrap();
568        dup2(pty.as_fd(), &mut stderr).unwrap();
569        std::mem::forget(stdin);
570        std::mem::forget(stdout);
571        std::mem::forget(stderr);
572      }
573
574      // Clean up a few things before we exec the program
575      // Clear out any potentially problematic signal
576      // dispositions that we might have inherited
577      for signo in &[
578        libc::SIGCHLD,
579        libc::SIGHUP,
580        libc::SIGINT,
581        libc::SIGQUIT,
582        libc::SIGTERM,
583        libc::SIGALRM,
584      ] {
585        unsafe {
586          _ = libc::signal(*signo, libc::SIG_DFL);
587        }
588      }
589
590      unsafe {
591        let empty_set: libc::sigset_t = std::mem::zeroed();
592        _ = libc::sigprocmask(libc::SIG_SETMASK, &empty_set, std::ptr::null_mut());
593      }
594
595      pre_exec(&cmd.program).unwrap();
596
597      close_random_fds();
598
599      if let Some(mask) = configured_umask {
600        _ = unsafe { libc::umask(mask) };
601      }
602
603      execv(
604        &CString::new(cmd.program.into_os_string().into_vec()).unwrap(),
605        &cmd.args,
606      )
607      .unwrap();
608      unreachable!()
609    }
610  }
611}
612
613/// Represents the master end of a pty.
614/// The file descriptor will be closed when the Pty is dropped.
615pub struct UnixMasterPty {
616  fd: PtyFd,
617  took_writer: RefCell<bool>,
618  tty_name: Option<PathBuf>,
619}
620
621/// Represents the slave end of a pty.
622/// The file descriptor will be closed when the Pty is dropped.
623pub struct UnixSlavePty {
624  pub fd: PtyFd,
625}
626
627impl UnixSlavePty {
628  pub fn spawn_command(
629    &self,
630    command: CommandBuilder,
631    pre_exec: impl FnOnce(&Path) -> color_eyre::Result<()> + Send + Sync + 'static,
632  ) -> color_eyre::Result<Pid> {
633    self.fd.spawn_command(command, pre_exec)
634  }
635}
636
637/// Helper function to set the close-on-exec flag for a raw descriptor
638fn cloexec(fd: RawFd) -> Result<(), Error> {
639  let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
640  if flags == -1 {
641    bail!(
642      "fcntl to read flags failed: {:?}",
643      io::Error::last_os_error()
644    );
645  }
646  let result = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) };
647  if result == -1 {
648    bail!(
649      "fcntl to set CLOEXEC failed: {:?}",
650      io::Error::last_os_error()
651    );
652  }
653  Ok(())
654}
655
656impl MasterPty for UnixMasterPty {
657  fn resize(&self, size: PtySize) -> Result<(), Error> {
658    self.fd.resize(size)
659  }
660
661  fn get_size(&self) -> Result<PtySize, Error> {
662    self.fd.get_size()
663  }
664
665  fn try_clone_reader(&self) -> Result<Box<dyn Read + Send>, Error> {
666    let fd = PtyFd(self.fd.try_clone()?);
667    Ok(Box::new(fd))
668  }
669
670  fn take_writer(&self) -> Result<Box<dyn Write + Send>, Error> {
671    if *self.took_writer.borrow() {
672      bail!("cannot take writer more than once");
673    }
674    *self.took_writer.borrow_mut() = true;
675    let fd = PtyFd(self.fd.try_clone()?);
676    Ok(Box::new(UnixMasterWriter { fd }))
677  }
678
679  fn as_raw_fd(&self) -> Option<RawFd> {
680    Some(self.fd.0.as_raw_fd())
681  }
682
683  fn tty_name(&self) -> Option<PathBuf> {
684    self.tty_name.clone()
685  }
686
687  fn process_group_leader(&self) -> Option<libc::pid_t> {
688    match unsafe { libc::tcgetpgrp(self.fd.0.as_raw_fd()) } {
689      pid if pid > 0 => Some(pid),
690      _ => None,
691    }
692  }
693
694  fn get_termios(&self) -> Option<nix::sys::termios::Termios> {
695    nix::sys::termios::tcgetattr(unsafe { File::from_raw_fd(self.fd.0.as_raw_fd()) }).ok()
696  }
697}
698
699/// Represents the master end of a pty.
700/// EOT will be sent, and then the file descriptor will be closed when
701/// the Pty is dropped.
702struct UnixMasterWriter {
703  fd: PtyFd,
704}
705
706impl Drop for UnixMasterWriter {
707  fn drop(&mut self) {
708    let mut t: libc::termios = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
709    if unsafe { libc::tcgetattr(self.fd.0.as_raw_fd(), &mut t) } == 0 {
710      // EOF is only interpreted after a newline, so if it is set,
711      // we send a newline followed by EOF.
712      let eot = t.c_cc[libc::VEOF];
713      if eot != 0 {
714        let _ = self.fd.0.write_all(&[b'\n', eot]);
715      }
716    }
717  }
718}
719
720impl Write for UnixMasterWriter {
721  fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
722    self.fd.write(buf)
723  }
724  fn flush(&mut self) -> Result<(), io::Error> {
725    self.fd.flush()
726  }
727}